Revision d8426ffb pithos-macos/PithosSyncDaemon.m

b/pithos-macos/PithosSyncDaemon.m
36 36
// or implied, of GRNET S.A.
37 37

  
38 38
#import "PithosSyncDaemon.h"
39
#import "PithosAccount.h"
39 40
#import "PithosLocalObjectState.h"
40 41
#import "PithosActivityFacility.h"
41 42
#import "PithosUtilities.h"
......
47 48
#import "ASIPithosObject.h"
48 49

  
49 50
@interface PithosSyncDaemon (Private)
51
- (void)resetLocalState;
50 52
- (NSString *)pithosStateFilePath;
51 53
- (void)saveLocalState;
52 54

  
......
58 60
- (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
59 61
                                   object:(ASIPithosObject *)object 
60 62
                            localFilePath:(NSString *)filePath;
63
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest;
61 64
- (void)requestFailed:(ASIPithosRequest *)request;
62 65

  
63
- (void)increaseSyncOperationCount;
64
- (void)decreaseSyncOperationCount;
66
- (void)syncOperationStarted;
67
- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull;
65 68

  
66 69
@end
67 70

  
68 71
@implementation PithosSyncDaemon
72
@synthesize directoryPath, pithosAccount, containerName, pithos;
69 73
@synthesize blockHash, blockSize, lastModified, lastCompletedSync, remoteObjects, storedLocalObjectStates, currentLocalObjectStates;
70
@synthesize directoryPath, containerDirectoryPath, pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
74
@synthesize containerDirectoryPath, pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
71 75

  
72 76
#pragma mark -
73 77
#pragma Object Lifecycle
74 78

  
75 79
- (id)initWithDirectoryPath:(NSString *)aDirectoryPath 
76
                     pithos:(ASIPithos *)aPithos 
80
              pithosAccount:(PithosAccount *)aPithosAccount 
77 81
              containerName:(NSString *)aContainerName 
78
               timeInterval:(NSTimeInterval)aTimeInterval 
79 82
            resetLocalState:(BOOL)resetLocalState {
80 83
    if ((self = [super init])) {
81 84
        directoryPath = [aDirectoryPath copy];
82
        pithos = [aPithos retain];
85
        pithosAccount = [aPithosAccount retain];
83 86
        containerName = [aContainerName copy];
84
        timeInterval = aTimeInterval;
85
        
86
        syncOperationCount = 0;
87
        newSyncRequested = NO;
88
        containerDirectoryPath = [[directoryPath stringByAppendingPathComponent:containerName] copy];
87
        self.pithos = pithosAccount.pithos;
89 88
        
90 89
        activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
91 90
        
92
        NSFileManager *fileManager = [NSFileManager defaultManager];
93
        if (resetLocalState) {
94
            NSError *error = nil;
95
            if ([fileManager fileExistsAtPath:self.pithosStateFilePath] && 
96
                (![fileManager removeItemAtPath:self.pithosStateFilePath error:&error] || error))
97
                [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
98
                                                        message:[NSString stringWithFormat:@"Cannot remove file at '%@'", self.pithosStateFilePath] 
99
                                                          error:error];
100
            if (self.tempDownloadsDirPath) {
101
                error = nil;
102
                for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
103
                    if (error) {
104
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
105
                                                                message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath] 
106
                                                                  error:error];
107
                        break;
108
                    }
109
                    NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
110
                    if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
111
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
112
                                                                message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
113
                                                                  error:error];
114
                    }
115
                    error = nil;
116
                }
117
            }
118
        }
119
        if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath]) {
120
            NSData *data = [NSData dataWithContentsOfFile:self.pithosStateFilePath];
121
            NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
122
            self.storedLocalObjectStates = [unarchiver decodeObjectForKey:containerName];
123
            [unarchiver finishDecoding];
124
        } else {
125
            self.storedLocalObjectStates = [NSMutableDictionary dictionary];
126
        }
91
        if (resetLocalState)
92
            [self resetLocalState];
127 93
        
128 94
        networkQueue = [[ASINetworkQueue alloc] init];
129 95
        networkQueue.showAccurateProgress = YES;
130 96
        networkQueue.shouldCancelAllRequestsOnFailure = NO;
131
        [networkQueue go];
97
//        networkQueue.maxConcurrentOperationCount = 1;
132 98
        
133
        queue = dispatch_queue_create("gr.grnet.pithos.SyncQueue", NULL);
99
        callbackQueue = [[NSOperationQueue alloc] init];
100
        [callbackQueue setSuspended:YES];
101
        callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
102
//        callbackQueue.maxConcurrentOperationCount = 1;
134 103
        
135 104
        [[NSNotificationCenter defaultCenter] addObserver:self
136 105
                                                 selector:@selector(applicationWillTerminate:)
137 106
                                                     name:NSApplicationWillTerminateNotification
138 107
                                                   object:[NSApplication sharedApplication]];
108
        
109
        [self startDaemon];
110
    }
111
    return self;
112
}
113

  
114
- (void)resetLocalState {
115
    self.lastCompletedSync = nil;
116
    NSFileManager *fileManager = [NSFileManager defaultManager];
117
    NSError *error = nil;
118
    if ([fileManager fileExistsAtPath:self.pithosStateFilePath] && 
119
        (![fileManager removeItemAtPath:self.pithosStateFilePath error:&error] || error))
120
        [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
121
                                                message:[NSString stringWithFormat:@"Cannot remove file at '%@'", self.pithosStateFilePath] 
122
                                                  error:error];
123
    if (self.tempDownloadsDirPath) {
124
        error = nil;
125
        for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
126
            if (error) {
127
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
128
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath] 
129
                                                          error:error];
130
                break;
131
            }
