-
Notifications
You must be signed in to change notification settings - Fork 128
/
MFServerFS.m
622 lines (537 loc) · 20.3 KB
/
MFServerFS.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
//
// MFServerFS.m
// MacFusion2
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "MFServerFS.h"
#import "MFConstants.h"
#import "MFPluginController.h"
#import "MFError.h"
#import "MFLogging.h"
#import "MFPreferences.h"
#import <sys/xattr.h>
#define FS_DIR_PATH @"~/Library/Application Support/Macfusion/Filesystems"
@interface MFServerFS (PrivateAPI)
- (MFServerFS *)initWithPlugin:(MFServerPlugin *)p;
- (MFServerFS *)initWithParameters:(NSDictionary*)params plugin:(MFServerPlugin*)p;
- (NSMutableDictionary *)fullParametersWithDictionary:(NSDictionary *)fsParams;
- (void)registerGeneralNotifications;
- (NSMutableDictionary *)initializedStatusInfo;
- (void)writeOutData;
- (NSString *)getNewUUID;
- (BOOL)validateParameters:(NSDictionary*)params error:(NSError**)error;
- (NSError *)genericError;
- (void)setError:(NSError*)error;
- (NSTimer *)newTimeoutTimer;
@end
@implementation MFServerFS
+ (MFServerFS *)newFilesystemWithPlugin:(MFServerPlugin *)plugin {
if (plugin) {
Class FSClass = [plugin subclassForClass:self];
return [[FSClass alloc] initWithPlugin:plugin];
}
return nil;
}
+ (MFServerFS *)loadFilesystemAtPath:(NSString *)path error:(NSError **)error {
MFServerFS *fs;
NSMutableDictionary *fsParameters = [NSMutableDictionary dictionaryWithContentsOfFile:path];
if (!fsParameters) {
NSDictionary* errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
@"Could not read dictionary data for filesystem", NSLocalizedDescriptionKey,
[NSString stringWithFormat:@"File at path %@", path], NSLocalizedRecoverySuggestionErrorKey,
nil];
if (error) {
*error = [NSError errorWithDomain:kMFErrorDomain code:kMFErrorCodeDataCannotBeRead userInfo:errorDict];
}
return nil;
}
[fsParameters setObject:path forKey:kMFFSFilePathParameter];
[fsParameters setObject:[NSNumber numberWithBool:YES] forKey:kMFFSPersistentParameter];
NSString *pluginID = [fsParameters objectForKey:kMFFSTypeParameter];
if (!pluginID) {
NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
@"Could not read plugin id key for filesystem", NSLocalizedDescriptionKey,
[NSString stringWithFormat:@"File at path %@", path], NSLocalizedRecoverySuggestionErrorKey,
nil];
if (error) {
*error = [NSError errorWithDomain:kMFErrorDomain code:kMFErrorCodeMissingParameter userInfo:errorDict];
}
return nil;
}
MFServerPlugin *plugin = [[MFPluginController sharedController] pluginWithID:pluginID];
if (plugin) {
Class FSClass = [plugin subclassForClass:self];
fs = [[FSClass alloc] initWithParameters:fsParameters plugin:plugin];
NSError *validationError;
BOOL ok = [fs validateParametersWithError:&validationError];
if (ok) {
return fs;
} else {
if (error) {
*error = validationError;
}
return nil;
}
} else {
if (error) {
NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
@"Invalid plugin ID given", NSLocalizedDescriptionKey,
[NSString stringWithFormat:@"File at path %@", path], NSLocalizedRecoverySuggestionErrorKey,
nil];
*error = [NSError errorWithDomain:kMFErrorDomain code:kMFErrorCodeInvalidParameterValue userInfo:errorDict];
}
return nil;
}
}
+ (MFServerFS *)filesystemFromURL:(NSURL *)url plugin:(MFServerPlugin *)p error:(NSError **)error {
NSMutableDictionary* params = [[[p delegate] parameterDictionaryForURL:url error:error] mutableCopy];
if (!params) {
if (error) {
*error = [MFError errorWithErrorCode:kMFErrorCodeMountFaliure description:@"Plugin failed to parse URL"];
}
return nil;
}
[params setValue:[NSNumber numberWithBool:NO] forKey:kMFFSPersistentParameter];
[params setValue:p.ID forKey:kMFFSTypeParameter];
[params setValue:[NSString stringWithFormat:@"%@", url] forKey:kMFFSDescriptionParameter];
Class FSClass = [p subclassForClass:self];
MFServerFS* fs = [[FSClass alloc] initWithParameters:params plugin:p];
NSError *validationError;
BOOL ok = [fs validateParametersWithError:&validationError];
if (!ok) {
if (error) {
*error = validationError;
}
return nil;
} else {
return fs;
}
}
- (MFServerFS *)initWithParameters:(NSDictionary *)params plugin:(MFServerPlugin *)p {
if (self = [super init]) {
[self setPlugin:p];
delegate = [p delegate];
parameters = [self fullParametersWithDictionary:params];
statusInfo = [self initializedStatusInfo];
_pauseTimeout = NO;
if (![parameters objectForKey:kMFFSUUIDParameter]) {
[parameters setObject:[self getNewUUID] forKey:kMFFSUUIDParameter];
}
[self registerGeneralNotifications];
}
return self;
}
- (MFServerFS *)initWithPlugin:(MFServerPlugin *)p {
NSAssert(p, @"Plugin null in MFServerFS initWithPlugin");
NSDictionary *newFSParameters = [NSDictionary dictionaryWithObjectsAndKeys:
p.ID, kMFFSTypeParameter,
[NSNumber numberWithBool:YES], kMFFSPersistentParameter,
nil];
return [self initWithParameters:newFSParameters plugin:p];
}
- (void)registerGeneralNotifications {
[self addObserver:self
forKeyPath:KMFStatusDict
options:NSKeyValueObservingOptionOld || NSKeyValueObservingOptionNew
context:nil];
[self addObserver:self
forKeyPath:kMFParameterDict
options:NSKeyValueObservingOptionOld || NSKeyValueObservingOptionNew
context:nil];
[self addObserver:self
forKeyPath:kMFSTStatusKey
options:NSKeyValueObservingOptionOld || NSKeyValueObservingOptionNew
context:nil];
}
- (NSMutableDictionary *)initializedStatusInfo {
NSMutableDictionary *initialStatusInfo = [NSMutableDictionary dictionaryWithCapacity:5];
// Initialize the important keys in the status dictionary
[initialStatusInfo setObject:kMFStatusFSUnmounted forKey:kMFSTStatusKey];
[initialStatusInfo setObject:[NSMutableString stringWithString:@""] forKey:kMFSTOutputKey];
return initialStatusInfo;
}
- (NSString *)getNewUUID {
CFUUIDRef uuidObject = CFUUIDCreate(NULL);
CFStringRef uuidCFString = CFUUIDCreateString(NULL, uuidObject);
CFRelease(uuidObject);
return [NSMakeCollectable(uuidCFString) autorelease];
}
- (NSDictionary *)defaultParameterDictionary {
NSMutableDictionary *defaultParameterDictionary = [NSMutableDictionary dictionary];
NSDictionary *delegateDict = [delegate defaultParameterDictionary];
[defaultParameterDictionary addEntriesFromDictionary:delegateDict];
[defaultParameterDictionary setObject:[NSNumber numberWithBool:NO] forKey:kMFFSNegativeVNodeCacheParameter];
[defaultParameterDictionary setObject:[NSNumber numberWithBool:NO] forKey:kMFFSNoAppleDoubleParameter];
return [defaultParameterDictionary copy];
}
# pragma mark Parameter processing
- (NSMutableDictionary *)fullParametersWithDictionary:(NSDictionary *)fsParams {
NSDictionary *defaultParams = [self defaultParameterDictionary];
NSMutableDictionary *params = [fsParams mutableCopy];
if (!params) {
params = [NSMutableDictionary dictionary];
}
for(NSString *parameterKey in [defaultParams allKeys]) {
if ([fsParams objectForKey:parameterKey]) {
} else {
[params setObject:[defaultParams objectForKey:parameterKey] forKey:parameterKey];
}
}
return params;
}
# pragma mark Initialization
# pragma mark Task Creation methods
- (NSDictionary *)taskEnvironment {
if ([delegate respondsToSelector:@selector(taskEnvironmentForParameters:)]) {
return [delegate taskEnvironmentForParameters:[self parametersWithImpliedValues]];
} else {
return [[NSProcessInfo processInfo] environment];
}
}
- (NSArray *)taskArguments
{
NSArray *delegateArgs;
NSMutableArray *taskArguments = [NSMutableArray array];
// MFLogS(self, @"Parameters are %@, implied parameters are %@", parameters, [self parametersWithImpliedValues]);
if ([delegate respondsToSelector:@selector(taskArgumentsForParameters:)]) {
delegateArgs = [delegate taskArgumentsForParameters:[self parametersWithImpliedValues]];
if (!delegateArgs || [delegateArgs count] == 0) {
MFLogS(self, @"Delegate returned nil arguments or empty array!");
return nil;
} else {
[taskArguments addObjectsFromArray:delegateArgs];
NSString *advancedArgumentsString = [self.parameters objectForKey:kMFFSAdvancedOptionsParameter];
NSArray *advancedArguments = [advancedArgumentsString componentsSeparatedByString:@" "];
[taskArguments addObjectsFromArray:advancedArguments];
if ([[self.parameters objectForKey:kMFFSNoAppleDoubleParameter] boolValue]) {
[taskArguments addObject:@"-onoappledouble"];
}
if ([[self.parameters objectForKey:kMFFSNegativeVNodeCacheParameter] boolValue]) {
[taskArguments addObject:@"-onegative_vncache"];
}
return taskArguments;
}
} else {
MFLogS(self, @"Could not get task arguments for delegate!");
return nil;
}
}
- (void)setupIOForTask:(NSTask *)t {
NSPipe *outputPipe = [[NSPipe alloc] init];
NSPipe *inputPipe = [[NSPipe alloc] init];
[t setStandardError:outputPipe];
[t setStandardOutput:outputPipe];
[t setStandardInput:inputPipe];
}
- (void)registerNotificationsForTask:(NSTask *)t {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(handleDataOnPipe:) name:NSFileHandleDataAvailableNotification object:[[t standardOutput] fileHandleForReading]];
[nc addObserver:self selector:@selector(handleTaskDidTerminate:) name:NSTaskDidTerminateNotification object:t];
}
- (NSTask *)taskForLaunch {
NSTask *t = [[NSTask alloc] init];
// Pull together all the tasks parameters
NSDictionary *env = [self taskEnvironment];
[t setEnvironment:env];
NSArray *args = [self taskArguments];
[t setArguments:args];
NSString *launchPath = [delegate executablePath];
if (launchPath) {
[t setLaunchPath:launchPath];
} else {
MFLogS(self, @"Delegate returned nil executable path");
return nil;
}
// MFLogS(self, @"Executing task with path %@ env %@ args %@", [t launchPath], [t environment], [t arguments]);
[self setupIOForTask:t];
[self registerNotificationsForTask:t];
return t;
}
# pragma mark Mounting mechanics
- (BOOL)setupMountPoint {
NSFileManager *fm = [NSFileManager defaultManager];
NSString *mountPath = [self mountPath];
BOOL pathExists, isDir, returnValue;
NSString *errorDescription;
NSAssert(mountPath, @"Attempted to filesystem with nil mountPath.");
pathExists = [fm fileExistsAtPath:mountPath isDirectory:&isDir];
if (pathExists && isDir == YES) {
// directory already exists
BOOL empty = ([[fm contentsOfDirectoryAtPath:mountPath error:nil] count] == 0);
BOOL writeable = [fm isWritableFileAtPath:mountPath];
if (!empty) {
errorDescription = @"Mount path directory in use.";
returnValue = NO;
} else if (!writeable) {
errorDescription = @"Mount path directory not writeable.";
returnValue = NO;
} else {
returnValue = YES;
}
} else if (pathExists && !isDir) {
errorDescription = @"Mount path is a file, not a directory.";
returnValue = NO;
} else {
if ([fm createDirectoryAtPath:mountPath withIntermediateDirectories:YES attributes:nil error:nil]) {
returnValue = YES;
} else {
errorDescription = @"Mount path could not be created.";
returnValue = NO;
}
}
if (returnValue == NO) {
NSError *error = [MFError errorWithErrorCode:kMFErrorCodeMountFaliure description:errorDescription];
[statusInfo setObject:error forKey:kMFSTErrorKey];
return NO;
} else {
return YES;
}
}
- (void)removeMountPoint {
NSFileManager *fm = [NSFileManager defaultManager];
NSString *mountPath = [self mountPath];
BOOL pathExists, isDir;
removexattr([mountPath cStringUsingEncoding:NSUTF8StringEncoding],[@"org.mgorbach.macfusion.xattr.uuid" cStringUsingEncoding:NSUTF8StringEncoding],0);
pathExists = [fm fileExistsAtPath:mountPath isDirectory:&isDir];
if (pathExists && isDir && ([[fm contentsOfDirectoryAtPath:mountPath error:nil] count] == 0)) {
[fm removeItemAtPath:mountPath error:nil];
}
}
- (void)mount {
if (self.status == kMFStatusFSMounted) {
return;
}
MFLogS(self, @"Mounting");
self.pauseTimeout = NO;
self.status = kMFStatusFSWaiting;
if ([self setupMountPoint] == YES) {
_task = [self taskForLaunch];
[[[_task standardOutput] fileHandleForReading] waitForDataInBackgroundAndNotify];
[_timer invalidate];
_timer = [self newTimeoutTimer];
[_task launch];
MFLogS(self, @"Task launched OK");
} else {
MFLogS(self, @"Mount point could not be created");
self.status = kMFStatusFSFailed;
}
}
- (void)unmount {
MFLogS(self, @"Unmounting");
NSString* path = [[self mountPath] stringByStandardizingPath];
NSString *taskPath = @"/sbin/umount";
NSTask* t = [[NSTask alloc] init];
[t setLaunchPath:taskPath];
[t setArguments:[NSArray arrayWithObject:path]];
[t launch];
/*
[t waitUntilExit];
if ([t terminationStatus] != 0)
{
MFLogS(self, @"Unmount failed. Unmount terminated with %d",
[t terminationStatus]);
}
*/
}
# pragma mark Validation
- (NSError *)validateAndSetParameters:(NSDictionary *)params {
NSError* error;
if ([self validateParameters:params error:&error]) {
[self willChangeValueForKey:kMFParameterDict];
parameters = [params mutableCopy];
[self didChangeValueForKey:kMFParameterDict];
} else {
return error;
}
return nil;
}
- (BOOL)validateParameters:(NSDictionary*)params error:(NSError **)error
{
NSDictionary* impliedParams = [self fillParametersWithImpliedValues:params];
BOOL ok = [delegate validateParameters:impliedParams error:error];
if (!ok) {
// Delegate didn't validate
// MFLogS(self, @"Delegate didn't validate %@", impliedParams);
return NO;
} else {
// MFLogS(self, @"Delegate did validate %@", impliedParams);
// Continue validation for general macfusion keys
if (![impliedParams objectForKey:kMFFSVolumeNameParameter]) {
if (error) {
*error = [MFError parameterMissingErrorWithParameterName:kMFFSVolumeNameParameter];
}
return NO;
}
if (![impliedParams objectForKey:kMFFSMountPathParameter]) {
if (error) {
*error = [MFError parameterMissingErrorWithParameterName:kMFFSMountPathParameter];
}
return NO;
}
if (![impliedParams objectForKey:kMFFSUUIDParameter]) {
if (error) {
*error = [MFError parameterMissingErrorWithParameterName:kMFFSUUIDParameter];
}
return NO;
}
}
return YES;
}
- (BOOL)validateParametersWithError:(NSError **)error {
return [self validateParameters:parameters error:error];
}
# pragma mark Notification handlers
- (void)handleMountNotification {
self.status = kMFStatusFSMounted;
[_timer invalidate];
}
- (void)handleTaskDidTerminate:(NSNotification *)note {
if (self.status == kMFStatusFSMounted) {
// We are terminating after a mount has been successful
// This may not quite be normal (may be for example a bad net connection)
// But we'll set status to unmounted anyway
self.status = kMFStatusFSUnmounted;
} else if (self.status == kMFStatusFSWaiting) {
// We terminated while trying to mount
NSDictionary* dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
self.uuid, kMFErrorFilesystemKey,
@"Mount process has terminated unexpectedly.", NSLocalizedDescriptionKey,
nil];
[self setError:[MFError errorWithDomain:kMFErrorDomain code:kMFErrorCodeMountFaliure userInfo:dictionary]];
self.status = kMFStatusFSFailed;
}
}
- (void)appendToOutput:(NSString *)newOutput {
NSMutableString *output = [statusInfo objectForKey:kMFSTOutputKey];
[output appendString:newOutput];
}
- (void)handleDataOnPipe:(NSNotification *)note {
NSData *pipeData = [[note object] availableData];
if ([pipeData length] == 0) {
// pipe is now closed
return;
} else {
NSString *recentOutput = [[NSString alloc] initWithData:pipeData encoding:NSUTF8StringEncoding];
[self appendToOutput:recentOutput];
[[note object] waitForDataInBackgroundAndNotify];
MFLogS(self, recentOutput);
}
}
- (void)handleUnmountNotification {
self.status = kMFStatusFSUnmounted;
[_timer invalidate];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
//MFLogS(self, @"Observes notification keypath %@ object %@, change %@",
// keyPath, object, change);
if ([keyPath isEqualToString:kMFSTStatusKey ] && object == self && [[change objectForKey:NSKeyValueChangeNewKey] isEqualToString:kMFStatusFSUnmounted]) {
[self removeMountPoint];
}
if ([keyPath isEqualToString:kMFSTStatusKey ] && object == self && [[change objectForKey:NSKeyValueChangeNewKey] isEqualToString:kMFStatusFSFailed]) {
[self removeMountPoint];
}
if ([keyPath isEqualToString:kMFParameterDict]) {
[self writeOutData];
}
}
- (NSTimer *)newTimeoutTimer {
return [NSTimer scheduledTimerWithTimeInterval:[[[MFPreferences sharedPreferences] getValueForPreference:kMFPrefsTimeout] doubleValue] target:self selector:@selector(handleMountTimeout:) userInfo:nil repeats:NO];
}
- (void)handleMountTimeout:(NSTimer *)theTimer {
if (_pauseTimeout) {
// MFLogS(self, @"Timeout paused");
_timer = [self newTimeoutTimer];
return;
}
if (![self isUnmounted] && ![self isMounted]) {
if (![self isFailedToMount]) {
MFLogS(self, @"Mount time out detected. Killing task %@ pid %d",
_task, [_task processIdentifier]);
kill([_task processIdentifier], SIGKILL);
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
self.uuid, kMFErrorFilesystemKey,
@"Mount has timed out.", NSLocalizedDescriptionKey,
nil];
[self setError:[MFError errorWithDomain:kMFErrorDomain code:kMFErrorCodeMountFaliure userInfo:dictionary]];
self.status = kMFStatusFSFailed;
}
}
}
# pragma mark Write out
- (void)writeOutData {
if ([self isPersistent]) {
NSString *expandedDirPath = [@"~/Library/Application Support/Macfusion/Filesystems" stringByExpandingTildeInPath];
BOOL isDir;
if (![[NSFileManager defaultManager] fileExistsAtPath:expandedDirPath isDirectory:&isDir] || !isDir) {
NSError *error = nil;
BOOL ok = [[NSFileManager defaultManager] createDirectoryAtPath:expandedDirPath withIntermediateDirectories:YES attributes:nil error:&error];
if (!ok) {
MFLogS(self, @"Failed to create directory save filesystem %@",[error localizedDescription]);
}
}
NSString *fullPath = [self valueForParameterNamed:kMFFSFilePathParameter];
if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath]) {
BOOL deleteOK = [[NSFileManager defaultManager] removeItemAtPath:fullPath error:NULL];
if (!deleteOK) {
MFLogS(self, @"Failed to delete old file during save");
}
}
BOOL writeOK = [parameters writeToFile:fullPath atomically:NO];
if (!writeOK) {
MFLogS(self, @"Failed to write out dictionary to file %@", fullPath);
}
}
}
- (NSError *)genericError {
NSDictionary* dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
self.uuid, kMFErrorFilesystemKey,
@"Mount has failed.", NSLocalizedDescriptionKey,
nil];
return [MFError errorWithDomain:kMFErrorDomain code:kMFErrorCodeMountFaliure userInfo:dictionary];
}
#pragma mark Accessors and Setters
- (void)setStatus:(NSString *)newStatus {
if (newStatus && ![newStatus isEqualToString:self.status]) {
// Hack this a bit so that we can set an error on faliure
// Do this only if an error hasn't already been set
[statusInfo setObject:newStatus forKey:kMFSTStatusKey];
if([newStatus isEqualToString:kMFStatusFSFailed]) {
NSError *error = nil;
// Ask the delegate for the error
if ([delegate respondsToSelector:@selector(errorForParameters:output:)] && (error = [delegate errorForParameters:[self parametersWithImpliedValues] output:[statusInfo objectForKey:kMFSTOutputKey]]) && error) {
[self setError:error];
} else if (![self error]) {
// Use a generic error
[self setError:[self genericError]];
}
}
}
}
- (void)setError:(NSError *)error {
if (error) {
[statusInfo setObject:error forKey:kMFSTErrorKey];
}
}
- (BOOL)pauseTimeout {
return _pauseTimeout;
}
- (void)setPauseTimeout:(BOOL)p {
_pauseTimeout = p;
[_timer invalidate];
_timer = [self newTimeoutTimer];
}
@synthesize plugin=_plugin;
@end