Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosSyncDaemon.m @ ca913781

History | View | Annotate | Download (132.3 kB)

1
//
2
//  PithosSyncDaemon.m
3
//  pithos-macos
4
//
5
// Copyright 2011-2012 GRNET S.A. All rights reserved.
6
//
7
// Redistribution and use in source and binary forms, with or
8
// without modification, are permitted provided that the following
9
// conditions are met:
10
// 
11
//   1. Redistributions of source code must retain the above
12
//      copyright notice, this list of conditions and the following
13
//      disclaimer.
14
// 
15
//   2. Redistributions in binary form must reproduce the above
16
//      copyright notice, this list of conditions and the following
17
//      disclaimer in the documentation and/or other materials
18
//      provided with the distribution.
19
// 
20
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
// POSSIBILITY OF SUCH DAMAGE.
32
// 
33
// The views and conclusions contained in the software and
34
// documentation are those of the authors and should not be
35
// interpreted as representing official policies, either expressed
36
// or implied, of GRNET S.A.
37

    
38
#import "PithosSyncDaemon.h"
39
#import "PithosAccount.h"
40
#import "PithosLocalObjectState.h"
41
#import "PithosActivityFacility.h"
42
#import "PithosUtilities.h"
43
#import "ASINetworkQueue.h"
44
#import "ASIPithosRequest.h"
45
#import "ASIPithos.h"
46
#import "ASIPithosContainer.h"
47
#import "ASIPithosContainerRequest.h"
48
#import "ASIPithosObjectRequest.h"
49
#import "ASIPithosObject.h"
50

    
51
@interface PithosSyncDaemon (Private)
52
- (void)loadLocalState;
53
- (void)resetLocalStateWithAll:(BOOL)all;
54
- (void)saveLocalState;
55

    
56
- (BOOL)moveToTempTrashFile:(NSString *)filePath pithosContainer:(ASIPithosContainer *)pithosContainer;
57
- (void)emptyTempTrash;
58
- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
59

    
60
- (void)updateLocalStateWithObject:(ASIPithosObject *)object 
61
                     localFilePath:(NSString *)filePath 
62
                   pithosContainer:(ASIPithosContainer *)pithosContainer;
63
- (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
64
                                   object:(ASIPithosObject *)object 
65
                            localFilePath:(NSString *)filePath 
66
                          pithosContainer:(ASIPithosContainer *)pithosContainer;
67
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest;
68
- (void)requestFailed:(ASIPithosRequest *)request;
69

    
70
- (void)syncOperationStarted;
71
- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull;
72

    
73
@end
74

    
75
@implementation PithosSyncDaemon
76
@synthesize directoryPath, containersDictionary, pithos;
77
@synthesize pithosContainers;
78
@synthesize lastCompletedSync, remoteObjects, previousRemoteObjects, storedLocalObjectStates, currentLocalObjectStates;
79
@synthesize pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
80

    
81
#pragma mark -
82
#pragma Object Lifecycle
83

    
84
- (id)initWithDirectoryPath:(NSString *)aDirectoryPath 
85
              pithosAccount:(PithosAccount *)aPithosAccount 
86
       containersDictionary:(NSDictionary *)aContainersDictionary 
87
            resetLocalState:(BOOL)resetLocalState {
88
    if ((self = [super init])) {
89
        directoryPath = [aDirectoryPath copy];
90
        pithosAccount = [aPithosAccount retain];
91
        containersDictionary = [aContainersDictionary copy];
92
        self.pithos = pithosAccount.pithos;
93
        
94
        containersCount = [containersDictionary count];
95
        self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount];
96
        for (NSString *containerName in containersDictionary) {
97
            ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
98
            pithosContainer.name = containerName;
99
            [pithosContainers addObject:pithosContainer];
100
        }
101
        
102
        activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
103
        
104
        if (resetLocalState)
105
            [self resetLocalStateWithAll:YES];
106
        else
107
            [self resetLocalStateWithAll:NO];
108
        
109
        networkQueue = [[ASINetworkQueue alloc] init];
110
        networkQueue.showAccurateProgress = YES;
111
        networkQueue.shouldCancelAllRequestsOnFailure = NO;
112
//        networkQueue.maxConcurrentOperationCount = 1;
113
        
114
        callbackQueue = [[NSOperationQueue alloc] init];
115
        [callbackQueue setSuspended:YES];
116
        callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
117
//        callbackQueue.maxConcurrentOperationCount = 1;
118
        
119
        [[NSNotificationCenter defaultCenter] addObserver:self
120
                                                 selector:@selector(applicationWillTerminate:)
121
                                                     name:NSApplicationWillTerminateNotification
122
                                                   object:[NSApplication sharedApplication]];
123
    }
124
    return self;
125
}
126

    
127
- (void)loadLocalState {
128
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
129
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath])
130
        self.storedLocalObjectStates = [NSKeyedUnarchiver unarchiveObjectWithFile:self.pithosStateFilePath];
131
    else
132
        self.storedLocalObjectStates = [NSMutableDictionary dictionary];
133
    if (!storedLocalObjectStates)
134
        self.storedLocalObjectStates = [NSMutableDictionary dictionary];
135
    for (ASIPithosContainer *pithosContainer in pithosContainers) {
136
        if (![storedLocalObjectStates objectForKey:pithosContainer.name]) {
137
            [storedLocalObjectStates setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
138
        }
139
    }
140
    [pool drain];
141
}
142

    
143
- (void)resetLocalStateWithAll:(BOOL)all {
144
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
145
    self.lastCompletedSync = nil;
146
    NSFileManager *fileManager = [NSFileManager defaultManager];
147
    NSError *error;
148
    if (all) {
149
        self.storedLocalObjectStates = [NSMutableDictionary dictionary];
150
        [self saveLocalState]; // Save an empty dictionary
151
        [self loadLocalState]; // Load to populate with containers
152
        [self saveLocalState]; // Save again
153
        if (self.tempDownloadsDirPath) {
154
            error = nil;
155
            for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
156
                if (error) {
157
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
158
                                                            message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath] 
159
                                                              error:error];
160
                    break;
161
                }
162
                NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
163
                if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
164
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
165
                                                            message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
166
                                                              error:error];
167
                }
168
                error = nil;
169
            }
170
        }
171
    } else {
172
        // Remove containers that don't interest us anymore and save
173
        if (!storedLocalObjectStates)
174
            [self loadLocalState];
175
        for (NSString *containerName in storedLocalObjectStates) {
176
            if (![containersDictionary objectForKey:containerName]) {
177
                [storedLocalObjectStates removeObjectForKey:containerName];
178
                if (self.tempDownloadsDirPath) {
179
                    NSString *containerTempDownloadsDirPath = [self.tempDownloadsDirPath stringByAppendingPathComponent:containerName];
180
                    BOOL isDirectory;
181
                    BOOL fileExists = [fileManager fileExistsAtPath:containerTempDownloadsDirPath isDirectory:&isDirectory];
182
                    if (fileExists && isDirectory) {
183
                        error = nil;
184
                        for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:containerTempDownloadsDirPath error:&error]) {
185
                            if (error) {
186
                                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
187
                                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", 
188
                                                                                 containerTempDownloadsDirPath] 
189
                                                                          error:error];
190
                                break;
191
                            }
192
                            NSString *subFilePath = [containerTempDownloadsDirPath stringByAppendingPathComponent:subPath];
193
                            if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
194
                                [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
195
                                                                        message:[NSString stringWithFormat:@"Cannot remove file at '%@'", 
196
                                                                                 subFilePath] 
197
                                                                          error:error];
198
                            }
199
                            error = nil;
200
                        }
201
                    } else if (fileExists && !isDirectory) {
202
                        error = nil;
203
                        if (![fileManager removeItemAtPath:containerTempDownloadsDirPath error:&error] || error) {
204
                            [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
205
                                                                    message:[NSString stringWithFormat:@"Cannot remove file at '%@'", 
206
                                                                             containerTempDownloadsDirPath] 
207
                                                                      error:error];
208
                        }
209
                    }
210
                }
211
            }
212
        }
213
        [self saveLocalState];
214
    }
215
    [pool drain];