132
            NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
133
            if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
134
                [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
135
                                                        message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
136
                                                          error:error];
137
            }
138
            error = nil;
139
        }
140
    }
141
}
142

  
143
- (void)resetDaemon {
144
    @synchronized(self) {
145
        if (!daemonActive)
146
            return;
147
    }
139 148
    
140
        timer = [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(sync) userInfo:nil repeats:YES] retain];
141
        [timer fire];
149
    [networkQueue reset];
150
    [callbackQueue cancelAllOperations];
151
    [callbackQueue setSuspended:YES];
152
    [self emptyTempTrash];
153
    
154
    @synchronized(self) {            
155
        daemonActive = NO;
156
    }
157
}
158

  
159
- (void)startDaemon {
160
    @synchronized(self) {
161
        if (daemonActive)
162
            return;
142 163
    }
143 164
    
144
    return self;
165
    syncOperationCount = 0;
166
    newSyncRequested = NO;
167
    syncIncomplete = NO;
168
    syncLate = NO;
169

  
170
    self.containerDirectoryPath = [directoryPath stringByAppendingPathComponent:containerName];
171
            
172
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath]) {
173
        NSData *data = [NSData dataWithContentsOfFile:self.pithosStateFilePath];
174
        NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
175
        self.storedLocalObjectStates = [unarchiver decodeObjectForKey:containerName];
176
        [unarchiver finishDecoding];
177
    } else {
178
        self.storedLocalObjectStates = [NSMutableDictionary dictionary];
179
    }
180

  
181
    // In the improbable case of leftover operations
182
    [networkQueue reset];
183
    [callbackQueue cancelAllOperations];
184
    
185
    [networkQueue go];
186
    [callbackQueue setSuspended:NO];
187
    
188
    @synchronized(self) {
189
        daemonActive = YES;
190
    }
145 191
}
146 192

  
147 193
- (void)dealloc {
148 194
    [[NSNotificationCenter defaultCenter] removeObserver:self];
149
    dispatch_release(queue);
150
    [networkQueue cancelAllOperations];
195
    [self resetDaemon];
196
    [callbackQueue release];
151 197
    [networkQueue release];
152
    [timer invalidate];
153
    [timer release];
154 198
    [tempTrashDirPath release];
155 199
    [tempDownloadsDirPath release];
156 200
    [pithosStateFilePath release];
......
162 206
    [lastCompletedSync release];
163 207
    [lastModified release];
164 208
    [blockHash release];
165
    [containerName release];
166 209
    [pithos release];
210
    [containerName release];
211
    [pithosAccount release];
167 212
    [directoryPath release];
168 213
    [super dealloc];
169 214
}
......
182 227
    if (!pithosStateFilePath)
183 228
        pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
184 229
                                 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
185
                                stringByAppendingPathComponent:@"PithosLocalObjectStates.archive"] retain];
230
                                stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive", 
231
                                                                pithosAccount.uniqueName]] retain];
186 232
    return [[pithosStateFilePath copy] autorelease];
187 233
}
188 234

  
189 235
- (NSString *)tempDownloadsDirPath {
190
    NSFileManager *fileManager = [NSFileManager defaultManager];
191
    if (!tempDownloadsDirPath || ![fileManager fileExistsAtPath:tempDownloadsDirPath]) {
192
        // Get the path from user defaults
193
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
194
        tempDownloadsDirPath = [userDefaults stringForKey:@"PithosSyncTempDownloadsDirPath"];
195
        if (tempDownloadsDirPath) {
196
            // Check if the path exists
197
            BOOL isDirectory;
198
            BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
199
            NSError *error = nil;
200
            if (fileExists && !isDirectory)
201
                [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
202
            if (!error & !fileExists)
203
                [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
204
            if (error)
205
                tempDownloadsDirPath = nil;
206
        }
207
        if (!tempDownloadsDirPath) {
208
            NSString *tempDirTemplate = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
209
                                          stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
210
                                         stringByAppendingPathComponent:@"Temp Downloads XXXXXX"];
211
            const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
212
            char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
213
            strcpy(tempDirNameCString, tempDirTemplateCString);
214
            tempDirNameCString = mkdtemp(tempDirNameCString);
215
            if (tempDirNameCString != NULL)
216
                tempDownloadsDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
217
            free(tempDirNameCString);
218
        }
219
        if (tempDownloadsDirPath)
220
            [userDefaults setObject:tempDownloadsDirPath forKey:@"PithosSyncTempDownloadsDirPath"];
221
        [tempDownloadsDirPath retain];
236
    if (!tempDownloadsDirPath) {
237
        tempDownloadsDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
238
                                  stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
239
                                 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads", 
240
                                                                 pithosAccount.uniqueName]] retain];
241
        NSFileManager *fileManager = [NSFileManager defaultManager];
242
        BOOL isDirectory;
243
        BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
244
        NSError *error = nil;
245
        if (fileExists && !isDirectory)
246
            [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
247
        if (!error & !fileExists)
248
            [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
249
        //if (error)
250
        //    tempDownloadsDirPath = nil;
251
        // XXX create a dir using mktmps?
222 252
    }
223
    return [[tempDownloadsDirPath copy] autorelease];
253
    return tempDownloadsDirPath;    
224 254
}
225 255

  
226 256
- (NSString *)tempTrashDirPath {
227
    NSFileManager *fileManager = [NSFileManager defaultManager];
228
    if (!tempTrashDirPath || ![fileManager fileExistsAtPath:tempTrashDirPath]) {
229
        // Get the path from user defaults
230
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
231
        tempTrashDirPath = [userDefaults stringForKey:@"PithosSyncTempTrashDirPath"];
232
        if (tempTrashDirPath) {
233
            // Check if the path exists
234
            BOOL isDirectory;
235
            BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
236
            NSError *error = nil;
237
            if (fileExists && !isDirectory)
238
                [fileManager removeItemAtPath:tempTrashDirPath error:&error];
239
            if (!error & !fileExists)
240
                [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
241
            if (error)
242
                tempTrashDirPath = nil;
243
        }
244
        if (!tempTrashDirPath) {
245
            NSString *tempDirTemplate = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
246
                                          stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
247
                                         stringByAppendingPathComponent:@"Temp Trash XXXXXX"];
248
            const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
249
            char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
250
            strcpy(tempDirNameCString, tempDirTemplateCString);
251
            tempDirNameCString = mkdtemp(tempDirNameCString);
252
            if (tempDirNameCString != NULL)
253
                tempTrashDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
254
            free(tempDirNameCString);
255
        }
256
        if (tempTrashDirPath)
257
            [userDefaults setObject:tempTrashDirPath forKey:@"PithosSyncTempTrashDirPath"];
258
        [tempTrashDirPath retain];
257
    if (!tempTrashDirPath) {
258
        tempTrashDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
259
                                  stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
260
                                 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash", 
261
                                                                 pithosAccount.uniqueName]] retain];
262
        NSFileManager *fileManager = [NSFileManager defaultManager];
263
        BOOL isDirectory;
264
        BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
265
        NSError *error = nil;
266
        if (fileExists && !isDirectory)
267
            [fileManager removeItemAtPath:tempTrashDirPath error:&error];
268
        if (!error & !fileExists)
269
            [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
270
        //if (error)
271
        //    tempTrashDirPath = nil;
272
        // XXX create a dir using mktmps?
259 273
    }
260
    return [[tempTrashDirPath copy] autorelease];
274
    return tempTrashDirPath;
275
}
276

  
277
- (void)setDirectoryPath:(NSString *)aDirectoryPath {
278
    if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
279
        [self resetDaemon];
280
        [self resetLocalState];
281
        [directoryPath release];
282
        directoryPath = [aDirectoryPath copy];
283
    }
284
}
285

  
286
- (void)setContainerName:(NSString *)aContainerName {
287
    if (aContainerName && ![aContainerName isEqualToString:containerName]) {
288
        [self resetDaemon];
289
        [self resetLocalState];
290
        [containerName release];
291
        containerName = [aContainerName copy];
292
    }    
293
}
294

  
295
- (void)setPithos:(ASIPithos *)aPithos {
296
    if (!pithos) {
297
        pithos = [[ASIPithos pithos] retain];
298
        pithos.authUser = [[aPithos.authUser copy] autorelease];
299
        pithos.authToken = [[aPithos.authToken copy] autorelease];
300
        pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
301
        pithos.authURL = [[aPithos.authURL copy] autorelease];
302
        pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
303
    }
304
    if (aPithos && 
305
        (![aPithos.authUser isEqualToString:pithos.authUser] || 
306
         ![aPithos.authToken isEqualToString:pithos.authToken] || 
307
         ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
308
        [self resetDaemon];
309
        if (![aPithos.authUser isEqualToString:pithos.authUser] || 
310
            ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
311
            [self resetLocalState];
312
        pithos.authUser = [[aPithos.authUser copy] autorelease];
313
        pithos.authToken = [[aPithos.authToken copy] autorelease];
314
        pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
315
        pithos.authURL = [[aPithos.authURL copy] autorelease];
316
        pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
317
    }        
261 318
}
262 319

  
263 320
#pragma mark -
......
273 330
    [pool drain];
274 331
}
275 332

  
276
- (void)increaseSyncOperationCount {
333
- (void)syncOperationStarted {
277 334
    @synchronized(self) {
278 335
        syncOperationCount++;
279 336
    }
280 337
}
281 338

  
282
- (void)decreaseSyncOperationCount {
339
- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
283 340
    @synchronized(self) {
341
        if (!operationSuccessfull)
342
            syncIncomplete = YES;
343
        if (syncOperationCount == 0)
344
            // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
345
            return;
284 346
        syncOperationCount--;
285
        if (!syncOperationCount) {
347
        if (syncOperationCount == 0) {
286 348
            if (!syncIncomplete) {
287 349
                self.lastCompletedSync = [NSDate date];
288 350
                dispatch_async(dispatch_get_main_queue(), ^{
289 351
                    [activityFacility startAndEndActivityWithType:PithosActivityOther 
290
                                                          message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]];
291

  
352
                                                          message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync] 
353
                                                    pithosAccount:pithosAccount];
354
                    
292 355
                });
293 356
            }
294 357
            [self emptyTempTrash];
358
            if (newSyncRequested && daemonActive)
359
                [self sync];
295 360
        }
296 361
    }
297 362
}
298 363

  
364
- (BOOL)isSyncing {
365
    @synchronized(self) {
366
        return ((syncOperationCount > 0) && daemonActive);
367
    }
368
}
369

  
370
- (void)syncLate {
371
    @synchronized(self) {
372
        if ([self isSyncing])
373
            syncLate = YES;
374
    }
375
}
376

  
299 377
- (void)sync {
300 378
    @synchronized(self) {
301
        if (syncOperationCount) {
379
        if ([self isSyncing]) {
302 380
            // If at least one operation is running return
303 381
            newSyncRequested = YES;
304 382
            return;
305
        } else {
383
        } else if (daemonActive) {
306 384
            // The first operation is the server listing
307
            syncOperationCount = 1;
385
            [self syncOperationStarted];
308 386
            newSyncRequested = NO;
309 387
            syncIncomplete = NO;
388
            syncLate = NO;
389
        } else {
390
            return;
310 391
        }
311 392
    }
312 393

  
......
319 400
            [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
320 401
                                                    message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", containerDirectoryPath] 
321 402
                                                      error:error];
322
            @synchronized(self) {
323
                syncOperationCount = 0;
324
            }
403
            [self syncOperationFinishedWithSuccess:NO];
325 404
            return;
326 405
        }
327 406
    } else if (!isDirectory) {
328 407
        [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
329 408
                                                message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", containerDirectoryPath] 
330 409
                                                  error:nil];
331
        @synchronized(self) {
332
            syncOperationCount = 0;
333
        }
410
        [self syncOperationFinishedWithSuccess:NO];
334 411
        return;
335 412
    }