216
}
217

    
218
- (void)saveLocalState {
219
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
220
    [NSKeyedArchiver archiveRootObject:storedLocalObjectStates toFile:self.pithosStateFilePath];
221
    [pool drain];
222
}
223

    
224
- (void)resetDaemon {
225
    @synchronized(self) {
226
        if (!daemonActive)
227
            return;
228
    }
229
    
230
    [networkQueue reset];
231
    [callbackQueue cancelAllOperations];
232
    [callbackQueue setSuspended:YES];
233
    [self emptyTempTrash];
234
    
235
    syncOperationCount = 0;
236
    
237
    @synchronized(self) {            
238
        daemonActive = NO;
239
    }
240
}
241

    
242
- (void)startDaemon {
243
    @synchronized(self) {
244
        if (daemonActive)
245
            return;
246
    }
247

    
248
    // In the improbable case of leftover operations
249
    [networkQueue reset];
250
    [callbackQueue cancelAllOperations];
251
    
252
    syncOperationCount = 0;
253
    newSyncRequested = NO;
254
    syncIncomplete = NO;
255
    syncLate = NO;
256
    
257
    [self loadLocalState];
258
    
259
    [networkQueue go];
260
    [callbackQueue setSuspended:NO];
261
    
262
    @synchronized(self) {
263
        daemonActive = YES;
264
    }
265
}
266

    
267
- (void)dealloc {
268
    [[NSNotificationCenter defaultCenter] removeObserver:self];
269
    [self resetDaemon];
270
    [callbackQueue release];
271
    [networkQueue release];
272
    [tempTrashDirPath release];
273
    [tempDownloadsDirPath release];
274
    [pithosStateFilePath release];
275
    [currentLocalObjectStates release];
276
    [storedLocalObjectStates release];
277
    [previousRemoteObjects release];
278
    [remoteObjects release];
279
    [objects release];
280
    [lastCompletedSync release];
281
    [pithosContainers release];
282
    [pithos release];
283
    [containersDictionary release];
284
    [pithosAccount release];
285
    [directoryPath release];
286
    [super dealloc];
287
}
288

    
289
#pragma mark -
290
#pragma mark Observers
291

    
292
- (void)applicationWillTerminate:(NSNotification *)notification {
293
    [self saveLocalState];
294
}
295

    
296
#pragma mark -
297
#pragma mark Properties
298

    
299
- (NSString *)pithosStateFilePath {
300
    if (!pithosStateFilePath) {
301
        pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
302
                                 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
303
                                stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive", 
304
                                                                pithosAccount.uniqueName]] retain];
305
        NSString *pithosStateFileDirPath = [pithosStateFilePath stringByDeletingLastPathComponent];
306
        NSFileManager *fileManager = [NSFileManager defaultManager];
307
        BOOL isDirectory;
308
        BOOL fileExists = [fileManager fileExistsAtPath:pithosStateFileDirPath isDirectory:&isDirectory];
309
        NSError *error = nil;
310
        if (fileExists && !isDirectory)
311
            [fileManager removeItemAtPath:pithosStateFileDirPath error:&error];
312
        if (!error && !fileExists)
313
            [fileManager createDirectoryAtPath:pithosStateFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
314
        //if (error)
315
        //  pithosStateFilePath = nil;
316
        // XXX create a dir using mktmps?
317
    }
318
    return [[pithosStateFilePath copy] autorelease];
319
}
320

    
321
- (NSString *)tempDownloadsDirPath {
322
    if (!tempDownloadsDirPath) {
323
        tempDownloadsDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
324
                                  stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
325
                                 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads", 
326
                                                                 pithosAccount.uniqueName]] retain];
327
        NSFileManager *fileManager = [NSFileManager defaultManager];
328
        BOOL isDirectory;
329
        BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
330
        NSError *error = nil;
331
        if (fileExists && !isDirectory)
332
            [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
333
        if (!error && !fileExists)
334
            [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
335
        //if (error)
336
        //    tempDownloadsDirPath = nil;
337
        // XXX create a dir using mktmps?
338
    }
339
    return tempDownloadsDirPath;    
340
}
341

    
342
- (NSString *)tempTrashDirPath {
343
    if (!tempTrashDirPath) {
344
        tempTrashDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
345
                                  stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
346
                                 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash", 
347
                                                                 pithosAccount.uniqueName]] retain];
348
        NSFileManager *fileManager = [NSFileManager defaultManager];
349
        BOOL isDirectory;
350
        BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
351
        NSError *error = nil;
352
        if (fileExists && !isDirectory)
353
            [fileManager removeItemAtPath:tempTrashDirPath error:&error];
354
        if (!error && !fileExists)
355
            [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
356
        //if (error)
357
        //    tempTrashDirPath = nil;
358
        // XXX create a dir using mktmps?
359
    }
360
    return tempTrashDirPath;
361
}
362

    
363
- (void)setDirectoryPath:(NSString *)aDirectoryPath {
364
    if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
365
        [self resetDaemon];
366
        [self resetLocalStateWithAll:YES];
367
        [directoryPath release];
368
        directoryPath = [aDirectoryPath copy];
369
    }
370
}
371

    
372
- (void)setContainersDictionary:(NSDictionary *)aContainersDictionary {
373
    if (aContainersDictionary && ![aContainersDictionary isEqualToDictionary:containersDictionary]) {
374
        [self resetDaemon];
375
        [containersDictionary release];
376
        containersDictionary = [aContainersDictionary copy];
377
        containersCount = [containersDictionary count];
378
        self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount];
379
        for (NSString *containerName in aContainersDictionary) {
380
            ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
381
            pithosContainer.name = containerName;
382
            [pithosContainers addObject:pithosContainer];
383
        }
384
        [self resetLocalStateWithAll:NO];
385
    }
386
}
387

    
388
- (void)setPithos:(ASIPithos *)aPithos {
389
    if (!pithos) {
390
        pithos = [[ASIPithos pithos] retain];
391
        pithos.authUser = [[aPithos.authUser copy] autorelease];
392
        pithos.authToken = [[aPithos.authToken copy] autorelease];
393
        pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
394
        pithos.authURL = [[aPithos.authURL copy] autorelease];
395
        pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
396
    }
397
    if (aPithos && 
398
        (![aPithos.authUser isEqualToString:pithos.authUser] || 
399
         ![aPithos.authToken isEqualToString:pithos.authToken] || 
400
         ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
401
        [self resetDaemon];
402
        if (![aPithos.authUser isEqualToString:pithos.authUser] || 
403
            ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
404
            [self resetLocalStateWithAll:YES];
405
        pithos.authUser = [[aPithos.authUser copy] autorelease];
406
        pithos.authToken = [[aPithos.authToken copy] autorelease];
407
        pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
408
        pithos.authURL = [[aPithos.authURL copy] autorelease];
409
        pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
410
    }        
411
}
412

    
413
#pragma mark -
414
#pragma mark Sync
415

    
416
- (void)syncOperationStarted {
417
    @synchronized(self) {
418
        syncOperationCount++;
419
    }
420
}
421

    
422
- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
423
    @synchronized(self) {
424
        if (!operationSuccessfull)
425
            syncIncomplete = YES;
426
        if (syncOperationCount == 0) {
427
            // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
428
            NSLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
429
            return;
430
        }
431
        syncOperationCount--;
432
        if (syncOperationCount == 0) {
433
            if (!syncIncomplete) {
434
                self.lastCompletedSync = [NSDate date];
435
                dispatch_async(dispatch_get_main_queue(), ^{
436
                    [activityFacility startAndEndActivityWithType:PithosActivityOther 
437
                                                          message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync] 
438
                                                    pithosAccount:pithosAccount];
439
                    
440
                });
441
            }
442
            [self emptyTempTrash];
443
            if (newSyncRequested && daemonActive)
444
                [self sync];
445
        }
446
    }
447
}
448

    
449
- (BOOL)isSyncing {
450
    @synchronized(self) {
451
        return ((syncOperationCount > 0) && daemonActive);
452
    }
453
}
454

    
455
- (void)syncLate {
456
    @synchronized(self) {
457
        if ([self isSyncing])
458
            syncLate = YES;
459
    }
460
}
461

    
462
- (void)sync {
463
    @synchronized(self) {
464
        if ([self isSyncing]) {
465
            // If at least one operation is running return
466
            newSyncRequested = YES;
467
            return;
468
        } else if (daemonActive && containersCount) {
469
            // The first operation is the server listing
470
            [self syncOperationStarted];
471
            newSyncRequested = NO;
472
            syncIncomplete = NO;
473
            syncLate = NO;
474
        } else {
475
            return;
476
        }
477
    }
478

    
479
    NSFileManager *fileManager = [NSFileManager defaultManager];
480
    BOOL isDirectory;
481
    NSError *error = nil;
482
    if (![fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory]) {
483
        if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error] || 
484
            error) {
485
            [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
486
                                                    message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", directoryPath] 
487
                                                      error:error];
488
            [self syncOperationFinishedWithSuccess:NO];
489
            return;
490
        }
491
    } else if (!isDirectory) {
492
        [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
493
                                                message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", directoryPath] 
494
                                                  error:nil];
495
        [self syncOperationFinishedWithSuccess:NO];
496
        return;
497
    }
498
    for (ASIPithosContainer *pithosContainer in pithosContainers) {
499
        NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
500
        error = nil;
501
        if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
502
            if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] || 
503
                error) {
504
                [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
505
                                                        message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", 
506
                                                                 containerDirectoryPath] 
507
                                                          error:error];
508
                [self syncOperationFinishedWithSuccess:NO];
509
                return;
510
            }
511
        } else if (!isDirectory) {
512
            [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
513
                                                    message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", 
514
                                                             containerDirectoryPath] 
515
                                                      error:nil];
516
            [self syncOperationFinishedWithSuccess:NO];
517
            return;
518
        }
519
    }
520
    containersIndex = 0;
521
    self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:containersCount];
522
    ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
523
                                                                                            containerName:[[pithosContainers objectAtIndex:containersIndex] name] 
524
                                                                                                    limit:0 
525
                                                                                                   marker:nil 
526
                                                                                                   prefix:nil 
527
                                                                                                delimiter:nil 
528
                                                                                                     path:nil 
529
                                                                                                     meta:nil 
530
                                                                                                   shared:NO 
531
                                                                                                    until:nil 