336 413
    
......
349 426
    containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
350 427
    containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
351 428
    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther 
352
                                                               message:@"Sync: Getting server listing"];
429
                                                               message:@"Sync: Getting server listing" 
430
                                                         pithosAccount:pithosAccount];
353 431
    containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
354 432
                                 activity, @"activity", 
355 433
                                 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage", 
......
385 463
//            for (NSString *subPath in subPaths) {
386 464
//                [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
387 465
//            }
388
//            syncOperationCount = 1;
466
//            [self syncOperationStarted];
389 467
//            [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
390 468
//                if (error) {
391 469
//                    dispatch_async(dispatch_get_main_queue(), ^{
......
394 472
//                                                                  error:error];
395 473
//                    });
396 474
//                }
397
//                syncOperationCount = 0;
475
//                [self syncOperationFinishedWithSuccess:YES];
398 476
//            }];
399 477
            for (NSString *subPath in subPaths) {
400 478
                NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
......
592 670
        if (!fileExists || [self moveToTempTrashFile:filePath]) {
593 671
            dispatch_async(dispatch_get_main_queue(), ^{
594 672
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
595
                                                      message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]];
673
                                                      message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name] 
674
                                                pithosAccount:pithosAccount];
596 675
            });
597 676
            [storedLocalObjectStates removeObjectForKey:object.name];
598 677
            [self saveLocalState];
......
629 708
        if (directoryCreated)
630 709
            dispatch_async(dispatch_get_main_queue(), ^{
631 710
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
632
                                                      message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name]];
711
                                                      message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name] 
712
                                                pithosAccount:pithosAccount];
633 713
            });
634 714
    } else if (object.bytes == 0) {
635 715
        // Create local object with zero length
......
673 753
        if (fileCreated)
674 754
            dispatch_async(dispatch_get_main_queue(), ^{
675 755
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
676
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
756
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name] 
757
                                                pithosAccount:pithosAccount];
677 758
            });
678 759
    } else if (storedState.tmpFilePath == nil) {
679 760
        // Create new local object
......
699 780
            [self saveLocalState];
700 781
            dispatch_async(dispatch_get_main_queue(), ^{
701 782
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
702
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
783
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name] 
784
                                                pithosAccount:pithosAccount];
703 785
            });
704 786
        } else {
705
            [self increaseSyncOperationCount];
787
            [self syncOperationStarted];
706 788
            __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
707 789
                                                                                                containerName:containerName 
708 790
                                                                                                       object:object 
......
714 796
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
715 797
                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
716 798
                                                                    totalBytes:object.bytes 
717
                                                                  currentBytes:0];
799
                                                                  currentBytes:0 
800
                                                                 pithosAccount:pithosAccount];
718 801
            dispatch_async(dispatch_get_main_queue(), ^{
719
                [activityFacility updateActivity:activity withMessage:activity.message];  
802
                [activityFacility updateActivity:activity withMessage:activity.message];
720 803
            });
721 804
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
722 805
                                      object, @"pithosObject", 
......
768 851
            [self saveLocalState];
769 852
            dispatch_async(dispatch_get_main_queue(), ^{
770 853
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
771
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
854
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name] 
855
                                                pithosAccount:pithosAccount];
772 856
            });
773 857
        } else {
774
            [self increaseSyncOperationCount];
858
            [self syncOperationStarted];
775 859
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos 
776 860
                                                                                             containerName:containerName 
777 861
                                                                                                objectName:object.name];
......
781 865
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
782 866
                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
783 867
                                                                    totalBytes:object.bytes 
784
                                                                  currentBytes:0];
868
                                                                  currentBytes:0 
869
                                                                 pithosAccount:pithosAccount];
785 870
            dispatch_async(dispatch_get_main_queue(), ^{
786 871
                [activityFacility updateActivity:activity withMessage:activity.message];
787 872
            });
......
807 892
                                  object:(ASIPithosObject *)object 
808 893
                           localFilePath:(NSString *)filePath {
809 894
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
810
    [self increaseSyncOperationCount];
895
    [self syncOperationStarted];
811 896
    NSFileManager *fileManager = [NSFileManager defaultManager];
812 897
    BOOL isDirectory;
813 898
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
......
815 900
        // Create remote directory object
816 901
        if (!fileExists || !isDirectory) {
817 902
            // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
818
            syncIncomplete = YES;
819
            [self decreaseSyncOperationCount];
903
            [self syncOperationFinishedWithSuccess:NO];
820 904
            [pool drain];
821 905
            return;
822 906
        }
......
836 920
        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
837 921
        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
838 922
        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
839
                                                                   message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]];