532
                                                                                          ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
533
    containerRequest.delegate = self;
534
    containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
535
    containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
536
    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther 
537
                                                               message:@"Sync: Getting server listing" 
538
                                                         pithosAccount:pithosAccount];
539
    containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
540
                                 activity, @"activity", 
541
                                 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage", 
542
                                 @"Sync: Getting server listing (failed)", @"failedActivityMessage", 
543
                                 @"Sync: Getting server listing (finished)", @"finishedActivityMessage", 
544
                                 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
545
                                 [NSNumber numberWithUnsignedInteger:10], @"retries", 
546
                                 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector", 
547
                                 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector", 
548
                                 nil];
549
    [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
550
}
551

    
552
- (void)emptyTempTrash {
553
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
554
    NSString *trashDirPath = self.tempTrashDirPath;
555
    if (trashDirPath) {
556
        NSFileManager *fileManager = [NSFileManager defaultManager];
557
        NSError *error = nil;
558
//        NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
559
        NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
560
        if (error) {
561
            dispatch_async(dispatch_get_main_queue(), ^{
562
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
563
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath] 
564
                                                          error:error];
565
            });
566
            [pool drain];
567
            return;
568
        }
569
        if ([subPaths count]) {
570
//            NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
571
//            for (NSString *subPath in subPaths) {
572
//                [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
573
//            }
574
//            [self syncOperationStarted];
575
//            [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
576
//                if (error) {
577
//                    dispatch_async(dispatch_get_main_queue(), ^{
578
//                        [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error" 
579
//                                                                message:@"Cannot move files to Trash" 
580
//                                                                  error:error];
581
//                    });
582
//                }
583
//                [self syncOperationFinishedWithSuccess:YES];
584
//            }];
585
            for (NSString *subPath in subPaths) {
586
                NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
587
                error = nil;
588
                if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
589
                    dispatch_async(dispatch_get_main_queue(), ^{
590
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
591
                                                                message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
592
                                                                  error:error];
593
                    });
594
            }
595
        }
596
    }
597
    [pool drain];
598
}
599

    
600
- (BOOL)moveToTempTrashFile:(NSString *)filePath pithosContainer:(ASIPithosContainer *)pithosContainer {
601
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
602
    if (!self.tempTrashDirPath) {
603
        [pool drain];
604
        return NO;
605
    }
606
    NSFileManager *fileManager = [NSFileManager defaultManager];
607
    BOOL isDirectory;
608
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
609
    NSError *error = nil;
610
    NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
611
    NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath withString:self.tempTrashDirPath];
612
    NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
613
    if (fileExists && isDirectory) {
614
        NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
615
        if (error) {
616
            dispatch_async(dispatch_get_main_queue(), ^{
617
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
618
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath] 
619
                                                          error:error];
620
            });
621
            [pool drain];
622
            return NO;
623
        }
624
        if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
625
            dispatch_async(dispatch_get_main_queue(), ^{
626
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
627
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
628
                                                          error:error];
629
            });
630
            [pool drain];
631
            return NO;
632
        }
633
        if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
634
            dispatch_async(dispatch_get_main_queue(), ^{
635
                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
636
                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
637
                                                                 filePath, newFilePath] 
638
                                                          error:error];
639
            });
640
            [pool drain];
641
            return NO;
642
        }
643
        PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
644
        if (currentState) {
645
            currentState.filePath = newFilePath;
646
            [currentLocalObjectStates setObject:currentState forKey:newFilePath];
647
            [currentLocalObjectStates removeObjectForKey:filePath];        
648
        } else {
649
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
650
                                                                                       blockHash:pithosContainer.blockHash 
651
                                                                                       blockSize:pithosContainer.blockSize] 
652
                                         forKey:newFilePath];
653
        }
654
        for (NSString *subPath in subPaths) {
655
            NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
656
            NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath 
657
                                                                              withString:self.tempTrashDirPath];
658
            currentState = [currentLocalObjectStates objectForKey:subFilePath];
659
            if (currentState) {
660
                currentState.filePath = newSubFilePath;
661
                [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
662
                [currentLocalObjectStates removeObjectForKey:subFilePath];
663
            } else {
664
                [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath 
665
                                                                                           blockHash:pithosContainer.blockHash 
666
                                                                                           blockSize:pithosContainer.blockSize] 
667
                                             forKey:newSubFilePath];
668
            }        
669
        }
670
    } else if (fileExists && !isDirectory) {
671
        if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
672
            dispatch_async(dispatch_get_main_queue(), ^{
673
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
674
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
675
                                                          error:error];
676
            });
677
            [pool drain];
678
            return NO;
679
        }
680
        if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
681
            dispatch_async(dispatch_get_main_queue(), ^{
682
                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
683
                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
684
                                                                 filePath, newFilePath] 
685
                                                          error:error];
686
            });
687
            [pool drain];
688
            return NO;
689
        }
690
        PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
691
        if (currentState) {
692
            currentState.filePath = newFilePath;
693
            [currentLocalObjectStates setObject:currentState forKey:newFilePath];
694
            [currentLocalObjectStates removeObjectForKey:filePath];        
695
        } else {
696
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
697
                                                                                       blockHash:pithosContainer.blockHash 
698
                                                                                       blockSize:pithosContainer.blockSize] 
699
                                         forKey:newFilePath];
700
        }
701
    }
702
    [pool drain];
703
    return YES;
704
}
705

    
706
- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
707
    if ([hash length] != 64)
708
        return NO;
709
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
710
    PithosLocalObjectState *localState;
711
    NSFileManager *fileManager = [NSFileManager defaultManager];
712
    BOOL isDirectory;
713
    NSError *error = nil;
714
    for (NSString *localFilePath in currentLocalObjectStates) {
715
        localState = [currentLocalObjectStates objectForKey:localFilePath];
716
        if (!localState.isDirectory && [hash isEqualToString:localState.hash] && 
717
            [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
718
            if ([localFilePath hasPrefix:directoryPath]) {
719
                if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
720
                    dispatch_async(dispatch_get_main_queue(), ^{
721
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error" 
722
                                                                message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'", 
723
                                                                         localFilePath, filePath] 
724
                                                                  error:error];
725
                    });
726
                } else {
727
                    [pool drain];
728
                    return YES;
729
                }
730
            } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
731
                if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
732
                    dispatch_async(dispatch_get_main_queue(), ^{
733
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
734
                                                                message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
735
                                                                         localFilePath, filePath] 
736
                                                                  error:error];
737
                    });
738
                } else {
739
                    localState.filePath = filePath;
740
                    [currentLocalObjectStates setObject:localState forKey:filePath];
741
                    [currentLocalObjectStates removeObjectForKey:localFilePath];
742
                    [pool drain];
743
                    return YES;
744
                }
745
            }
746
        }
747
    }
748
    [pool drain];
749
    return NO;
750
}
751

    
752
- (void)updateLocalStateWithObject:(ASIPithosObject *)object
753
                     localFilePath:(NSString *)filePath 
754
                   pithosContainer:(ASIPithosContainer *)pithosContainer {
755
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
756
    NSFileManager *fileManager = [NSFileManager defaultManager];
757
    NSError *error;
758
    BOOL isDirectory;
759
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
760
    NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
761
    NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
762
    PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
763
    // Remote updated info
764
    NSError *remoteError;
765
    BOOL remoteIsDirectory;
766
    BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos 
767
                                                      containerName:pithosContainer.name 
768
                                                         objectName:object.name 
769
                                                              error:&remoteError 
770
                                                        isDirectory:&remoteIsDirectory 
771
                                                     sharingAccount:nil];
772
    if (!object || !object.objectHash) {
773
        // Delete local object
774
        if (remoteObjectExists) {
775
            // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
776
            syncIncomplete = YES;
777
        }
778
        NSLog(@"Sync::delete local object: %@", filePath);
779
        if (!fileExists || [self moveToTempTrashFile:filePath pithosContainer:pithosContainer]) {
780
            dispatch_async(dispatch_get_main_queue(), ^{
781
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
782
                                                      message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)", 
783
                                                               pithosContainer.name, object.name] 
784
                                                pithosAccount:pithosAccount];
785
            });
786
            [containerStoredLocalObjectStates removeObjectForKey:object.name];
787
            [self saveLocalState];
788
        }
789
    } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
790
        // Create local directory object
791
        if (!remoteObjectExists || !remoteIsDirectory) {
792
            // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
793
            syncIncomplete = YES;
794
            [pool drain];
795
            return;
796
        }
797
        NSLog(@"Sync::create local directory object: %@", filePath);
798
        BOOL directoryCreated = NO;
799
        if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath pithosContainer:pithosContainer])) {
800
            NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
801
            error = nil;
802
            if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
803
                dispatch_async(dispatch_get_main_queue(), ^{
804
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
805
                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath] 
806
                                                              error:error];
807
                });
808
            } else {
809
                directoryCreated = YES;
810
                storedState.filePath = filePath;
811
                storedState.isDirectory = YES;
812
                [self saveLocalState];
813
            }
814
        } else {
815
            NSLog(@"Sync::local directory object exists: %@", filePath);
816
            directoryCreated = YES;
817
        }
818
        if (directoryCreated)
819
            dispatch_async(dispatch_get_main_queue(), ^{
820
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
821
                                                      message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)", 
822
                                                               pithosContainer.name, object.name] 
823
                                                pithosAccount:pithosAccount];
824
            });
825
    } else if (object.bytes == 0) {
826
        // Create local object with zero length
827
        if (!remoteObjectExists || remoteIsDirectory) {
828
            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
829
            syncIncomplete = YES;
830
            [pool drain];
831
            return;
832
        }
833
        NSLog(@"Sync::create local zero length object: %@", filePath);
834
        BOOL fileCreated = NO;
835
        if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath pithosContainer:pithosContainer])) {
836
            NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
837
            // Create directory of the file, if it doesn't exist
838
            error = nil;
839
            if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
840
                dispatch_async(dispatch_get_main_queue(), ^{
841
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
842
                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
843
                                                              error:error];
844
                });
845
            }
846
            error = nil;
847
            if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
848
                dispatch_async(dispatch_get_main_queue(), ^{
849
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error" 
850
                                                            message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath] 
851
                                                              error:error];
852
                });
853
            } else {
854
                fileCreated = YES;
855
                storedState.filePath = filePath;
856
                storedState.hash = object.objectHash;
857
                storedState.tmpFilePath = nil;
858
                [self saveLocalState];
859
            }
860
        } else {
861
            NSLog(@"Sync::local zero length object exists: %@", filePath);
862
            fileCreated = YES;
863
        }
864
        if (fileCreated)
865
            dispatch_async(dispatch_get_main_queue(), ^{
866
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
867
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", 
868
                                                               pithosContainer.name, object.name] 
869
                                                pithosAccount:pithosAccount];
870
            });
871
    } else if (storedState.tmpFilePath == nil) {
872
        // Create new local object
873
        if (!remoteObjectExists || remoteIsDirectory) {
874
            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
875
            syncIncomplete = YES;
876
            [pool drain];
877
            return;
878
        }
879
        // Create directory of the file, if it doesn't exist
880
        error = nil;
881
        if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
882
            dispatch_async(dispatch_get_main_queue(), ^{
883
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
884
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
885
                                                          error:error];
886
            });
887
        }
888
        // Check first if a local copy exists
889
        if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
890
            storedState.filePath = filePath;
891
            storedState.hash = object.objectHash;
892
            [self saveLocalState];
893
            dispatch_async(dispatch_get_main_queue(), ^{
894
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
895
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", 
896
                                                               pithosContainer.name, object.name] 
897
                                                pithosAccount:pithosAccount];
898
            });
899
        } else {
900
            [self syncOperationStarted];
901
            __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
902
                                                                                                containerName:pithosContainer.name 
903
                                                                                                       object:object 
904
                                                                                                   blockIndex:0 
905
                                                                                                    blockSize:pithosContainer.blockSize];
906
            objectRequest.delegate = self;
907
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
908
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
909
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
910
                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (0%%)", 
911
                                                                                pithosContainer.name, object.name] 
912
                                                                    totalBytes:object.bytes 
913
                                                                  currentBytes:0 
914
                                                                 pithosAccount:pithosAccount];
915
            dispatch_async(dispatch_get_main_queue(), ^{
916
                [activityFacility updateActivity:activity withMessage:activity.message];
917
            });
918
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
919
                                      pithosContainer, @"pithosContainer", 
920
                                      object, @"pithosObject", 
921
                                      [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks", 
922
                                      [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex", 
923
                                      filePath, @"filePath", 
924
                                      activity, @"activity", 
925
                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
926
                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
927
                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", pithosContainer.name, object.name], @"finishedActivityMessage", 
928
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
929
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
930
                                      NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector", 
931
                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
932
                                      nil];
933
            [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
934
                [activityFacility updateActivity:activity 
935
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
936
                                                  objectRequest.containerName, 
937
                                                  [[objectRequest.userInfo objectForKey:@"pithosObject"] name], 
938
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
939
                                      totalBytes:activity.totalBytes 
940
                                    currentBytes:(activity.currentBytes + size)];
941
            }];
942
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
943
        }
944
    } else {
945
        // Resume local object download
946
        if (!remoteObjectExists || remoteIsDirectory) {
947
            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
948
            syncIncomplete = YES;
949
            [pool drain];
950
            return;
951
        }
952
        // Create directory of the file, if it doesn't exist
953
        error = nil;
954
        if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
955
            dispatch_async(dispatch_get_main_queue(), ^{
956
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
957
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
958
                                                          error:error];
959
            });
960
        }
961
        // Check first if a local copy exists
962
        if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
963
            storedState.filePath = filePath;
964
            storedState.hash = object.objectHash;
965
            // Delete incomplete temp download
966
            storedState.tmpFilePath = nil;
967
            [self saveLocalState];
968
            dispatch_async(dispatch_get_main_queue(), ^{
969
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
970
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", 
971
                                                               pithosContainer.name, object.name] 
972
                                                pithosAccount:pithosAccount];
973
            });
974
        } else {
975
            [self syncOperationStarted];
976
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos 
977
                                                                                             containerName:pithosContainer.name 
978
                                                                                                objectName:object.name];
979
            objectRequest.delegate = self;
980
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
981
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
982
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
983
                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (0%%)", 
984
                                                                                pithosContainer.name, object.name] 
985
                                                                    totalBytes:object.bytes 
986
                                                                  currentBytes:0 
987
                                                                 pithosAccount:pithosAccount];
988
            dispatch_async(dispatch_get_main_queue(), ^{
989
                [activityFacility updateActivity:activity withMessage:activity.message];
990
            });
991
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
992
                                      pithosContainer, @"pithosContainer", 
993
                                      object, @"pithosObject", 
994
                                      filePath, @"filePath", 
995
                                      activity, @"activity", 
996
                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
997
                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
998
                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", pithosContainer.name, object.name], @"finishedActivityMessage", 
999
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1000
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
1001
                                      NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector", 
1002
                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1003
                                      nil];
1004
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1005
        }
1006
    }
1007
    [pool drain];
1008
}
1009

    
1010
-(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
1011
                                  object:(ASIPithosObject *)object 
1012
                           localFilePath:(NSString *)filePath 
1013
                         pithosContainer:(ASIPithosContainer *)pithosContainer {
1014
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1015
    [self syncOperationStarted];
1016
    NSFileManager *fileManager = [NSFileManager defaultManager];
1017
    BOOL isDirectory;
1018
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1019
    if (currentState.isDirectory) {
1020
        // Create remote directory object
1021
        if (!fileExists || !isDirectory) {
1022
            // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
1023
            [self syncOperationFinishedWithSuccess:NO];
1024
            [pool drain];
1025
            return;
1026
        }
1027
        ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
1028
                                                                                           containerName:pithosContainer.name 
1029
                                                                                              objectName:object.name 
1030
                                                                                                    eTag:nil 
1031
                                                                                             contentType:@"application/directory" 
1032
                                                                                         contentEncoding:nil 
1033
                                                                                      contentDisposition:nil 
1034
                                                                                                manifest:nil 
1035
                                                                                                 sharing:nil 
1036
                                                                                                isPublic:ASIPithosObjectRequestPublicIgnore 
1037
                                                                                                metadata:nil 
1038
                                                                                                    data:[NSData data]];
1039
        objectRequest.delegate = self;
1040
        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1041
        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1042
        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
1043
                                                                   message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@'", 
1044
                                                                            pithosContainer.name, object.name] 
1045
                                                             pithosAccount:pithosAccount];
1046
        dispatch_async(dispatch_get_main_queue(), ^{
1047
            [activityFacility updateActivity:activity withMessage:activity.message];
1048
        });
1049
        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1050
                                  pithosContainer, @"pithosContainer", 
1051
                                  object, @"pithosObject", 
1052
                                  activity, @"activity", 
1053
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
1054
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
1055
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage", 
1056
                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1057
                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
1058
                                  NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
1059
                                  NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1060
                                  nil];
1061
        [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1062
    } else if (![currentState exists]) {
1063
        // Delete remote object
1064
        if (fileExists) {
1065
            // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
1066
            syncIncomplete = YES;
1067
        }
1068
        if ([pithosContainer.name isEqualToString:@"trash"]) {
1069
            // Delete
1070
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos 
1071
                                                                                            containerName:pithosContainer.name 
1072
                                                                                               objectName:object.name];
1073
            objectRequest.delegate = self;
1074
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1075
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1076
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
1077
                                                                       message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@'", 
1078
                                                                                pithosContainer.name, object.name] 
1079
                                                                 pithosAccount:pithosAccount];
1080
            dispatch_async(dispatch_get_main_queue(), ^{
1081
                [activityFacility updateActivity:activity withMessage:activity.message];
1082
            });
1083
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1084
                                      pithosContainer, @"pithosContainer", 
1085
                                      object, @"pithosObject", 
1086
                                      activity, @"activity", 