923
                                                                   message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name] 
924
                                                             pithosAccount:pithosAccount];
840 925
        dispatch_async(dispatch_get_main_queue(), ^{
841 926
            [activityFacility updateActivity:activity withMessage:activity.message];
842 927
        });
......
875 960
                objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
876 961
                objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
877 962
                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
878
                                                                           message:[NSString stringWithFormat:@"Sync: Moving to trash '%@'", object.name]];
963
                                                                           message:[NSString stringWithFormat:@"Sync: Moving to trash '%@'", object.name] 
964
                                                                     pithosAccount:pithosAccount];
879 965
                dispatch_async(dispatch_get_main_queue(), ^{
880 966
                    [activityFacility updateActivity:activity withMessage:activity.message];
881 967
                });
......
892 978
                                          nil];
893 979
                [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
894 980
            } else {
895
                syncIncomplete = YES;
896
                [self decreaseSyncOperationCount];
981
                [self syncOperationFinishedWithSuccess:NO];
897 982
            }
898 983
        } else {
899
            syncIncomplete = YES;
900
            [self decreaseSyncOperationCount];
984
            [self syncOperationFinishedWithSuccess:NO];
901 985
        }
902 986
    } else {
903 987
        // Upload file to remote object
904 988
        if (!fileExists || isDirectory) {
905 989
            // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
906
            syncIncomplete = YES;
907
            [self decreaseSyncOperationCount];
990
            [self syncOperationFinishedWithSuccess:NO];
908 991
            [pool drain];
909 992
            return;
910 993
        }
......
932 1015
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
933 1016
                                                                       message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
934 1017
                                                                    totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
935
                                                                  currentBytes:0];
1018
                                                                  currentBytes:0 
1019
                                                                 pithosAccount:pithosAccount];
936 1020
            dispatch_async(dispatch_get_main_queue(), ^{
937 1021
                [activityFacility updateActivity:activity withMessage:activity.message];
938 1022
            });
......
953 1037
              nil]];
954 1038
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
955 1039
        } else {
956
            syncIncomplete = YES;
957
            [self decreaseSyncOperationCount];
1040
            [self syncOperationFinishedWithSuccess:NO];
958 1041
        }
959 1042
    }
960 1043
    [pool drain];
......
964 1047
#pragma mark ASIHTTPRequestDelegate
965 1048

  
966 1049
- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
967
    dispatch_async(queue, ^{
968
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) withObject:request];
969
    });
1050
    // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1051
    NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1052
                                                                             selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) 
1053
                                                                               object:request] autorelease];
1054
    operation.completionBlock = ^{
1055
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1056
        if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1057
            dispatch_async(dispatch_get_main_queue(), ^{
1058
                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1059
                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1060
            });
1061
            [self syncOperationFinishedWithSuccess:NO];
1062
        }
1063
        [pool drain];
1064
    };
1065
    [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1066
    [callbackQueue addOperation:operation];
970 1067
}
971 1068

  
972 1069
- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
973
    dispatch_async(queue, ^{
974
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) withObject:request];
975
    });
1070
    if (request.isCancelled) {
1071
        // Request has been cancelled 
1072
        // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1073
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1074
                   withObject:request];
1075
    } else {
1076
        // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1077
        NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1078
                                                                                 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1079
                                                                                   object:request] autorelease];
1080
        operation.completionBlock = ^{
1081
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1082
            if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1083
                dispatch_async(dispatch_get_main_queue(), ^{
1084
                    [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1085
                                      withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1086
                });
1087
                [self syncOperationFinishedWithSuccess:NO];
1088
            }
1089
            [pool drain];
1090
        };
1091
        [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1092
        [callbackQueue addOperation:operation];
1093
    }
976 1094
}
977 1095

  
978 1096
- (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
979 1097
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1098
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
980 1099
    NSLog(@"Sync::list request finished: %@", containerRequest.url);
981
    if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
1100
    if (operation.isCancelled) {
1101
        [self listRequestFailed:containerRequest];
1102
    } else if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
982 1103
        if (containerRequest.responseStatusCode == 200) {
983 1104
            NSArray *someObjects = [containerRequest objects];
984 1105
            if (objects == nil) {
......
1012 1133
                newContainerRequest.delegate = self;
1013 1134
                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1014 1135
                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1015
                newContainerRequest.userInfo = newContainerRequest.userInfo;
1136
                newContainerRequest.userInfo = containerRequest.userInfo;
1016 1137
                [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1017 1138
                [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1018 1139
                [pool drain];
1019 1140
                return;
1020 1141
            }
1021 1142
        }
1143
        
1144
        if (operation.isCancelled) {
1145
            [self listRequestFailed:containerRequest];
1146
            [pool drain];
1147
            return;
1148
        }
1149

  
1022 1150
        dispatch_async(dispatch_get_main_queue(), ^{
1023 1151
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1024 1152
                              withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
......
1031 1159
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
1032 1160
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
1033 1161
                                                          error:error];
1034
                [activityFacility startAndEndActivityWithType:PithosActivityOther message:@"Sync: Failed to read contents of sync directory"];
1162
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
1163
                                                      message:@"Sync: Failed to read contents of sync directory" 
1164
                                                pithosAccount:pithosAccount];
1035 1165
            });
1036
            @synchronized(self) {
1037
                // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccessfully
1038
                syncOperationCount = 0;
1039
                if (newSyncRequested)
1040
                    [self sync];
1041
            }
1166
            // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccessfully
1167
            [self syncOperationFinishedWithSuccess:NO];
1168
            [pool drain];
1169
            return;
1170
        }