1087
                                      [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
1088
                                      [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
1089
                                      [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage", 
1090
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1091
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
1092
                                      NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", 
1093
                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1094
                                      nil];
1095
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1096
        } else {
1097
            // Move to container trash
1098
            NSString *safeName;
1099
            if ([PithosUtilities isContentTypeDirectory:object.contentType])
1100
                safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
1101
            else
1102
                safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
1103
            if (safeName) {
1104
                ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
1105
                                                                                       containerName:pithosContainer.name 
1106
                                                                                          objectName:object.name 
1107
                                                                            destinationContainerName:@"trash" 
1108
                                                                               destinationObjectName:safeName 
1109
                                                                                       checkIfExists:NO];
1110
                if (objectRequest) {
1111
                    objectRequest.delegate = self;
1112
                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1113
                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1114
                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
1115
                                                                               message:[NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'", 
1116
                                                                                        pithosContainer.name, object.name] 
1117
                                                                         pithosAccount:pithosAccount];
1118
                    dispatch_async(dispatch_get_main_queue(), ^{
1119
                        [activityFacility updateActivity:activity withMessage:activity.message];
1120
                    });
1121
                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1122
                                              pithosContainer, @"pithosContainer", 
1123
                                              object, @"pithosObject", 
1124
                                              activity, @"activity", 
1125
                                              [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
1126
                                              [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
1127
                                              [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage", 
1128
                                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1129
                                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
1130
                                              NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector", 
1131
                                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1132
                                              nil];
1133
                    [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1134
                } else {
1135
                    [self syncOperationFinishedWithSuccess:NO];
1136
                }
1137
            } else {
1138
                [self syncOperationFinishedWithSuccess:NO];
1139
            }
1140
        }
1141
    } else {
1142
        // Upload file to remote object
1143
        if (!fileExists || isDirectory) {
1144
            // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
1145
            [self syncOperationFinishedWithSuccess:NO];
1146
            [pool drain];
1147
            return;
1148
        }
1149
        NSError *error = nil;
1150
        object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1151
        if (object.contentType == nil)
1152
            object.contentType = @"application/octet-stream";
1153
        if (error)
1154
            NSLog(@"contentType detection error: %@", error);
1155
        NSArray *hashes = nil;
1156
        ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
1157
                                                                                    containerName:pithosContainer.name 
1158
                                                                                       objectName:object.name 
1159
                                                                                      contentType:object.contentType 
1160
                                                                                        blockSize:pithosContainer.blockSize 
1161
                                                                                        blockHash:pithosContainer.blockHash 
1162
                                                                                          forFile:filePath 
1163
                                                                                    checkIfExists:NO 
1164
                                                                                           hashes:&hashes 
1165
                                                                                   sharingAccount:nil];
1166
        if (objectRequest) {
1167
            objectRequest.delegate = self;
1168
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1169
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1170
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
1171
                                                                       message:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (0%%)", 
1172
                                                                                pithosContainer.name, object.name]
1173
                                                                    totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1174
                                                                  currentBytes:0 
1175
                                                                 pithosAccount:pithosAccount];
1176
            dispatch_async(dispatch_get_main_queue(), ^{
1177
                [activityFacility updateActivity:activity withMessage:activity.message];
1178
            });
1179
            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1180
             [NSDictionary dictionaryWithObjectsAndKeys:
1181
              pithosContainer, @"pithosContainer", 
1182
              object, @"pithosObject", 
1183
              filePath, @"filePath", 
1184
              hashes, @"hashes", 
1185
              [NSNumber numberWithUnsignedInteger:10], @"iteration", 
1186
              activity, @"activity", 
1187
              [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
1188
              [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage", 
1189
              [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage", 
1190
              [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1191
              [NSNumber numberWithUnsignedInteger:10], @"retries", 
1192
              NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
1193
              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1194
              nil]];
1195
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1196
        } else {
1197
            [self syncOperationFinishedWithSuccess:NO];
1198
        }
1199
    }
1200
    [pool drain];
1201
}
1202

    
1203
#pragma mark -
1204
#pragma mark ASIHTTPRequestDelegate
1205

    
1206
- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1207
    // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1208
    NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1209
                                                                             selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) 
1210
                                                                               object:request] autorelease];
1211
    operation.completionBlock = ^{
1212
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1213
        if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1214
            dispatch_async(dispatch_get_main_queue(), ^{
1215
                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1216
                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1217
            });
1218
            [self syncOperationFinishedWithSuccess:NO];
1219
        }
1220
        [pool drain];
1221
    };
1222
    [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1223
    [callbackQueue addOperation:operation];
1224
}
1225

    
1226
- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1227
    if (request.isCancelled) {
1228
        // Request has been cancelled 
1229
        // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1230
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1231
                   withObject:request];
1232
    } else {
1233
        // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1234
        NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1235
                                                                                 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1236
                                                                                   object:request] autorelease];
1237
        operation.completionBlock = ^{
1238
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1239
            if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1240
                dispatch_async(dispatch_get_main_queue(), ^{
1241
                    [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1242
                                      withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1243
                });
1244
                [self syncOperationFinishedWithSuccess:NO];
1245
            }
1246
            [pool drain];
1247
        };
1248
        [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1249
        [callbackQueue addOperation:operation];
1250
    }
1251
}
1252

    
1253
- (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1254
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1255
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1256
    NSLog(@"Sync::list request finished: %@", containerRequest.url);
1257
    if (operation.isCancelled) {
1258
        [self listRequestFailed:containerRequest];
1259
    } else if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
1260
        if (containerRequest.responseStatusCode == 200) {
1261
            NSArray *someObjects = [containerRequest objects];
1262
            if (objects == nil) {
1263
                objects = [[NSMutableArray alloc] initWithArray:someObjects];
1264
            } else {
1265
                [objects addObjectsFromArray:someObjects];
1266
            }
1267
            if ([someObjects count] < 10000) {
1268
                ASIPithosContainer *pithosContainer = [pithosContainers objectAtIndex:containersIndex];
1269
                pithosContainer.blockHash = [containerRequest blockHash];
1270
                pithosContainer.blockSize = [containerRequest blockSize];
1271
                pithosContainer.lastModified = [containerRequest lastModified];
1272
                NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1273
                for (ASIPithosObject *object in objects) {
1274
                    [containerRemoteObjects setObject:object forKey:object.name];
1275
                }
1276
                [remoteObjects setObject:containerRemoteObjects forKey:pithosContainer.name];
1277
                [objects release];
1278
                objects = nil;
1279
            } else {
1280
                // Do an additional request to fetch more objects
1281
                ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
1282
                                                                                                           containerName:[[pithosContainers objectAtIndex:containersIndex] name] 
1283
                                                                                                                   limit:0 
1284
                                                                                                                  marker:[[someObjects lastObject] name] 
1285
                                                                                                                  prefix:nil 
1286
                                                                                                               delimiter:nil 
1287
                                                                                                                    path:nil 
1288
                                                                                                                    meta:nil 
1289
                                                                                                                  shared:NO 
1290
                                                                                                                   until:nil 
1291
                                                                                                         ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
1292
                newContainerRequest.delegate = self;
1293
                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1294
                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1295
                newContainerRequest.userInfo = containerRequest.userInfo;
1296
                [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1297
                [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1298
                [pool drain];
1299
                return;
1300
            }
1301
        } else {
1302
            ASIPithosContainer *pithosContainer = [pithosContainers objectAtIndex:containersIndex];
1303
            NSMutableDictionary *containerRemoteObjects = [previousRemoteObjects objectForKey:pithosContainer.name];
1304
            if (containerRemoteObjects) 
1305
                [remoteObjects setObject:containerRemoteObjects forKey:pithosContainer.name];
1306
        }
1307
        containersIndex++;
1308
        if (containersIndex < containersCount) {
1309
            // Do a request for the next container
1310
            ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
1311
                                                                                                       containerName:[[pithosContainers objectAtIndex:containersIndex] name] 
1312
                                                                                                               limit:0 
1313
                                                                                                              marker:nil 
1314
                                                                                                              prefix:nil 
1315
                                                                                                           delimiter:nil 
1316
                                                                                                                path:nil 
1317
                                                                                                                meta:nil 
1318
                                                                                                              shared:NO 
1319
                                                                                                               until:nil 
1320
                                                                                                     ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
1321
            newContainerRequest.delegate = self;
1322
            newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1323
            newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1324
            newContainerRequest.userInfo = containerRequest.userInfo;
1325
            [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1326
            [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1327
            [pool drain];
1328
            return;
1329
        }
1330
        self.previousRemoteObjects = remoteObjects;
1331
        // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
1332
        
1333
        if (operation.isCancelled) {
1334
            [self listRequestFailed:containerRequest];
1335
            [pool drain];
1336
            return;
1337
        }
1338

    
1339
        dispatch_async(dispatch_get_main_queue(), ^{
1340
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1341
                              withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1342
        });
1343
        NSFileManager *fileManager = [NSFileManager defaultManager];
1344
        
1345
        // Compute current state of legal existing local objects 
1346
        // and add an empty stored state for legal new local objects since last sync
1347
        self.currentLocalObjectStates = [NSMutableDictionary dictionary];
1348
        for (ASIPithosContainer *pithosContainer in pithosContainers) {
1349
            NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
1350
            NSArray *containerExcludedDirectories = [containersDictionary objectForKey:pithosContainer.name];
1351
            BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1352
            NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
1353
            NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath];
1354
            for (NSString *objectName in dirEnumerator) {
1355
                if (operation.isCancelled) {
1356
                    operation.completionBlock = nil;
1357
                    [self saveLocalState];
1358
                    [self syncOperationFinishedWithSuccess:NO];
1359
                    [pool drain];
1360
                    return;
1361
                }
1362

    
1363
                NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1364
                NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1365
                BOOL isDirectory;
1366
                BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1367
                if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) {
1368
                    dispatch_async(dispatch_get_main_queue(), ^{
1369
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error" 
1370
                                                                message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.", 
1371
                                                                         containerDirectoryPath, pithosAccount.name] 
1372
                                                                  error:nil];
1373
                    });
1374
                    pithosAccount.syncActive = NO;
1375
                    return;
1376
                } else if (fileExists) {
1377
                    NSArray *pathComponents = [objectName pathComponents];
1378
                    if ([pathComponents count] == 1) {
1379
                        if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) {
1380
                            // Skip excluded directory and its descendants, or root file with same name
1381
                            if (isDirectory)
1382
                                [dirEnumerator skipDescendants];
1383
                            // Remove stored state if any
1384
                            [containerStoredLocalObjectStates removeObjectForKey:objectName];
1385
                            continue;
1386
                        } else if (!isDirectory && containerExludeRootFiles) {
1387
                            // Skip excluded root file
1388
                            // Remove stored state if any
1389
                            [containerStoredLocalObjectStates removeObjectForKey:objectName];
1390
                            continue;
1391
                        }
1392
                    }
1393
                    // Include local object
1394
                    PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
1395
                    if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1396
                        // New or modified existing local object, compute current state
1397
                        if (!storedLocalObjectState)
1398
                            // For new local object, also create empty stored state
1399
                            [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1400
                        [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath 
1401
                                                                                                   blockHash:pithosContainer.blockHash 
1402
                                                                                                   blockSize:pithosContainer.blockSize] 
1403
                                                     forKey:filePath];
1404
                    } else {
1405
                        // Local object hasn't changed, set stored state also to current
1406
                        [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1407
                    }
1408
                }
1409
            }
1410
            [self saveLocalState];
1411
        }    