1171
        
1172
        if (operation.isCancelled) {
1173
            operation.completionBlock = nil;
1174
            [self syncOperationFinishedWithSuccess:NO];
1042 1175
            [pool drain];
1043 1176
            return;
1044 1177
        }
1178

  
1045 1179
        self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:[subPaths count]];
1046 1180
        for (NSString *objectName in subPaths) {
1181
            if (operation.isCancelled) {
1182
                operation.completionBlock = nil;
1183
                [self saveLocalState];
1184
                [self syncOperationFinishedWithSuccess:NO];
1185
                [pool drain];
1186
                return;
1187
            }
1188

  
1047 1189
            PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:objectName];
1048 1190
            NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1049 1191
            if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
......
1062 1204
        }
1063 1205
        [self saveLocalState];
1064 1206

  
1207
        if (operation.isCancelled) {
1208
            operation.completionBlock = nil;
1209
            [self syncOperationFinishedWithSuccess:NO];
1210
            [pool drain];
1211
            return;
1212
        }
1213

  
1065 1214
        for (NSString *objectName in remoteObjects) {
1215
            if (operation.isCancelled) {
1216
                operation.completionBlock = nil;
1217
                [self saveLocalState];
1218
                [self syncOperationFinishedWithSuccess:NO];
1219
                [pool drain];
1220
                return;
1221
            }
1222

  
1066 1223
            if (![storedLocalObjectStates objectForKey:objectName])
1067 1224
                [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1068 1225
        }
1226
        [self saveLocalState];
1227

  
1228
        if (operation.isCancelled) {
1229
            operation.completionBlock = nil;
1230
            [self syncOperationFinishedWithSuccess:NO];
1231
            [pool drain];
1232
            return;
1233
        }
1069 1234

  
1070 1235
        for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1236
            if (operation.isCancelled) {
1237
                operation.completionBlock = nil;
1238
                [self syncOperationFinishedWithSuccess:NO];
1239
                [pool drain];
1240
                return;
1241
            }
1242

  
1071 1243
            NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1072 1244
            if ([objectName hasSuffix:@"/"])
1073 1245
                filePath = [filePath stringByAppendingString:@":"];
......
1179 1351
                }
1180 1352
            }
1181 1353
        }
1182
        @synchronized(self) {
1183
            [self decreaseSyncOperationCount];
1184
            if (newSyncRequested && !syncOperationCount)
1185
                [self sync];
1186
        }
1354
        [self syncOperationFinishedWithSuccess:YES];
1187 1355
    } else {
1188
        NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1189
        if (retries > 0) {
1190
            ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1191
            [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1192
            [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1193
        } else {
1194
            dispatch_async(dispatch_get_main_queue(), ^{
1195
                [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1196
                                  withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1197
            });
1198
            @synchronized(self) {
1199
                // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1200
                syncOperationCount = 0;
1201
                if (newSyncRequested)
1202
                    [self sync];
1203
            }
1204
        }
1356
        [self listRequestFailed:containerRequest];
1205 1357
    }
1206 1358
    [pool drain];
1207 1359
}
1208 1360

  
1209 1361
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1210 1362
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1211
    if ([containerRequest isCancelled]) {
1363
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1364
    if (operation.isCancelled) {
1365
        [objects release];
1366
        objects = nil;
1367
        [pool drain];
1368
        return;        
1369
    }
1370
    if (containerRequest.isCancelled) {
1212 1371
        dispatch_async(dispatch_get_main_queue(), ^{
1213 1372
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1214 1373
                              withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1215 1374
        });
1216 1375
        [objects release];
1217 1376
        objects = nil;
1218
        @synchronized(self) {
1219
            syncOperationCount = 0;
1220
        }
1377
        [self syncOperationFinishedWithSuccess:NO];
1221 1378
        [pool drain];
1222 1379
        return;
1223 1380
    }
......
1234 1391
        });
1235 1392
        [objects release];
1236 1393
        objects = nil;
1237
        @synchronized(self) {
1238
            // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1239
            syncOperationCount = 0;
1240
            if (newSyncRequested)
1241
                [self sync];
1242
        }
1394
        // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1395
        [self syncOperationFinishedWithSuccess:NO];
1243 1396
    }
1244 1397
    [pool drain];
1245 1398
}
1246 1399

  
1247 1400
- (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1248 1401
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1402
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1249 1403
    NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1250
    if (objectRequest.responseStatusCode == 206) {
1404
    if (operation.isCancelled) {
1405
        [self requestFailed:objectRequest];
1406
    } else if (objectRequest.responseStatusCode == 206) {
1251 1407
        ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1252 1408
        NSFileManager *fileManager = [NSFileManager defaultManager];
1253 1409
        NSError *error;
......
1259 1415
                [activityFacility endActivity:activity 
1260 1416
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1261 1417
            });
1262
            @synchronized(self) {
1263
                syncIncomplete = YES;
1264
                [self decreaseSyncOperationCount];
1265
                if (newSyncRequested && !syncOperationCount)
1266
                    [self sync];
1267
            }
1418
            [self syncOperationFinishedWithSuccess:NO];
1268 1419
            [pool drain];
1269 1420
            return;
1270 1421
        }
......
1286 1437
                    [activityFacility endActivity:activity 
1287 1438
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1288 1439
                });
1289
                @synchronized(self) {
1290
                    syncIncomplete = YES;
1291
                    [self decreaseSyncOperationCount];
1292
                    if (newSyncRequested && !syncOperationCount)
1293
                        [self sync];
1294
                }