1412
        
1413
        if (operation.isCancelled) {
1414
            operation.completionBlock = nil;
1415
            [self syncOperationFinishedWithSuccess:NO];
1416
            [pool drain];
1417
            return;
1418
        }
1419

    
1420
        // Add an empty stored state for legal new remote objects since last sync
1421
        for (ASIPithosContainer *pithosContainer in pithosContainers) {
1422
            NSArray *containerExcludedDirectories = [containersDictionary objectForKey:pithosContainer.name];
1423
            BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1424
            NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
1425
            NSMutableDictionary *containerRemoteObjects = [remoteObjects objectForKey:pithosContainer.name];
1426
            for (NSString *objectName in containerRemoteObjects) {
1427
                if (operation.isCancelled) {
1428
                    operation.completionBlock = nil;
1429
                    [self saveLocalState];
1430
                    [self syncOperationFinishedWithSuccess:NO];
1431
                    [pool drain];
1432
                    return;
1433
                }
1434

    
1435
                ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName];
1436
                NSString *localObjectName;
1437
                if ([object.name hasSuffix:@"/"])
1438
                    localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1439
                else
1440
                    localObjectName = [NSString stringWithString:object.name];
1441
                NSArray *pathComponents = [localObjectName pathComponents];
1442
                if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1443
                    // Skip excluded directory object and its descendants, or root file object with same name
1444
                    // Remove stored state if any
1445
                    [containerStoredLocalObjectStates removeObjectForKey:object.name];
1446
                    continue;
1447
                } else if (containerExludeRootFiles && 
1448
                           ([pathComponents count] == 1) && 
1449
                           ![PithosUtilities isContentTypeDirectory:object.contentType]) {
1450
                    // Skip root file object
1451
                    // Remove stored state if any
1452
                    [containerStoredLocalObjectStates removeObjectForKey:object.name];                    
1453
                    continue;
1454
                }
1455
                if (![containerStoredLocalObjectStates objectForKey:object.name])
1456
                    [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name];
1457
            }
1458
            [self saveLocalState];
1459
        }
1460

    
1461
        if (operation.isCancelled) {
1462
            operation.completionBlock = nil;
1463
            [self syncOperationFinishedWithSuccess:NO];
1464
            [pool drain];
1465
            return;
1466
        }
1467

    
1468
        // For each stored state compare with current and remote state
1469
        // Stored states of local objects that have been deleted, 
1470
        // haven't been checked for legality (only existing local remote objects)
1471
        // These should be identified and skipped
1472
        for (ASIPithosContainer *pithosContainer in pithosContainers) {
1473
            NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
1474
            NSArray *containerExcludedDirectories = [containersDictionary objectForKey:pithosContainer.name];
1475
            BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1476
            NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
1477
            NSMutableDictionary *containerRemoteObjects = [remoteObjects objectForKey:pithosContainer.name];
1478
            for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1479
                if (operation.isCancelled) {
1480
                    operation.completionBlock = nil;
1481
                    [self syncOperationFinishedWithSuccess:NO];
1482
                    [pool drain];
1483
                    return;
1484
                }
1485

    
1486
                NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1487
                if ([objectName hasSuffix:@"/"])
1488
                    filePath = [filePath stringByAppendingString:@":"];
1489
                ASIPithosObject *object = [ASIPithosObject object];
1490
                object.name = objectName;
1491
                NSLog(@"Sync::object name: %@", object.name);
1492
            
1493
                PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
1494
                PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1495
                if (!currentLocalObjectState) {
1496
                    // The stored state corresponds to a remote or deleted local object, that's why there is no current state
1497
                    // In the latter case it must be checked for legality, which can be determined by its stored state
1498
                    // If it existed locally, but was deleted, state.exists is true, 
1499
                    // else if the stored state is an empty state that was created due to the server object, state.exists is false
1500
                    if (storedLocalObjectState.exists) {
1501
                        NSString *localObjectName;
1502
                        if ([object.name hasSuffix:@"/"])
1503
                            localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1504
                        else
1505
                            localObjectName = [NSString stringWithString:object.name];
1506
                        NSArray *pathComponents = [localObjectName pathComponents];
1507
                        if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1508
                            // Skip excluded directory object and its descendants, or root file object with same name
1509
                            // Remove stored state
1510
                            [containerStoredLocalObjectStates removeObjectForKey:object.name];
1511
                            [self saveLocalState];
1512
                            continue;
1513
                        } else if (containerExludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) {
1514
                            // Skip root file object
1515
                            // Remove stored state
1516
                            [containerStoredLocalObjectStates removeObjectForKey:object.name];                    
1517
                            [self saveLocalState];
1518
                            continue;
1519
                        }
1520
                    }
1521
                    // There is also the off case that a local object has been created in the meantime
1522
                    // This call works in any case, existent or non-existent local object 
1523
                    currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath 
1524
                                                                                     blockHash:pithosContainer.blockHash 
1525
                                                                                     blockSize:pithosContainer.blockSize];
1526
                }
1527
            
1528
                PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1529
                ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
1530
                if (remoteObject) {
1531
                    if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1532
                        remoteObjectState.isDirectory = YES;
1533
                    } else {
1534
                        remoteObjectState.hash = remoteObject.objectHash;
1535
                    }
1536
                }
1537

    
1538
                BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1539
                BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1540
                NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1541
                if (!localStateHasChanged) {
1542
                    // Local state hasn't changed
1543
                    if (serverStateHasChanged) {
1544
                        // Server state has changed
1545
                        // Update local state to match that of the server 
1546
                        object.bytes = remoteObject.bytes;
1547
                        object.version = remoteObject.version;
1548
                        object.contentType = remoteObject.contentType;
1549
                        object.objectHash = remoteObject.objectHash;
1550
                        [self updateLocalStateWithObject:object localFilePath:filePath pithosContainer:pithosContainer];
1551
                    } else if (!remoteObject && ![currentLocalObjectState exists]) {
1552
                        // Server state hasn't changed
1553
                        // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1554
                        [containerStoredLocalObjectStates removeObjectForKey:objectName];
1555
                        [self saveLocalState];
1556
                    }
1557
                } else {
1558
                    // Local state has changed
1559
                    if (!serverStateHasChanged) {
1560
                        // Server state hasn't changed
1561
                        if (currentLocalObjectState.isDirectory)
1562
                            object.contentType = @"application/directory";
1563
                        else
1564
                            object.objectHash = currentLocalObjectState.hash;
1565
                        [self updateServerStateWithCurrentState:currentLocalObjectState 
1566
                                                         object:object 
1567
                                                  localFilePath:filePath 
1568
                                                pithosContainer:pithosContainer];
1569
                    } else {
1570
                        // Server state has also changed
1571
                        if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1572
                            // Both did the same change (directory)
1573
                            storedLocalObjectState.filePath = filePath;
1574
                            storedLocalObjectState.isDirectory = YES;
1575
                            [self saveLocalState];
1576
                        } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1577
                            // Both did the same change (object edit or delete)
1578
                            if (![remoteObjectState exists]) {
1579
                                [containerStoredLocalObjectStates removeObjectForKey:object.name];
1580
                            } else {
1581
                                storedLocalObjectState.filePath = filePath;
1582
                                storedLocalObjectState.hash = remoteObjectState.hash;
1583
                            }
1584
                            [self saveLocalState];
1585
                        } else {
1586
                            // Conflict, we ask the user which change to keep
1587
                            NSString *informativeText;
1588
                            NSString *firstButtonText;
1589
                            NSString *secondButtonText;
1590
                            
1591
                            if (![remoteObjectState exists]) {
1592
                                // Remote object has been deleted
1593
                                informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.", 
1594
                                                   pithosContainer.name, object.name ];
1595
                                firstButtonText = @"Delete local file";
1596
                                secondButtonText = @"Upload file to server";
1597
                            } else if (![currentLocalObjectState exists]) {
1598
                                informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.", 
1599
                                                   pithosContainer.name, object.name];