1440
                [self syncOperationFinishedWithSuccess:NO];
1295 1441
                [pool drain];
1296 1442
                return;
1297 1443
            }
......
1317 1463
                    [activityFacility endActivity:activity 
1318 1464
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1319 1465
                });
1320
                @synchronized(self) {
1321
                    syncIncomplete = YES;
1322
                    [self decreaseSyncOperationCount];
1323
                    if (newSyncRequested && !syncOperationCount)
1324
                        [self sync];
1325
                }
1466
                [self syncOperationFinishedWithSuccess:NO];
1326 1467
                [pool drain];
1327 1468
                return;
1328 1469
            } else if (![fileManager fileExistsAtPath:dirPath]) {
......
1339 1480
                        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1340 1481
                                          withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1341 1482
                    });
1342
                    @synchronized(self) {
1343
                        syncIncomplete = YES;
1344
                        [self decreaseSyncOperationCount];
1345
                        if (newSyncRequested && !syncOperationCount)
1346
                            [self sync];
1347
                    }
1483
                    [self syncOperationFinishedWithSuccess:NO];
1348 1484
                    [pool drain];
1349 1485
                    return;
1350 1486
                }
......
1360 1496
                    [activityFacility endActivity:activity 
1361 1497
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1362 1498
                });
1363
                @synchronized(self) {
1364
                    syncIncomplete = YES;
1365
                    [self decreaseSyncOperationCount];
1366
                    if (newSyncRequested && !syncOperationCount)
1367
                        [self sync];
1368
                }
1499
                [self syncOperationFinishedWithSuccess:NO];
1369 1500
                [pool drain];
1370 1501
                return;
1371 1502
            }
......
1380 1511
            storedState.hash = object.objectHash;
1381 1512
            storedState.tmpFilePath = nil;
1382 1513
            [self saveLocalState];
1383
            
1384
            @synchronized(self) {
1385
                [self decreaseSyncOperationCount];
1386
                if (newSyncRequested && !syncOperationCount)
1387
                    [self sync];
1388
            }
1514
            [self syncOperationFinishedWithSuccess:YES];
1389 1515
            [pool drain];
1390 1516
            return;