1600
                                firstButtonText = @"Download file from server";
1601
                                secondButtonText = @"Delete file on server";
1602
                            } else {
1603
                                informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.", 
1604
                                                   pithosContainer.name, object.name];
1605
                                firstButtonText = @"Keep server version";
1606
                                secondButtonText = @"Keep local version";
1607
                            }
1608
                            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1609
                            [alert setMessageText:@"Conflict"];
1610
                            [alert setInformativeText:informativeText];
1611
                            [alert addButtonWithTitle:firstButtonText];
1612
                            [alert addButtonWithTitle:secondButtonText];
1613
                            [alert addButtonWithTitle:@"Do nothing"];
1614
                            NSInteger choice = [alert runModal];
1615
                            if (choice == NSAlertFirstButtonReturn) {
1616
                                object.bytes = remoteObject.bytes;
1617
                                object.version = remoteObject.version;
1618
                                object.contentType = remoteObject.contentType;
1619
                                object.objectHash = remoteObject.objectHash;
1620
                                [self updateLocalStateWithObject:object localFilePath:filePath pithosContainer:pithosContainer];
1621
                            } if (choice == NSAlertSecondButtonReturn) {
1622
                                if (currentLocalObjectState.isDirectory)
1623
                                    object.contentType = @"application/directory";
1624
                                else
1625
                                    object.objectHash = currentLocalObjectState.hash;
1626
                                [self updateServerStateWithCurrentState:currentLocalObjectState 
1627
                                                                 object:object 
1628
                                                          localFilePath:filePath 
1629
                                                        pithosContainer:pithosContainer];
1630
                            }
1631
                        }
1632
                    }
1633
                }
1634
            }
1635
        }
1636
        [self syncOperationFinishedWithSuccess:YES];
1637
    } else {
1638
        [self listRequestFailed:containerRequest];
1639
    }
1640
    [pool drain];
1641
}
1642

    
1643
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1644
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1645
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1646
    if (operation.isCancelled) {
1647
        [objects release];
1648
        objects = nil;
1649
        [pool drain];
1650
        return;        
1651
    }
1652
    if (containerRequest.isCancelled) {
1653
        dispatch_async(dispatch_get_main_queue(), ^{
1654
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1655
                              withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1656
        });
1657
        [objects release];
1658
        objects = nil;
1659
        [self syncOperationFinishedWithSuccess:NO];
1660
        [pool drain];
1661
        return;
1662
    }
1663
    // If the server listing fails, the sync should start over, so just retrying is enough
1664
    NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1665
    if (retries > 0) {
1666
        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1667
        [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1668
        [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1669
    } else {
1670
        dispatch_async(dispatch_get_main_queue(), ^{
1671
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1672
                              withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1673
        });
1674
        [objects release];
1675
        objects = nil;
1676
        // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1677
        [self syncOperationFinishedWithSuccess:NO];
1678
    }
1679
    [pool drain];
1680
}
1681

    
1682
- (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1683
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1684
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1685
    NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1686
    if (operation.isCancelled) {
1687
        [self requestFailed:objectRequest];
1688
    } else if (objectRequest.responseStatusCode == 206) {
1689
        ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1690
        ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1691
        NSFileManager *fileManager = [NSFileManager defaultManager];
1692
        NSError *error;
1693
        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1694
        
1695
        NSString *downloadsDirPath = self.tempDownloadsDirPath;
1696
        if (!downloadsDirPath) {
1697
            dispatch_async(dispatch_get_main_queue(), ^{
1698
                [activityFacility endActivity:activity 
1699
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1700
            });
1701
            [self syncOperationFinishedWithSuccess:NO];
1702
            [pool drain];
1703
            return;
1704
        }
1705
        
1706
        PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
1707
        if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1708
            NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1709
            const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1710
            char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1711
            strcpy(tempFileNameCString, tempFileTemplateCString);
1712
            int fileDescriptor = mkstemp(tempFileNameCString);
1713
            NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1714
            free(tempFileNameCString);
1715
            if (fileDescriptor == -1) {
1716
                dispatch_async(dispatch_get_main_queue(), ^{
1717
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error" 
1718
                                                            message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath] 
1719
                                                              error:nil];
1720
                    [activityFacility endActivity:activity 
1721
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1722
                });
1723
                [self syncOperationFinishedWithSuccess:NO];
1724
                [pool drain];
1725
                return;
1726
            }
1727
            close(fileDescriptor);
1728
            storedState.tmpFilePath = tempFilePath;
1729
            [self saveLocalState];
1730
        }
1731
        
1732

    
1733
        NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1734
        NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1735
        [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
1736
        [tempFileHandle writeData:[objectRequest responseData]];
1737
        [tempFileHandle closeFile];
1738

    
1739
        NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1740
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1741
        if (missingBlockIndex == NSNotFound) {
1742
            NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1743
            NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1744
            if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath pithosContainer:pithosContainer]) {
1745
                dispatch_async(dispatch_get_main_queue(), ^{
1746
                    [activityFacility endActivity:activity 
1747
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1748
                });
1749
                [self syncOperationFinishedWithSuccess:NO];
1750
                [pool drain];
1751
                return;
1752
            } else if (![fileManager fileExistsAtPath:dirPath]) {
1753
                // File doesn't exist but also the containing directory doesn't exist
1754
                // In most cases this should have been resolved as an update of the corresponding local object,
1755
                // but it never hurts to check
1756
                error = nil;
1757
                [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1758
                if (error != nil) {
1759
                    dispatch_async(dispatch_get_main_queue(), ^{
1760
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
1761
                                                                message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath] 
1762
                                                                  error:error];
1763
                        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1764
                                          withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1765
                    });
1766
                    [self syncOperationFinishedWithSuccess:NO];
1767
                    [pool drain];
1768
                    return;
1769
                }
1770
            }
1771
            // Move file from tmp download
1772
            error = nil;
1773
            [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1774
            if (error != nil) {
1775
                dispatch_async(dispatch_get_main_queue(), ^{
1776
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
1777
                                                            message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath] 
1778
                                                              error:error];
1779
                    [activityFacility endActivity:activity 
1780
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1781
                });
1782
                [self syncOperationFinishedWithSuccess:NO];
1783
                [pool drain];
1784
                return;
1785
            }
1786
            dispatch_async(dispatch_get_main_queue(), ^{
1787
                [activityFacility endActivity:activity 
1788
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1789
                                   totalBytes:activity.totalBytes 
1790
                                 currentBytes:activity.totalBytes];
1791
            });
1792

    
1793
            storedState.filePath = filePath;
1794
            storedState.hash = object.objectHash;
1795
            storedState.tmpFilePath = nil;
1796
            [self saveLocalState];
1797
            [self syncOperationFinishedWithSuccess:YES];
1798
            [pool drain];
1799
            return;
1800
        } else {
1801
            if (newSyncRequested || syncLate || operation.isCancelled) {
1802
                [self requestFailed:objectRequest];
1803
            } else {
1804
                __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
1805
                                                                                                       containerName:pithosContainer.name 
1806
                                                                                                              object:object 
1807
                                                                                                          blockIndex:missingBlockIndex 
1808
                                                                                                           blockSize:pithosContainer.blockSize];
1809
                newObjectRequest.delegate = self;
1810
                newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1811
                newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1812
                newObjectRequest.userInfo = objectRequest.userInfo;
1813
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1814
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1815
                [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1816
                    [activityFacility updateActivity:activity 
1817
                                         withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
1818
                                                      newObjectRequest.containerName, object.name, 
1819
                                                      (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1820
                                          totalBytes:activity.totalBytes 
1821
                                        currentBytes:(activity.currentBytes + size)];
1822
                }];
1823
                [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1824
            }
1825
        }
1826
    } else if (objectRequest.responseStatusCode == 412) {
1827
        // The object has changed on the server
1828
        dispatch_async(dispatch_get_main_queue(), ^{
1829
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1830
                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1831
        });
1832
        [self syncOperationFinishedWithSuccess:NO];
1833
    } else {
1834
        [self requestFailed:objectRequest];
1835
    }
1836
    [pool drain];
1837
}
1838

    
1839
- (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1840
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1841
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1842
    NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1843
    if (operation.isCancelled) {
1844
        [self requestFailed:objectRequest];
1845
    } else if (objectRequest.responseStatusCode == 200) {
1846
        if (newSyncRequested || syncLate || operation.isCancelled) {
1847
            [self requestFailed:objectRequest];
1848
        } else {
1849
            ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1850
            ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1851
            PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
1852
            if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1853
                [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
1854
            PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1855
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath 
1856
                                                                    blockSize:pithosContainer.blockSize 
1857
                                                                    blockHash:pithosContainer.blockHash 
1858
                                                                   withHashes:[objectRequest hashes]];
1859
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1860
            dispatch_async(dispatch_get_main_queue(), ^{
1861
                [activityFacility updateActivity:activity 
1862
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
1863
                                                  pithosContainer.name, object.name, 
1864
                                                  (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))] 
1865
                                      totalBytes:activity.totalBytes 
1866
                                    currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
1867
            });
1868

    
1869
            __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
1870
                                                                                                   containerName:pithosContainer.name 
1871
                                                                                                          object:object 
1872
                                                                                                      blockIndex:missingBlockIndex 
1873
                                                                                                       blockSize:pithosContainer.blockSize];
1874
            newObjectRequest.delegate = self;
1875
            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1876
            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1877
            newObjectRequest.userInfo = objectRequest.userInfo;
1878
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1879
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1880
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1881
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1882
            [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1883
                [activityFacility updateActivity:activity 
1884
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
1885
                                                  newObjectRequest.containerName, object.name, 
1886
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1887
                                      totalBytes:activity.totalBytes 
1888
                                    currentBytes:(activity.currentBytes + size)];
1889
            }];
1890
            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1891
        }
1892
    } else {
1893
        [self requestFailed:objectRequest];
1894
    }
1895
    [pool drain];
1896
}
1897

    
1898
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1899
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1900
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1901
    NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1902
    if (operation.isCancelled) {
1903
        [self requestFailed:objectRequest];
1904
    } else if (objectRequest.responseStatusCode == 201) {
1905
        PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
1906
                                               objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1907
        storedState.isDirectory = YES;
1908
        [self saveLocalState];
1909
        dispatch_async(dispatch_get_main_queue(), ^{
1910
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1911
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1912
        });
1913
        [self syncOperationFinishedWithSuccess:YES];
1914
    } else {
1915
        [self requestFailed:objectRequest];
1916
    }
1917
    [pool drain];
1918
}
1919

    
1920
- (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1921
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1922
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1923
    NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1924
    if (operation.isCancelled) {
1925
        [self requestFailed:objectRequest];
1926
    } else if (objectRequest.responseStatusCode == 201) {
1927
        [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
1928
         removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1929
        [self saveLocalState];
1930
        dispatch_async(dispatch_get_main_queue(), ^{
1931
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1932
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1933
        });
1934
        [self syncOperationFinishedWithSuccess:YES];
1935
    } else {
1936
        [self requestFailed:objectRequest];
1937
    }
1938
    [pool drain];
1939
}
1940

    
1941
- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1942
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1943
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1944
    NSLog(@"Sync::delete object finished: %@", objectRequest.url);
1945
    if (operation.isCancelled) {
1946
        [self requestFailed:objectRequest];
1947
    } else if (objectRequest.responseStatusCode == 204) {
1948
        [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
1949
         removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1950
        [self saveLocalState];
1951
        dispatch_async(dispatch_get_main_queue(), ^{
1952
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1953
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1954
        });
1955
        [self syncOperationFinishedWithSuccess:YES];
1956
    } else {
1957
        [self requestFailed:objectRequest];
1958
    }
1959
    [pool drain];
1960
}
1961

    
1962
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1963
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1964
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1965
    NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1966
    ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1967
    ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1968
    PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
1969
    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1970
    NSUInteger totalBytes = activity.totalBytes;
1971
    NSUInteger currentBytes = activity.currentBytes;
1972
    if (operation.isCancelled) {
1973
        [self requestFailed:objectRequest];
1974
    } else if (objectRequest.responseStatusCode == 201) {
1975
        NSLog(@"Sync::object created: %@", objectRequest.url);
1976
        storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1977
        storedState.hash = object.objectHash;
1978
        [self saveLocalState];
1979
        dispatch_async(dispatch_get_main_queue(), ^{
1980
            [activityFacility endActivity:activity 
1981
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1982
                               totalBytes:totalBytes 
1983
                             currentBytes:totalBytes];
1984
        });
1985
        [self syncOperationFinishedWithSuccess:YES];
1986
    } else if (objectRequest.responseStatusCode == 409) {
1987
        if (newSyncRequested || syncLate || operation.isCancelled) {
1988
            [self requestFailed:objectRequest];
1989
        } else {
1990
            NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1991
            if (iteration == 0) {
1992
                NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
1993
                dispatch_async(dispatch_get_main_queue(), ^{
1994
                    [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1995
                });
1996
                [self syncOperationFinishedWithSuccess:NO];
1997
                [pool drain];
1998
                return;
1999
            }
2000
            NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
2001
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
2002
                                                              withMissingHashes:[objectRequest hashes]];
2003
            if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
2004
                currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
2005
            dispatch_async(dispatch_get_main_queue(), ^{
2006
                [activityFacility updateActivity:activity 
2007
                                     withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", 
2008
                                                  pithosContainer.name, object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
2009
                                      totalBytes:totalBytes 
2010
                                    currentBytes:currentBytes];
2011
            });
2012
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2013
            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
2014
                                                                                                             containerName:pithosContainer.name 
2015
                                                                                                                 blockSize:pithosContainer.blockSize 
2016
                                                                                                                   forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
2017
                                                                                                         missingBlockIndex:missingBlockIndex 
2018
                                                                                                            sharingAccount:nil];
2019
            newContainerRequest.delegate = self;
2020
            newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2021
            newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2022
            newContainerRequest.userInfo = objectRequest.userInfo;
2023
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
2024
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2025
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2026
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2027
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
2028
            [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2029
                [activityFacility updateActivity:activity 
2030
                                     withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", 
2031
                                                  newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
2032
                                      totalBytes:activity.totalBytes 
2033
                                    currentBytes:(activity.currentBytes + size)];
2034
            }];
2035
            [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2036
        }
2037
    } else {
2038
        [self requestFailed:objectRequest];
2039
    }
2040
    [pool drain];
2041
}
2042

    
2043
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
2044
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2045
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
2046
    NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
2047
    if (operation.isCancelled) {
2048
        [self requestFailed:containerRequest];
2049
    } else if (containerRequest.responseStatusCode == 202) {
2050
        ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
2051
        ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
2052
        PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
2053
        NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
2054
        NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
2055
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
2056
        if (operation.isCancelled) {
2057
            [self requestFailed:containerRequest];
2058
        } else if (missingBlockIndex == NSNotFound) {
2059
            NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
2060
            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
2061
                                                                                           containerName:pithosContainer.name 
2062
                                                                                              objectName:object.name 
2063
                                                                                             contentType:object.contentType 
2064
                                                                                               blockSize:pithosContainer.blockSize 
2065
                                                                                               blockHash:pithosContainer.blockHash
2066
                                                                                                 forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
2067
                                                                                           checkIfExists:NO 
2068
                                                                                                  hashes:&hashes 
2069
                                                                                          sharingAccount:nil];
2070
            newObjectRequest.delegate = self;
2071
            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2072
            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2073
            newObjectRequest.userInfo = containerRequest.userInfo;
2074
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2075
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
2076
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
2077
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
2078
            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2079
        } else {
2080
            if (newSyncRequested || syncLate || operation.isCancelled) {
2081
                [self requestFailed:containerRequest];
2082
            } else {
2083
                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
2084
                                                                                                                 containerName:pithosContainer.name
2085
                                                                                                                     blockSize:pithosContainer.blockSize
2086
                                                                                                                       forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
2087
                                                                                                             missingBlockIndex:missingBlockIndex 
2088
                                                                                                                sharingAccount:nil];
2089
                newContainerRequest.delegate = self;
2090
                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2091
                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2092
                newContainerRequest.userInfo = containerRequest.userInfo;
2093
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2094
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2095
                [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2096
                    [activityFacility updateActivity:activity 
2097
                                         withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", 
2098
                                                      newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
2099
                                          totalBytes:activity.totalBytes 
2100
                                        currentBytes:(activity.currentBytes + size)];
2101
                }];
2102
                [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2103
            }
2104
        }
2105
    } else {
2106
        [self requestFailed:containerRequest];
2107
    }
2108
    [pool drain];
2109
}
2110

    
2111
- (void)requestFailed:(ASIPithosRequest *)request {
2112
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2113
    NSOperation *operation = [request.userInfo objectForKey:@"operation"];
2114
    NSLog(@"Sync::request failed: %@", request.url);
2115
    if (operation.isCancelled) {
2116
        [pool drain];
2117
        return;        
2118
    }
2119
    if (request.isCancelled || newSyncRequested || syncLate) {
2120
        dispatch_async(dispatch_get_main_queue(), ^{
2121
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
2122
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
2123
        });
2124
        [self syncOperationFinishedWithSuccess:NO];
2125
        [pool drain];
2126
        return;
2127
    }
2128
    NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
2129
    if (retries > 0) {
2130
        ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
2131
        [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
2132
        [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
2133
    } else {
2134
        dispatch_async(dispatch_get_main_queue(), ^{
2135
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
2136
                              withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
2137
        });
2138
        [self syncOperationFinishedWithSuccess:NO];
2139
    }
2140
    [pool drain];
2141
}
2142

    
2143
@end