1391 1517
        } else {
1392
            if (newSyncRequested) {
1393
                dispatch_async(dispatch_get_main_queue(), ^{
1394
                    [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1395
                                      withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1396
                });
1397
                @synchronized(self) {
1398
                    syncIncomplete = YES;
1399
                    [self decreaseSyncOperationCount];
1400
                    if (!syncOperationCount)
1401
                        [self sync];
1402
                }
1403
                [pool drain];
1404
                return;
1518
            if (newSyncRequested || syncLate || operation.isCancelled) {
1519
                [self requestFailed:objectRequest];
1405 1520
            } else {
1406 1521
                __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
1407 1522
                                                                                                       containerName:containerName 
......
1431 1546
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1432 1547
                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1433 1548
        });
1434
        @synchronized(self) {
1435
            syncIncomplete = YES;
1436
            [self decreaseSyncOperationCount];
1437
            if (newSyncRequested && !syncOperationCount)
1438
                [self sync];
1439
        }
1549
        [self syncOperationFinishedWithSuccess:NO];
1440 1550
    } else {
1441 1551
        [self requestFailed:objectRequest];
1442 1552
    }
......
1445 1555

  
1446 1556
- (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1447 1557
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1558
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1448 1559
    NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1449
    if (objectRequest.responseStatusCode == 200) {
1450
        if (newSyncRequested) {
1451
            dispatch_async(dispatch_get_main_queue(), ^{
1452
                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1453
                                  withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1454
            });
1455
            @synchronized(self) {
1456
                syncIncomplete = YES;
1457
                [self decreaseSyncOperationCount];
1458
                if (!syncOperationCount)
1459
                    [self sync];
1460
            }
1461
            [pool drain];
1462
            return;
1560
    if (operation.isCancelled) {
1561
        [self requestFailed:objectRequest];
1562
    } else if (objectRequest.responseStatusCode == 200) {
1563
        if (newSyncRequested || syncLate || operation.isCancelled) {
1564
            [self requestFailed:objectRequest];
1463 1565
        } else {
1464 1566
            ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1465 1567
            PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
......
1511 1613

  
1512 1614
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1513 1615
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1616
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1514 1617
    NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1515
    if (objectRequest.responseStatusCode == 201) {
1618
    if (operation.isCancelled) {
1619
        [self requestFailed:objectRequest];
1620
    } else if (objectRequest.responseStatusCode == 201) {
1516 1621
        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1517 1622
        storedState.isDirectory = YES;
1518 1623
        [self saveLocalState];
......
1520 1625
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1521 1626
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1522 1627
        });
1523
        @synchronized(self) {
1524
            [self decreaseSyncOperationCount];
1525
            if (newSyncRequested && !syncOperationCount)
1526
                [self sync];
1527
        }
1628
        [self syncOperationFinishedWithSuccess:YES];
1528 1629
    } else {
1529 1630
        [self requestFailed:objectRequest];
1530 1631
    }
......
1533 1634

  
1534 1635
- (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1535 1636
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1637
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1536 1638
    NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1537
    if (objectRequest.responseStatusCode == 201) {
1639
    if (operation.isCancelled) {
1640
        [self requestFailed:objectRequest];
1641
    } else if (objectRequest.responseStatusCode == 201) {
1538 1642
        [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1539 1643
        [self saveLocalState];
1540 1644
        dispatch_async(dispatch_get_main_queue(), ^{
1541 1645
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1542 1646
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1543 1647
        });
1544
        @synchronized(self) {
1545
            [self decreaseSyncOperationCount];
1546
            if (newSyncRequested && !syncOperationCount)
1547
                [self sync];
1548
        }
1648
        [self syncOperationFinishedWithSuccess:YES];
1549 1649
    } else {
1550 1650
        [self requestFailed:objectRequest];
1551 1651
    }
......
1554 1654

  
1555 1655
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1556 1656
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1657
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1557 1658
    NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1558 1659
    ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1559 1660
    PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1560 1661
    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1561 1662
    NSUInteger totalBytes = activity.totalBytes;
1562 1663
    NSUInteger currentBytes = activity.currentBytes;
1563
    if (objectRequest.responseStatusCode == 201) {
1664
    if (operation.isCancelled) {
1665
        [self requestFailed:objectRequest];
1666
    } else if (objectRequest.responseStatusCode == 201) {
1564 1667
        NSLog(@"Sync::object created: %@", objectRequest.url);
1565 1668
        storedState.hash = [objectRequest objectHash];
1566 1669
        [self saveLocalState];
......
1570 1673
                               totalBytes:totalBytes 
1571 1674
                             currentBytes:totalBytes];
1572 1675
        });
1573
        @synchronized(self) {
1574
            [self decreaseSyncOperationCount];
1575
            if (newSyncRequested && !syncOperationCount)
1576
                [self sync];
1577
        }
1676
        [self syncOperationFinishedWithSuccess:YES];
1578 1677
    } else if (objectRequest.responseStatusCode == 409) {
1579
        if (newSyncRequested) {
1580
            dispatch_async(dispatch_get_main_queue(), ^{
1581
                [activityFacility endActivity:activity 
1582
                                  withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1583
            });
1584
            @synchronized(self) {
1585
                syncIncomplete = YES;
1586
                [self decreaseSyncOperationCount];
1587
                if (!syncOperationCount)
1588
                    [self sync];
1589
            }
1590
            [pool drain];
1591
            return;
1678
        if (newSyncRequested || syncLate || operation.isCancelled) {
1679
            [self requestFailed:objectRequest];
1592 1680
        } else {
1593 1681
            NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1594 1682
            if (iteration == 0) {
1595 1683
                NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
1596 1684
                dispatch_async(dispatch_get_main_queue(), ^{
1597
                    [activityFacility endActivity:activity 
1598
                                      withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1685
                    [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1599 1686
                });
1600
                syncIncomplete = YES;
1601
                [self decreaseSyncOperationCount];
1687
                [self syncOperationFinishedWithSuccess:NO];
1602 1688
                [pool drain];
1603 1689
                return;
1604 1690
            }
......
1645 1731

  
1646 1732
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1647 1733
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1734
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1648 1735
    NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
1649
    if (containerRequest.responseStatusCode == 202) {
1736
    if (operation.isCancelled) {
1737
        [self requestFailed:containerRequest];
1738
    } else if (containerRequest.responseStatusCode == 202) {
1650 1739
        ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
1651 1740
        PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1652 1741
        NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1653 1742
        NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1654 1743
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1655
        if (missingBlockIndex == NSNotFound) {
1744
        if (operation.isCancelled) {
1745
            [self requestFailed:containerRequest];
1746
        } else if (missingBlockIndex == NSNotFound) {
1656 1747
            NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1657 1748
            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
1658 1749
                                                                                           containerName:containerName 
......
1674 1765
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1675 1766
            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1676 1767
        } else {
1677
            if (newSyncRequested) {
1678
                dispatch_async(dispatch_get_main_queue(), ^{
1679
                    [activityFacility endActivity:activity 
1680
                                      withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1681
                });
1682
                @synchronized(self) {
1683
                    syncIncomplete = YES;
1684
                    [self decreaseSyncOperationCount];
1685
                    if (!syncOperationCount)
1686
                        [self sync];
1687
                }
1688
                [pool drain];
1689
                return;
1768
            if (newSyncRequested || syncLate || operation.isCancelled) {
1769
                [self requestFailed:containerRequest];
1690 1770
            } else {
1691 1771
                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
1692 1772
                                                                                                                 containerName:containerName
......
1717 1797

  
1718 1798
- (void)requestFailed:(ASIPithosRequest *)request {
1719 1799
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1720
    if ([request isCancelled]) {
1721
        dispatch_async(dispatch_get_main_queue(), ^{
1722
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1723
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1724
        });
1725
        syncIncomplete = YES;
1726
        [self decreaseSyncOperationCount];
1800
    NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1801
    if (operation.isCancelled) {
1727 1802
        [pool drain];
1728
        return;
1803
        return;        
1729 1804
    }
1730
    if (newSyncRequested) {
1805
    if (request.isCancelled || newSyncRequested || syncLate) {
1731 1806
        dispatch_async(dispatch_get_main_queue(), ^{
1732 1807
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1733 1808
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1734 1809
        });
1735
        @synchronized(self) {
1736
            syncIncomplete = YES;
1737
            [self decreaseSyncOperationCount];
1738
            if (!syncOperationCount)
1739
                [self sync];
1740
        }
1810
        [self syncOperationFinishedWithSuccess:NO];
1741 1811
        [pool drain];
1742 1812
        return;
1743 1813
    }
......
1751 1821
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1752 1822
                              withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1753 1823
        });
1754
        syncIncomplete = YES;
1755
        [self decreaseSyncOperationCount];
1824
        [self syncOperationFinishedWithSuccess:NO];
1756 1825
    }
1757 1826
    [pool drain];
1758 1827
}

Also available in: Unified diff