Run alerts from background threads in the main thread.
[pithos-macos] / pithos-macos / PithosSyncDaemon.m
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)createSyncDirectory:(NSString *)dirPath;
57 - (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName;
58 - (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName;
59
60 - (BOOL)moveToTempTrashFile:(NSString *)filePath 
61                 accountName:(NSString *)accountName 
62             pithosContainer:(ASIPithosContainer *)pithosContainer;
63 - (void)emptyTempTrash;
64 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
65
66 - (void)updateLocalStateWithObject:(ASIPithosObject *)object 
67                      localFilePath:(NSString *)filePath 
68                        accountName:(NSString *)accountName 
69                    pithosContainer:(ASIPithosContainer *)pithosContainer;
70 - (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
71                                    object:(ASIPithosObject *)object 
72                             localFilePath:(NSString *)filePath 
73                               accountName:(NSString *)accountName 
74                           pithosContainer:(ASIPithosContainer *)pithosContainer;
75 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest;
76 - (void)requestFailed:(ASIPithosRequest *)request;
77
78 - (void)syncOperationStarted;
79 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull;
80
81 @end
82
83 @implementation PithosSyncDaemon
84 @synthesize directoryPath, accountsDictionary, pithos;
85 @synthesize accountsNames, accountsPithosContainers;
86 @synthesize lastCompletedSync, remoteObjects, previousRemoteObjects, storedLocalObjectStates, currentLocalObjectStates;
87 @synthesize pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
88
89 #pragma mark -
90 #pragma Object Lifecycle
91
92 - (id)initWithDirectoryPath:(NSString *)aDirectoryPath 
93               pithosAccount:(PithosAccount *)aPithosAccount 
94          accountsDictionary:(NSDictionary *)anAccountsDictionary 
95             resetLocalState:(BOOL)resetLocalState {
96     if ((self = [super init])) {
97         directoryPath = [aDirectoryPath copy];
98         pithosAccount = [aPithosAccount retain];
99         self.accountsDictionary = anAccountsDictionary;
100         self.pithos = pithosAccount.pithos;        
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 (NSString *accountName in accountsNames) {
136         NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
137         if (!accountStoredLocalObjectStates) {
138             accountStoredLocalObjectStates = [NSMutableDictionary dictionary];
139             [storedLocalObjectStates setObject:accountStoredLocalObjectStates forKey:accountName];
140         }
141         for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
142             if (![accountStoredLocalObjectStates objectForKey:pithosContainer.name])
143                 [accountStoredLocalObjectStates setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
144         }
145     }
146     [pool drain];
147 }
148
149 - (void)resetLocalStateWithAll:(BOOL)all {
150     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
151     self.lastCompletedSync = nil;
152     if (all) {
153         self.storedLocalObjectStates = [NSMutableDictionary dictionary];
154         [self saveLocalState]; // Save an empty dictionary
155         [self loadLocalState]; // Load to populate with containers
156         [self saveLocalState]; // Save again
157         if (self.tempDownloadsDirPath)
158             [PithosUtilities removeContentsAtPath:self.tempDownloadsDirPath];
159     } else {
160         // Remove containers that don't interest us anymore and save
161         if (!storedLocalObjectStates)
162             [self loadLocalState];
163         for (NSString *accountName in storedLocalObjectStates) {
164             NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
165             NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
166             if (!containersDictionary) {
167                 if (self.tempDownloadsDirPath) {
168                     if ([accountName isEqualToString:@""]) {
169                         for (NSString *containerName in accountStoredLocalObjectStates) {
170                             [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]];
171                         }
172                     } else {
173                         [PithosUtilities removeContentsAtPath:[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared to me"] 
174                                                                stringByAppendingPathComponent:accountName]];
175                     }
176                 }
177                 [storedLocalObjectStates removeObjectForKey:accountName];
178             } else {
179                 // Check the account's containers
180                 for (NSString *containerName in accountStoredLocalObjectStates) {
181                     if (![containersDictionary objectForKey:containerName]) {
182                         if ([accountName isEqualToString:@""])
183                             [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]];
184                         else
185                             [PithosUtilities removeContentsAtPath:[[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared to me"] 
186                                                                     stringByAppendingPathComponent:accountName] 
187                                                                    stringByAppendingPathComponent:containerName]];
188                         [accountStoredLocalObjectStates removeObjectForKey:containerName];
189                     }
190                 }
191             }
192         }
193         [self saveLocalState];
194     }
195     [pool drain];
196 }
197
198 - (void)saveLocalState {
199     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
200     [NSKeyedArchiver archiveRootObject:storedLocalObjectStates toFile:self.pithosStateFilePath];
201     [pool drain];
202 }
203
204 - (void)resetDaemon {
205     @synchronized(self) {
206         if (!daemonActive)
207             return;
208     }
209     
210     [networkQueue reset];
211     [callbackQueue cancelAllOperations];
212     [callbackQueue setSuspended:YES];
213     [self emptyTempTrash];
214     
215     syncOperationCount = 0;
216     
217     @synchronized(self) {            
218         daemonActive = NO;
219     }
220 }
221
222 - (void)startDaemon {
223     @synchronized(self) {
224         if (daemonActive)
225             return;
226     }
227
228     // In the improbable case of leftover operations
229     [networkQueue reset];
230     [callbackQueue cancelAllOperations];
231     
232     syncOperationCount = 0;
233     newSyncRequested = NO;
234     syncIncomplete = NO;
235     syncLate = NO;
236     
237     [self loadLocalState];
238     
239     [networkQueue go];
240     [callbackQueue setSuspended:NO];
241     
242     @synchronized(self) {
243         daemonActive = YES;
244     }
245 }
246
247 - (void)dealloc {
248     [[NSNotificationCenter defaultCenter] removeObserver:self];
249     [self resetDaemon];
250     [callbackQueue release];
251     [networkQueue release];
252     [tempTrashDirPath release];
253     [tempDownloadsDirPath release];
254     [pithosStateFilePath release];
255     [currentLocalObjectStates release];
256     [storedLocalObjectStates release];
257     [previousRemoteObjects release];
258     [remoteObjects release];
259     [objects release];
260     [lastCompletedSync release];
261     [accountsPithosContainers release];
262     [accountsNames release];
263     [pithos release];
264     [accountsDictionary release];
265     [pithosAccount release];
266     [directoryPath release];
267     [super dealloc];
268 }
269
270 #pragma mark -
271 #pragma mark Observers
272
273 - (void)applicationWillTerminate:(NSNotification *)notification {
274     [self saveLocalState];
275 }
276
277 #pragma mark -
278 #pragma mark Properties
279
280 - (NSString *)pithosStateFilePath {
281     if (!pithosStateFilePath) {
282         pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
283                                  stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
284                                 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive", 
285                                                                 pithosAccount.uniqueName]] retain];
286         NSString *pithosStateFileDirPath = [pithosStateFilePath stringByDeletingLastPathComponent];
287         NSFileManager *fileManager = [NSFileManager defaultManager];
288         BOOL isDirectory;
289         BOOL fileExists = [fileManager fileExistsAtPath:pithosStateFileDirPath isDirectory:&isDirectory];
290         NSError *error = nil;
291         if (fileExists && !isDirectory)
292             [fileManager removeItemAtPath:pithosStateFileDirPath error:&error];
293         if (!error && !fileExists)
294             [fileManager createDirectoryAtPath:pithosStateFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
295         //if (error)
296         //  pithosStateFilePath = nil;
297         // XXX create a dir using mktmps?
298     }
299     return [[pithosStateFilePath copy] autorelease];
300 }
301
302 - (NSString *)tempDownloadsDirPath {
303     if (!tempDownloadsDirPath) {
304         tempDownloadsDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
305                                   stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
306                                  stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads", 
307                                                                  pithosAccount.uniqueName]] retain];
308         NSFileManager *fileManager = [NSFileManager defaultManager];
309         BOOL isDirectory;
310         BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
311         NSError *error = nil;
312         if (fileExists && !isDirectory)
313             [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
314         if (!error && !fileExists)
315             [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
316         //if (error)
317         //    tempDownloadsDirPath = nil;
318         // XXX create a dir using mktmps?
319     }
320     return tempDownloadsDirPath;    
321 }
322
323 - (NSString *)tempTrashDirPath {
324     if (!tempTrashDirPath) {
325         tempTrashDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
326                                   stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
327                                  stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash", 
328                                                                  pithosAccount.uniqueName]] retain];
329         NSFileManager *fileManager = [NSFileManager defaultManager];
330         BOOL isDirectory;
331         BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
332         NSError *error = nil;
333         if (fileExists && !isDirectory)
334             [fileManager removeItemAtPath:tempTrashDirPath error:&error];
335         if (!error && !fileExists)
336             [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
337         //if (error)
338         //    tempTrashDirPath = nil;
339         // XXX create a dir using mktmps?
340     }
341     return tempTrashDirPath;
342 }
343
344 - (void)setDirectoryPath:(NSString *)aDirectoryPath {
345     if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
346         [self resetDaemon];
347         [self resetLocalStateWithAll:YES];
348         [directoryPath release];
349         directoryPath = [aDirectoryPath copy];
350     }
351 }
352
353 - (void)setAccountsDictionary:(NSDictionary *)anAccountsDictionary {
354     if (anAccountsDictionary && ![anAccountsDictionary isEqualToDictionary:accountsDictionary]) {
355         BOOL reset = (accountsDictionary != nil);
356         if (reset)
357             [self resetDaemon];
358         
359         [accountsDictionary release];
360         accountsDictionary = [anAccountsDictionary copy];
361         
362         accountsCount = [accountsDictionary count];
363         self.accountsNames = [NSMutableArray arrayWithCapacity:accountsCount];
364         self.accountsPithosContainers = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
365         NSDictionary *containersDictionary = [accountsDictionary objectForKey:@""];
366         if (containersDictionary && [containersDictionary count]) {
367             NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
368             for (NSString *containerName in containersDictionary) {
369                 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
370                 pithosContainer.name = containerName;
371                 [pithosContainers addObject:pithosContainer];
372             }
373             [accountsNames addObject:@""];
374             [accountsPithosContainers setObject:pithosContainers forKey:@""];
375         }
376         for (NSString *accountName in accountsDictionary) {
377             NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
378             if (![accountsNames containsObject:accountName] && [containersDictionary count]) {
379                 NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
380                 for (NSString *containerName in containersDictionary) {
381                     ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
382                     pithosContainer.name = containerName;
383                     [pithosContainers addObject:pithosContainer];
384                 }
385                 [accountsNames addObject:accountName];
386                 [accountsPithosContainers setObject:pithosContainers forKey:accountName];
387             }
388         }
389
390         if (reset)
391             [self resetLocalStateWithAll:NO];
392     }
393 }
394
395 - (void)setPithos:(ASIPithos *)aPithos {
396     if (!pithos) {
397         pithos = [[ASIPithos pithos] retain];
398         pithos.authUser = [[aPithos.authUser copy] autorelease];
399         pithos.authToken = [[aPithos.authToken copy] autorelease];
400         pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
401         pithos.authURL = [[aPithos.authURL copy] autorelease];
402         pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
403     }
404     if (aPithos && 
405         (![aPithos.authUser isEqualToString:pithos.authUser] || 
406          ![aPithos.authToken isEqualToString:pithos.authToken] || 
407          ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
408         [self resetDaemon];
409         if (![aPithos.authUser isEqualToString:pithos.authUser] || 
410             ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
411             [self resetLocalStateWithAll:YES];
412         pithos.authUser = [[aPithos.authUser copy] autorelease];
413         pithos.authToken = [[aPithos.authToken copy] autorelease];
414         pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
415         pithos.authURL = [[aPithos.authURL copy] autorelease];
416         pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
417     }        
418 }
419
420 #pragma mark -
421 #pragma mark Helper Methods
422
423 - (BOOL)createSyncDirectory:(NSString *)dirPath {
424     NSFileManager *fileManager = [NSFileManager defaultManager];
425     BOOL isDirectory;
426     NSError *error = nil;
427     if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory]) {
428         if (![fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
429             dispatch_async(dispatch_get_main_queue(), ^{
430                 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
431                                                         message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", 
432                                                                  dirPath] 
433                                                           error:error];
434             });
435             return NO;
436         }
437     } else if (!isDirectory) {
438         dispatch_async(dispatch_get_main_queue(), ^{
439             [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
440                                                     message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", 
441                                                              dirPath] 
442                                                       error:nil];
443         });
444         return NO;
445     }
446     return YES;
447 }
448
449 - (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
450     if ([accountName isEqualToString:@""])
451         return [directoryPath stringByAppendingPathComponent:containerName];
452     else
453         return [[[directoryPath stringByAppendingPathComponent:@"shared to me"] 
454                  stringByAppendingPathComponent:accountName] 
455                 stringByAppendingPathComponent:containerName];
456 }
457
458 - (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
459     if ([accountName isEqualToString:@""])
460         return containerName;
461     else
462         return [[[NSString stringWithString:@"shared to me"] 
463                  stringByAppendingPathComponent:accountName] 
464                 stringByAppendingPathComponent:containerName];    
465 }
466
467 #pragma mark -
468 #pragma mark Sync
469
470 - (void)syncOperationStarted {
471     @synchronized(self) {
472         syncOperationCount++;
473     }
474 }
475
476 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
477     @synchronized(self) {
478         if (!operationSuccessfull)
479             syncIncomplete = YES;
480         if (syncOperationCount == 0) {
481             // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
482             NSLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
483             return;
484         }
485         syncOperationCount--;
486         if (syncOperationCount == 0) {
487             if (!syncIncomplete) {
488                 self.lastCompletedSync = [NSDate date];
489                 dispatch_async(dispatch_get_main_queue(), ^{
490                     [activityFacility startAndEndActivityWithType:PithosActivityOther 
491                                                           message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync] 
492                                                     pithosAccount:pithosAccount];
493                     
494                 });
495             }
496             [self emptyTempTrash];
497             if (newSyncRequested && daemonActive)
498                 [self sync];
499         }
500     }
501 }
502
503 - (BOOL)isSyncing {
504     @synchronized(self) {
505         return ((syncOperationCount > 0) && daemonActive);
506     }
507 }
508
509 - (void)syncLate {
510     @synchronized(self) {
511         if ([self isSyncing])
512             syncLate = YES;
513     }
514 }
515
516 - (void)sync {
517     @synchronized(self) {
518         if ([self isSyncing]) {
519             // If at least one operation is running return
520             newSyncRequested = YES;
521             return;
522         } else if (daemonActive && accountsCount) {
523             // The first operation is the server listing
524             [self syncOperationStarted];
525             newSyncRequested = NO;
526             syncIncomplete = NO;
527             syncLate = NO;
528         } else {
529             return;
530         }
531     }
532
533     if (![self createSyncDirectory:directoryPath]) {
534         [self syncOperationFinishedWithSuccess:NO];
535         return;
536     }
537     for (NSString *accountName in accountsNames) {
538         for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
539             if (![self createSyncDirectory:[self dirPathForAccount:accountName container:pithosContainer.name]]) {
540                 [self syncOperationFinishedWithSuccess:NO];
541                 return;
542             }
543         }
544     }
545
546     self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
547     for (NSString *accountName in accountsNames) {
548         [remoteObjects setObject:[NSMutableDictionary dictionary] forKey:accountName];
549     }
550     accountsIndex = 0;
551     containersIndex = 0;
552     NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
553     ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
554     ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
555                                                                                             containerName:pithosContainer.name 
556                                                                                                     limit:0 
557                                                                                                    marker:nil 
558                                                                                                    prefix:nil 
559                                                                                                 delimiter:nil 
560                                                                                                      path:nil 
561                                                                                                      meta:nil 
562                                                                                                    shared:NO 
563                                                                                                     until:nil 
564                                                                                           ifModifiedSince:pithosContainer.lastModified];
565     if (![accountName isEqualToString:@""])
566         [containerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
567     containerRequest.delegate = self;
568     containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
569     containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
570     PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther 
571                                                                message:@"Sync: Getting server listing" 
572                                                          pithosAccount:pithosAccount];
573     containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
574                                  activity, @"activity", 
575                                  @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage", 
576                                  @"Sync: Getting server listing (failed)", @"failedActivityMessage", 
577                                  @"Sync: Getting server listing (finished)", @"finishedActivityMessage", 
578                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
579                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
580                                  NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector", 
581                                  NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector", 
582                                  nil];
583     [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
584 }
585
586 - (void)emptyTempTrash {
587     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
588     NSString *trashDirPath = self.tempTrashDirPath;
589     if (trashDirPath) {
590         NSFileManager *fileManager = [NSFileManager defaultManager];
591         NSError *error = nil;
592 //        NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
593         NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
594         if (error) {
595             dispatch_async(dispatch_get_main_queue(), ^{
596                 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
597                                                         message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath] 
598                                                           error:error];
599             });
600             [pool drain];
601             return;
602         }
603         if ([subPaths count]) {
604 //            NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
605 //            for (NSString *subPath in subPaths) {
606 //                [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
607 //            }
608 //            [self syncOperationStarted];
609 //            [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
610 //                if (error) {
611 //                    dispatch_async(dispatch_get_main_queue(), ^{
612 //                        [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error" 
613 //                                                                message:@"Cannot move files to Trash" 
614 //                                                                  error:error];
615 //                    });
616 //                }
617 //                [self syncOperationFinishedWithSuccess:YES];
618 //            }];
619             for (NSString *subPath in subPaths) {
620                 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
621                 error = nil;
622                 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
623                     dispatch_async(dispatch_get_main_queue(), ^{
624                         [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
625                                                                 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
626                                                                   error:error];
627                     });
628             }
629         }
630     }
631     [pool drain];
632 }
633
634 - (BOOL)moveToTempTrashFile:(NSString *)filePath 
635                 accountName:(NSString *)accountName 
636             pithosContainer:(ASIPithosContainer *)pithosContainer {
637     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
638     if (!self.tempTrashDirPath) {
639         [pool drain];
640         return NO;
641     }
642     NSFileManager *fileManager = [NSFileManager defaultManager];
643     BOOL isDirectory;
644     BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
645     NSError *error = nil;
646     NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
647     NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath withString:self.tempTrashDirPath];
648     NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
649     if (fileExists && isDirectory) {
650         NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
651         if (error) {
652             dispatch_async(dispatch_get_main_queue(), ^{
653                 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
654                                                         message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath] 
655                                                           error:error];
656             });
657             [pool drain];
658             return NO;
659         }
660         if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
661             dispatch_async(dispatch_get_main_queue(), ^{
662                 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
663                                                         message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
664                                                           error:error];
665             });
666             [pool drain];
667             return NO;
668         }
669         if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
670             dispatch_async(dispatch_get_main_queue(), ^{
671                 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
672                                                         message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
673                                                                  filePath, newFilePath] 
674                                                           error:error];
675             });
676             [pool drain];
677             return NO;
678         }
679         PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
680         if (currentState) {
681             currentState.filePath = newFilePath;
682             [currentLocalObjectStates setObject:currentState forKey:newFilePath];
683             [currentLocalObjectStates removeObjectForKey:filePath];        
684         } else {
685             [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
686                                                                                        blockHash:pithosContainer.blockHash 
687                                                                                        blockSize:pithosContainer.blockSize] 
688                                          forKey:newFilePath];
689         }
690         for (NSString *subPath in subPaths) {
691             NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
692             NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath 
693                                                                               withString:self.tempTrashDirPath];
694             currentState = [currentLocalObjectStates objectForKey:subFilePath];
695             if (currentState) {
696                 currentState.filePath = newSubFilePath;
697                 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
698                 [currentLocalObjectStates removeObjectForKey:subFilePath];
699             } else {
700                 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath 
701                                                                                            blockHash:pithosContainer.blockHash 
702                                                                                            blockSize:pithosContainer.blockSize] 
703                                              forKey:newSubFilePath];
704             }        
705         }
706     } else if (fileExists && !isDirectory) {
707         if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
708             dispatch_async(dispatch_get_main_queue(), ^{
709                 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
710                                                         message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
711                                                           error:error];
712             });
713             [pool drain];
714             return NO;
715         }
716         if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
717             dispatch_async(dispatch_get_main_queue(), ^{
718                 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
719                                                         message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
720                                                                  filePath, newFilePath] 
721                                                           error:error];
722             });
723             [pool drain];
724             return NO;
725         }
726         PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
727         if (currentState) {
728             currentState.filePath = newFilePath;
729             [currentLocalObjectStates setObject:currentState forKey:newFilePath];
730             [currentLocalObjectStates removeObjectForKey:filePath];        
731         } else {
732             [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
733                                                                                        blockHash:pithosContainer.blockHash 
734                                                                                        blockSize:pithosContainer.blockSize] 
735                                          forKey:newFilePath];
736         }
737     }
738     [pool drain];
739     return YES;
740 }
741
742 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
743     if ([hash length] != 64)
744         return NO;
745     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
746     PithosLocalObjectState *localState;
747     NSFileManager *fileManager = [NSFileManager defaultManager];
748     BOOL isDirectory;
749     NSError *error = nil;
750     for (NSString *localFilePath in currentLocalObjectStates) {
751         localState = [currentLocalObjectStates objectForKey:localFilePath];
752         if (!localState.isDirectory && [hash isEqualToString:localState.hash] && 
753             [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
754             if ([localFilePath hasPrefix:directoryPath]) {
755                 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
756                     dispatch_async(dispatch_get_main_queue(), ^{
757                         [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error" 
758                                                                 message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'", 
759                                                                          localFilePath, filePath] 
760                                                                   error:error];
761                     });
762                 } else {
763                     [pool drain];
764                     return YES;
765                 }
766             } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
767                 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
768                     dispatch_async(dispatch_get_main_queue(), ^{
769                         [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
770                                                                 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
771                                                                          localFilePath, filePath] 
772                                                                   error:error];
773                     });
774                 } else {
775                     localState.filePath = filePath;
776                     [currentLocalObjectStates setObject:localState forKey:filePath];
777                     [currentLocalObjectStates removeObjectForKey:localFilePath];
778                     [pool drain];
779                     return YES;
780                 }
781             }
782         }
783     }
784     [pool drain];
785     return NO;
786 }
787
788 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
789                      localFilePath:(NSString *)filePath 
790                        accountName:(NSString *)accountName 
791                    pithosContainer:(ASIPithosContainer *)pithosContainer {
792     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
793     NSFileManager *fileManager = [NSFileManager defaultManager];
794     NSError *error;
795     BOOL isDirectory;
796     BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
797     NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
798     NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName] 
799                                                              objectForKey:pithosContainer.name];
800     PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
801     // Remote updated info
802     NSError *remoteError;
803     BOOL remoteIsDirectory;
804     BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos 
805                                                       containerName:pithosContainer.name 
806                                                          objectName:object.name 
807                                                               error:&remoteError 
808                                                         isDirectory:&remoteIsDirectory 
809                                                      sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
810     if (!object || !object.objectHash) {
811         // Delete local object
812         if (![accountName isEqualToString:@""]) {
813             // If "shared to me" skip
814             [pool drain];
815             return;
816         }
817         if (remoteObjectExists) {
818             // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
819             syncIncomplete = YES;
820         }
821         NSLog(@"Sync::delete local object: %@", filePath);
822         if (!fileExists || [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer]) {
823             dispatch_async(dispatch_get_main_queue(), ^{
824                 [activityFacility startAndEndActivityWithType:PithosActivityOther 
825                                                       message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)", 
826                                                                pithosContainer.name, object.name] 
827                                                 pithosAccount:pithosAccount];
828             });
829             [containerStoredLocalObjectStates removeObjectForKey:object.name];
830             [self saveLocalState];
831         }
832     } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
833         // Create local directory object
834         if (!remoteObjectExists || !remoteIsDirectory) {
835             // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
836             syncIncomplete = YES;
837             [pool drain];
838             return;
839         }
840         NSLog(@"Sync::create local directory object: %@", filePath);
841         BOOL directoryCreated = NO;
842         if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
843             NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
844             error = nil;
845             if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
846                 dispatch_async(dispatch_get_main_queue(), ^{
847                     [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
848                                                             message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath] 
849                                                               error:error];
850                 });
851             } else {
852                 directoryCreated = YES;
853                 storedState.filePath = filePath;
854                 storedState.isDirectory = YES;
855                 [self saveLocalState];
856             }
857         } else {
858             NSLog(@"Sync::local directory object exists: %@", filePath);
859             directoryCreated = YES;
860         }
861         if (directoryCreated)
862             dispatch_async(dispatch_get_main_queue(), ^{
863                 [activityFacility startAndEndActivityWithType:PithosActivityOther 
864                                                       message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)", 
865                                                                [self relativeDirPathForAccount:accountName container:pithosContainer.name], 
866                                                                object.name] 
867                                                 pithosAccount:pithosAccount];
868             });
869     } else if (object.bytes == 0) {
870         // Create local object with zero length
871         if (!remoteObjectExists || remoteIsDirectory) {
872             // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
873             syncIncomplete = YES;
874             [pool drain];
875             return;
876         }
877         NSLog(@"Sync::create local zero length object: %@", filePath);
878         BOOL fileCreated = NO;
879         if (!fileExists || 
880             ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && 
881              [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
882             NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
883             // Create directory of the file, if it doesn't exist
884             error = nil;
885             if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
886                 dispatch_async(dispatch_get_main_queue(), ^{
887                     [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
888                                                             message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
889                                                               error:error];
890                 });
891             }
892             error = nil;
893             if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
894                 dispatch_async(dispatch_get_main_queue(), ^{
895                     [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error" 
896                                                             message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath] 
897                                                               error:error];
898                 });
899             } else {
900                 fileCreated = YES;
901                 storedState.filePath = filePath;
902                 storedState.hash = object.objectHash;
903                 storedState.tmpFilePath = nil;
904                 [self saveLocalState];
905             }
906         } else {
907             NSLog(@"Sync::local zero length object exists: %@", filePath);
908             fileCreated = YES;
909         }
910         if (fileCreated)
911             dispatch_async(dispatch_get_main_queue(), ^{
912                 [activityFacility startAndEndActivityWithType:PithosActivityOther 
913                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", 
914                                                                [self relativeDirPathForAccount:accountName container:pithosContainer.name], 
915                                                                object.name] 
916                                                 pithosAccount:pithosAccount];
917             });
918     } else if (storedState.tmpFilePath == nil) {
919         // Create new local object
920         if (!remoteObjectExists || remoteIsDirectory) {
921             // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
922             syncIncomplete = YES;
923             [pool drain];
924             return;
925         }
926         // Create directory of the file, if it doesn't exist
927         error = nil;
928         if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
929             dispatch_async(dispatch_get_main_queue(), ^{
930                 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
931                                                         message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
932                                                           error:error];
933             });
934         }
935         // Check first if a local copy exists
936         if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
937             storedState.filePath = filePath;
938             storedState.hash = object.objectHash;
939             [self saveLocalState];
940             dispatch_async(dispatch_get_main_queue(), ^{
941                 [activityFacility startAndEndActivityWithType:PithosActivityOther 
942                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", 
943                                                                [self relativeDirPathForAccount:accountName container:pithosContainer.name], 
944                                                                object.name] 
945                                                 pithosAccount:pithosAccount];
946             });
947         } else {
948             [self syncOperationStarted];
949             __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
950                                                                                                 containerName:pithosContainer.name 
951                                                                                                        object:object 
952                                                                                                    blockIndex:0 
953                                                                                                     blockSize:pithosContainer.blockSize];
954             if (![accountName isEqualToString:@""])
955                 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
956             objectRequest.delegate = self;
957             objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
958             objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
959             NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'", 
960                                        [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
961             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
962                                                                        message:[messagePrefix stringByAppendingString:@" (0%%)"] 
963                                                                     totalBytes:object.bytes 
964                                                                   currentBytes:0 
965                                                                  pithosAccount:pithosAccount];
966             dispatch_async(dispatch_get_main_queue(), ^{
967                 [activityFacility updateActivity:activity withMessage:activity.message];
968             });
969             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
970                                       accountName, @"accountName", 
971                                       pithosContainer, @"pithosContainer", 
972                                       object, @"pithosObject", 
973                                       messagePrefix, @"messagePrefix", 
974                                       [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks", 
975                                       [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex", 
976                                       filePath, @"filePath", 
977                                       activity, @"activity", 
978                                       [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
979                                       [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
980                                       [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage", 
981                                       [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
982                                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
983                                       NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector", 
984                                       NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
985                                       nil];
986             [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
987                 [activityFacility updateActivity:activity 
988                                      withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", 
989                                                   messagePrefix, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
990                                       totalBytes:activity.totalBytes 
991                                     currentBytes:(activity.currentBytes + size)];
992             }];
993             [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
994         }
995     } else {
996         // Resume local object download
997         if (!remoteObjectExists || remoteIsDirectory) {
998             // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
999             syncIncomplete = YES;
1000             [pool drain];
1001             return;
1002         }
1003         // Create directory of the file, if it doesn't exist
1004         error = nil;
1005         if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
1006             dispatch_async(dispatch_get_main_queue(), ^{
1007                 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
1008                                                         message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
1009                                                           error:error];
1010             });
1011         }
1012         // Check first if a local copy exists
1013         if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
1014             storedState.filePath = filePath;
1015             storedState.hash = object.objectHash;
1016             // Delete incomplete temp download
1017             storedState.tmpFilePath = nil;
1018             [self saveLocalState];
1019             dispatch_async(dispatch_get_main_queue(), ^{
1020                 [activityFacility startAndEndActivityWithType:PithosActivityOther 
1021                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", 
1022                                                                [self relativeDirPathForAccount:accountName container:pithosContainer.name], 
1023                                                                object.name] 
1024                                                 pithosAccount:pithosAccount];
1025             });
1026         } else {
1027             [self syncOperationStarted];
1028             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos 
1029                                                                                              containerName:pithosContainer.name 
1030                                                                                                 objectName:object.name];
1031             if (![accountName isEqualToString:@""])
1032                 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1033             objectRequest.delegate = self;
1034             objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1035             objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1036             NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'", 
1037                                        [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1038             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
1039                                                                        message:[messagePrefix stringByAppendingString:@" (0%%)"] 
1040                                                                     totalBytes:object.bytes 
1041                                                                   currentBytes:0 
1042                                                                  pithosAccount:pithosAccount];
1043             dispatch_async(dispatch_get_main_queue(), ^{
1044                 [activityFacility updateActivity:activity withMessage:activity.message];
1045             });
1046             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1047                                       accountName, @"accountName", 
1048                                       pithosContainer, @"pithosContainer", 
1049                                       object, @"pithosObject", 
1050                                       messagePrefix, @"messagePrefix", 
1051                                       filePath, @"filePath", 
1052                                       activity, @"activity", 
1053                                       [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1054                                       [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1055                                       [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage", 
1056                                       [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1057                                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
1058                                       NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector", 
1059                                       NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1060                                       nil];
1061             [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1062         }
1063     }
1064     [pool drain];
1065 }
1066
1067 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
1068                                   object:(ASIPithosObject *)object 
1069                            localFilePath:(NSString *)filePath 
1070                              accountName:(NSString *)accountName 
1071                          pithosContainer:(ASIPithosContainer *)pithosContainer {
1072     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1073     [self syncOperationStarted];
1074     NSFileManager *fileManager = [NSFileManager defaultManager];
1075     BOOL isDirectory;
1076     BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1077     if (currentState.isDirectory) {
1078         // Create remote directory object
1079         if (![accountName isEqualToString:@""]) {
1080             if (!object.allowedTo) {
1081                 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1082                 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1083                 while ([objectAncestorName length] && !object.allowedTo) {
1084                     object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1085                     objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1086                 }
1087             }
1088             if (![object.allowedTo isEqualToString:@"write"]) {
1089                 // If read-only "shared to me" skip
1090                 [self syncOperationFinishedWithSuccess:YES];
1091                 [pool drain];
1092                 return;
1093             }
1094         }
1095         if (!fileExists || !isDirectory) {
1096             // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
1097             [self syncOperationFinishedWithSuccess:NO];
1098             [pool drain];
1099             return;
1100         }
1101         ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
1102                                                                                            containerName:pithosContainer.name 
1103                                                                                               objectName:object.name 
1104                                                                                                     eTag:nil 
1105                                                                                              contentType:@"application/directory" 
1106                                                                                          contentEncoding:nil 
1107                                                                                       contentDisposition:nil 
1108                                                                                                 manifest:nil 
1109                                                                                                  sharing:nil 
1110                                                                                                 isPublic:ASIPithosObjectRequestPublicIgnore 
1111                                                                                                 metadata:nil 
1112                                                                                                     data:[NSData data]];
1113         if (![accountName isEqualToString:@""])
1114             [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1115         objectRequest.delegate = self;
1116         objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1117         objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1118         NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Creating directory '%@/%@'", 
1119                                    [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1120         PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
1121                                                                    message:messagePrefix 
1122                                                              pithosAccount:pithosAccount];
1123         dispatch_async(dispatch_get_main_queue(), ^{
1124             [activityFacility updateActivity:activity withMessage:activity.message];
1125         });
1126         objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1127                                   accountName, @"accountName", 
1128                                   pithosContainer, @"pithosContainer", 
1129                                   object, @"pithosObject", 
1130                                   messagePrefix, @"messagePrefix", 
1131                                   activity, @"activity", 
1132                                   [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1133                                   [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1134                                   [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1135                                   [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1136                                   [NSNumber numberWithUnsignedInteger:10], @"retries", 
1137                                   NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
1138                                   NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1139                                   nil];
1140         [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1141     } else if (![currentState exists]) {
1142         // Delete remote object
1143         if (![accountName isEqualToString:@""]) {
1144             // If "shared to me" skip
1145             [self syncOperationFinishedWithSuccess:YES];
1146             [pool drain];
1147             return;
1148         }
1149         if (fileExists) {
1150             // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
1151             syncIncomplete = YES;
1152         }
1153         if ([pithosContainer.name isEqualToString:@"trash"]) {
1154             // Delete
1155             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos 
1156                                                                                             containerName:pithosContainer.name 
1157                                                                                                objectName:object.name];
1158             objectRequest.delegate = self;
1159             objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1160             objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1161             NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Deleting '%@/%@'", 
1162                                        [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1163             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
1164                                                                        message:messagePrefix 
1165                                                                  pithosAccount:pithosAccount];
1166             dispatch_async(dispatch_get_main_queue(), ^{
1167                 [activityFacility updateActivity:activity withMessage:activity.message];
1168             });
1169             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1170                                       accountName, @"accountName", 
1171                                       pithosContainer, @"pithosContainer", 
1172                                       object, @"pithosObject", 
1173                                       messagePrefix, @"messagePrefix", 
1174                                       activity, @"activity", 
1175                                       [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1176                                       [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1177                                       [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1178                                       [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1179                                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
1180                                       NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", 
1181                                       NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1182                                       nil];
1183             [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1184         } else {
1185             // Move to container trash
1186             NSString *safeName;
1187             if ([PithosUtilities isContentTypeDirectory:object.contentType])
1188                 safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
1189             else
1190                 safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
1191             if (safeName) {
1192                 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
1193                                                                                        containerName:pithosContainer.name 
1194                                                                                           objectName:object.name 
1195                                                                             destinationContainerName:@"trash" 
1196                                                                                destinationObjectName:safeName 
1197                                                                                        checkIfExists:NO];
1198                 if (objectRequest) {
1199                     objectRequest.delegate = self;
1200                     objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1201                     objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1202                     NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'", 
1203                                                [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1204                     PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
1205                                                                                message:messagePrefix 
1206                                                                          pithosAccount:pithosAccount];
1207                     dispatch_async(dispatch_get_main_queue(), ^{
1208                         [activityFacility updateActivity:activity withMessage:activity.message];
1209                     });
1210                     objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1211                                               accountName, @"accountName", 
1212                                               pithosContainer, @"pithosContainer", 
1213                                               object, @"pithosObject", 
1214                                               messagePrefix, @"messagePrefix", 
1215                                               activity, @"activity", 
1216                                               [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1217                                               [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1218                                               [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", 
1219                                               [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1220                                               [NSNumber numberWithUnsignedInteger:10], @"retries", 
1221                                               NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector", 
1222                                               NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1223                                               nil];
1224                     [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1225                 } else {
1226                     [self syncOperationFinishedWithSuccess:NO];
1227                 }
1228             } else {
1229                 [self syncOperationFinishedWithSuccess:NO];
1230             }
1231         }
1232     } else {
1233         // Upload file to remote object
1234         if (![accountName isEqualToString:@""]) {
1235             if (!object.allowedTo) {
1236                 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1237                 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1238                 while ([objectAncestorName length] && !object.allowedTo) {
1239                     object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1240                     objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1241                 }
1242             }
1243             if (![object.allowedTo isEqualToString:@"write"]) {
1244                 // If read-only "shared to me" skip
1245                 [self syncOperationFinishedWithSuccess:YES];
1246                 [pool drain];
1247                 return;
1248             }
1249         }
1250         if (!fileExists || isDirectory) {
1251             // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
1252             [self syncOperationFinishedWithSuccess:NO];
1253             [pool drain];
1254             return;
1255         }
1256         NSError *error = nil;
1257         object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1258         if (object.contentType == nil)
1259             object.contentType = @"application/octet-stream";
1260         if (error)
1261             NSLog(@"contentType detection error: %@", error);
1262         NSArray *hashes = nil;
1263         ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
1264                                                                                     containerName:pithosContainer.name 
1265                                                                                        objectName:object.name 
1266                                                                                       contentType:object.contentType 
1267                                                                                         blockSize:pithosContainer.blockSize 
1268                                                                                         blockHash:pithosContainer.blockHash 
1269                                                                                           forFile:filePath 
1270                                                                                     checkIfExists:NO 
1271                                                                                            hashes:&hashes 
1272                                                                                    sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
1273         if (objectRequest) {
1274             objectRequest.delegate = self;
1275             objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1276             objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1277             NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Uploading '%@/%@'", 
1278                                        [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1279             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
1280                                                                        message:[messagePrefix stringByAppendingString:@" (0%%)"] 
1281                                                                     totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1282                                                                   currentBytes:0 
1283                                                                  pithosAccount:pithosAccount];
1284             dispatch_async(dispatch_get_main_queue(), ^{
1285                 [activityFacility updateActivity:activity withMessage:activity.message];
1286             });
1287             [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1288              [NSDictionary dictionaryWithObjectsAndKeys:
1289               accountName, @"accountName", 
1290               pithosContainer, @"pithosContainer", 
1291               object, @"pithosObject", 
1292               messagePrefix, @"messagePrefix", 
1293               filePath, @"filePath", 
1294               hashes, @"hashes", 
1295               [NSNumber numberWithUnsignedInteger:10], @"iteration", 
1296               activity, @"activity", 
1297               [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", 
1298               [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", 
1299               [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage", 
1300               [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1301               [NSNumber numberWithUnsignedInteger:10], @"retries", 
1302               NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
1303               NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1304               nil]];
1305             [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1306         } else {
1307             [self syncOperationFinishedWithSuccess:NO];
1308         }
1309     }
1310     [pool drain];
1311 }
1312
1313 #pragma mark -
1314 #pragma mark ASIHTTPRequestDelegate
1315
1316 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1317     // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1318     NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1319                                                                              selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) 
1320                                                                                object:request] autorelease];
1321     operation.completionBlock = ^{
1322         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1323         if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1324             dispatch_async(dispatch_get_main_queue(), ^{
1325                 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1326                                   withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1327             });
1328             [self syncOperationFinishedWithSuccess:NO];
1329         }
1330         [pool drain];
1331     };
1332     [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1333     [callbackQueue addOperation:operation];
1334 }
1335
1336 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1337     if (request.isCancelled) {
1338         // Request has been cancelled 
1339         // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1340         [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1341                    withObject:request];
1342     } else {
1343         // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1344         NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1345                                                                                  selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1346                                                                                    object:request] autorelease];
1347         operation.completionBlock = ^{
1348             NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1349             if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1350                 dispatch_async(dispatch_get_main_queue(), ^{
1351                     [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1352                                       withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1353                 });
1354                 [self syncOperationFinishedWithSuccess:NO];
1355             }
1356             [pool drain];
1357         };
1358         [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1359         [callbackQueue addOperation:operation];
1360     }
1361 }
1362
1363 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1364     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1365     NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1366     NSLog(@"Sync::list request finished: %@", containerRequest.url);
1367     if (operation.isCancelled) {
1368         [self listRequestFailed:containerRequest];
1369     } else if ((containerRequest.responseStatusCode == 200) || 
1370                (containerRequest.responseStatusCode == 304) || 
1371                (containerRequest.responseStatusCode == 403)) {
1372         NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
1373         ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1374         if (containerRequest.responseStatusCode == 200) {
1375             NSArray *someObjects = [containerRequest objects];
1376             if (objects == nil) {
1377                 objects = [[NSMutableArray alloc] initWithArray:someObjects];
1378             } else {
1379                 [objects addObjectsFromArray:someObjects];
1380             }
1381             if ([someObjects count] < 10000) {
1382                 pithosContainer.blockHash = [containerRequest blockHash];
1383                 pithosContainer.blockSize = [containerRequest blockSize];
1384                 pithosContainer.lastModified = [containerRequest lastModified];
1385                 NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1386                 for (ASIPithosObject *object in objects) {
1387                     [containerRemoteObjects setObject:object forKey:object.name];
1388                 }
1389                 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1390                 [objects release];
1391                 objects = nil;
1392             } else {
1393                 // Do an additional request to fetch more objects
1394                 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
1395                                                                                                            containerName:pithosContainer.name 
1396                                                                                                                    limit:0 
1397                                                                                                                   marker:[[someObjects lastObject] name] 
1398                                                                                                                   prefix:nil 
1399                                                                                                                delimiter:nil 
1400                                                                                                                     path:nil 
1401                                                                                                                     meta:nil 
1402                                                                                                                   shared:NO 
1403                                                                                                                    until:nil 
1404                                                                                                          ifModifiedSince:pithosContainer.lastModified];
1405                 if (![accountName isEqualToString:@""])
1406                     [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1407                 newContainerRequest.delegate = self;
1408                 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1409                 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1410                 newContainerRequest.userInfo = containerRequest.userInfo;
1411                 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1412                 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1413                 [pool drain];
1414                 return;
1415             }
1416         } else if (containerRequest.responseStatusCode == 304) {
1417             NSMutableDictionary *containerRemoteObjects = [[previousRemoteObjects objectForKey:accountName] 
1418                                                            objectForKey:pithosContainer.name];
1419             if (containerRemoteObjects) 
1420                 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1421         } else if (containerRequest.responseStatusCode == 403) {
1422             [[remoteObjects objectForKey:accountName] setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
1423         }
1424         
1425         containersIndex++;
1426         if (containersIndex == [[accountsPithosContainers objectForKey:accountName] count]) {
1427             accountsIndex++;
1428             containersIndex = 0;
1429         }
1430         if (accountsIndex < accountsCount) {
1431             accountName = [accountsNames objectAtIndex:accountsIndex];
1432             pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1433             // Do a request for the next container
1434             ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
1435                                                                                                        containerName:pithosContainer.name 
1436                                                                                                                limit:0 
1437                                                                                                               marker:nil 
1438                                                                                                               prefix:nil 
1439                                                                                                            delimiter:nil 
1440                                                                                                                 path:nil 
1441                                                                                                                 meta:nil 
1442                                                                                                               shared:NO 
1443                                                                                                                until:nil 
1444                                                                                                      ifModifiedSince:pithosContainer.lastModified];
1445             if (![accountName isEqualToString:@""])
1446                 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1447             newContainerRequest.delegate = self;
1448             newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1449             newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1450             newContainerRequest.userInfo = containerRequest.userInfo;
1451             [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1452             [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1453             [pool drain];
1454             return;
1455         }
1456         self.previousRemoteObjects = remoteObjects;
1457         // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
1458         
1459         if (operation.isCancelled) {
1460             [self listRequestFailed:containerRequest];
1461             [pool drain];
1462             return;
1463         }
1464
1465         dispatch_async(dispatch_get_main_queue(), ^{
1466             [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1467                               withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1468         });
1469         NSFileManager *fileManager = [NSFileManager defaultManager];
1470         
1471         // Compute current state of legal existing local objects 
1472         // and add an empty stored state for legal new local objects since last sync
1473         self.currentLocalObjectStates = [NSMutableDictionary dictionary];
1474         for (NSString *accountName in accountsNames) {
1475             for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1476                 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1477                 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1478                 BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1479                 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName] 
1480                                                                          objectForKey:pithosContainer.name];
1481                 NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath];
1482                 for (NSString *objectName in dirEnumerator) {
1483                     objectName = [objectName precomposedStringWithCanonicalMapping];
1484                     if (operation.isCancelled) {
1485                         operation.completionBlock = nil;
1486                         [self saveLocalState];
1487                         [self syncOperationFinishedWithSuccess:NO];
1488                         [pool drain];
1489                         return;
1490                     }
1491
1492                     NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1493                     NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1494                     BOOL isDirectory;
1495                     BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1496                     if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) {
1497                         dispatch_async(dispatch_get_main_queue(), ^{
1498                             [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error" 
1499                                                                     message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.", 
1500                                                                              containerDirectoryPath, pithosAccount.name] 
1501                                                                       error:nil];
1502                         });
1503                         pithosAccount.syncActive = NO;
1504                         return;
1505                     } else if (fileExists) {
1506                         NSArray *pathComponents = [objectName pathComponents];
1507                         if ([pathComponents count] == 1) {
1508                             if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) {
1509                                 // Skip excluded directory and its descendants, or root file with same name
1510                                 if (isDirectory)
1511                                     [dirEnumerator skipDescendants];
1512                                 // Remove stored state if any
1513                                 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1514                                 continue;
1515                             } else if (!isDirectory && containerExludeRootFiles) {
1516                                 // Skip excluded root file
1517                                 // Remove stored state if any
1518                                 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1519                                 continue;
1520                             }
1521                         }
1522                         // Include local object
1523                         PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
1524                         if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1525                             // New or modified existing local object, compute current state
1526                             if (!storedLocalObjectState)
1527                                 // For new local object, also create empty stored state
1528                                 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1529                             [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath 
1530                                                                                                        blockHash:pithosContainer.blockHash 
1531                                                                                                        blockSize:pithosContainer.blockSize] 
1532                                                          forKey:filePath];
1533                         } else {
1534                             // Local object hasn't changed, set stored state also to current
1535                             [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1536                         }
1537                     }
1538                 }
1539                 [self saveLocalState];
1540             }
1541         }
1542         
1543         if (operation.isCancelled) {
1544             operation.completionBlock = nil;
1545             [self syncOperationFinishedWithSuccess:NO];
1546             [pool drain];
1547             return;
1548         }
1549
1550         // Add an empty stored state for legal new remote objects since last sync
1551         for (NSString *accountName in accountsNames) {
1552             for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1553                 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1554                 BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1555                 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName] 
1556                                                                          objectForKey:pithosContainer.name];
1557                 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1558                 for (NSString *objectName in containerRemoteObjects) {
1559                     if (operation.isCancelled) {
1560                         operation.completionBlock = nil;
1561                         [self saveLocalState];
1562                         [self syncOperationFinishedWithSuccess:NO];
1563                         [pool drain];
1564                         return;
1565                     }
1566
1567                     ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName];
1568                     NSString *localObjectName;
1569                     if ([object.name hasSuffix:@"/"])
1570                         localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1571                     else
1572                         localObjectName = [NSString stringWithString:object.name];
1573                     NSArray *pathComponents = [localObjectName pathComponents];
1574                     if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1575                         // Skip excluded directory object and its descendants, or root file object with same name
1576                         // Remove stored state if any
1577                         [containerStoredLocalObjectStates removeObjectForKey:object.name];
1578                         continue;
1579                     } else if (containerExludeRootFiles && 
1580                                ([pathComponents count] == 1) && 
1581                                ![PithosUtilities isContentTypeDirectory:object.contentType]) {
1582                         // Skip root file object
1583                         // Remove stored state if any
1584                         [containerStoredLocalObjectStates removeObjectForKey:object.name];                    
1585                         continue;
1586                     }
1587                     if (![containerStoredLocalObjectStates objectForKey:object.name])
1588                         [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name];
1589                 }
1590                 [self saveLocalState];
1591             }
1592         }
1593
1594         if (operation.isCancelled) {
1595             operation.completionBlock = nil;
1596             [self syncOperationFinishedWithSuccess:NO];
1597             [pool drain];
1598             return;
1599         }
1600
1601         // For each stored state compare with current and remote state
1602         // Stored states of local objects that have been deleted, 
1603         // haven't been checked for legality (only existing local remote objects)
1604         // These should be identified and skipped
1605         for (NSString *accountName in accountsNames) {
1606             for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1607                 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1608                 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1609                 BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1610                 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName] 
1611                                                                          objectForKey:pithosContainer.name];
1612                 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1613                 for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1614                     if (operation.isCancelled) {
1615                         operation.completionBlock = nil;
1616                         [self syncOperationFinishedWithSuccess:NO];
1617                         [pool drain];
1618                         return;
1619                     }
1620
1621                     NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1622                     if ([objectName hasSuffix:@"/"])
1623                         filePath = [filePath stringByAppendingString:@":"];
1624                     ASIPithosObject *object = [ASIPithosObject object];
1625                     object.name = objectName;
1626                     NSLog(@"Sync::object name: %@", object.name);
1627
1628                     PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
1629                     PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1630                     if (!currentLocalObjectState) {
1631                         // The stored state corresponds to a remote or deleted local object, that's why there is no current state
1632                         // In the latter case it must be checked for legality, which can be determined by its stored state
1633                         // If it existed locally, but was deleted, state.exists is true, 
1634                         // else if the stored state is an empty state that was created due to the server object, state.exists is false
1635                         if (storedLocalObjectState.exists) {
1636                             NSString *localObjectName;
1637                             if ([object.name hasSuffix:@"/"])
1638                                 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1639                             else
1640                                 localObjectName = [NSString stringWithString:object.name];
1641                             NSArray *pathComponents = [localObjectName pathComponents];
1642                             if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1643                                 // Skip excluded directory object and its descendants, or root file object with same name
1644                                 // Remove stored state
1645                                 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1646                                 [self saveLocalState];
1647                                 continue;
1648                             } else if (containerExludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) {
1649                                 // Skip root file object
1650                                 // Remove stored state
1651                                 [containerStoredLocalObjectStates removeObjectForKey:object.name];                    
1652                                 [self saveLocalState];
1653                                 continue;
1654                             }
1655                         }
1656                         // There is also the off case that a local object has been created in the meantime
1657                         // This call works in any case, existent or non-existent local object 
1658                         currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath 
1659                                                                                          blockHash:pithosContainer.blockHash 
1660                                                                                          blockSize:pithosContainer.blockSize];
1661                     }
1662             
1663                     PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1664                     ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
1665                     if (remoteObject) {
1666                         if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1667                             remoteObjectState.isDirectory = YES;
1668                         } else {
1669                             remoteObjectState.hash = remoteObject.objectHash;
1670                         }
1671                     }
1672
1673                     BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1674                     BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1675                     NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1676                     if (!localStateHasChanged) {
1677                         // Local state hasn't changed
1678                         if (serverStateHasChanged) {
1679                             // Server state has changed
1680                             // Update local state to match that of the server 
1681                             object.bytes = remoteObject.bytes;
1682                             object.version = remoteObject.version;
1683                             object.contentType = remoteObject.contentType;
1684                             object.objectHash = remoteObject.objectHash;
1685                             [self updateLocalStateWithObject:object localFilePath:filePath 
1686                                                  accountName:accountName pithosContainer:pithosContainer];
1687                         } else if (!remoteObject && ![currentLocalObjectState exists]) {
1688                             // Server state hasn't changed
1689                             // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1690                             [containerStoredLocalObjectStates removeObjectForKey:objectName];
1691                             [self saveLocalState];
1692                         }
1693                 } else {
1694                     // Local state has changed
1695                     if (!serverStateHasChanged) {
1696                         // Server state hasn't changed
1697                         if (currentLocalObjectState.isDirectory)
1698                             object.contentType = @"application/directory";
1699                         else
1700                             object.objectHash = currentLocalObjectState.hash;
1701                         [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath 
1702                                                     accountName:accountName pithosContainer:pithosContainer];
1703                     } else {
1704                         // Server state has also changed
1705                         if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1706                             // Both did the same change (directory)
1707                             storedLocalObjectState.filePath = filePath;
1708                             storedLocalObjectState.isDirectory = YES;
1709                             [self saveLocalState];
1710                         } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1711                             // Both did the same change (object edit or delete)
1712                             if (![remoteObjectState exists]) {
1713                                 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1714                             } else {
1715                                 storedLocalObjectState.filePath = filePath;
1716                                 storedLocalObjectState.hash = remoteObjectState.hash;
1717                             }
1718                             [self saveLocalState];
1719                         } else {
1720                             // Conflict, we ask the user which change to keep
1721                             NSString *informativeText;
1722                             NSString *firstButtonText;
1723                             NSString *secondButtonText;
1724                             
1725                             if (![remoteObjectState exists]) {
1726                                 // Remote object has been deleted
1727                                 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.", 
1728                                                    [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name ];
1729                                 firstButtonText = @"Delete local file";
1730                                 secondButtonText = @"Upload file to server";
1731                             } else if (![currentLocalObjectState exists]) {
1732                                 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.", 
1733                                                    [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1734                                 firstButtonText = @"Download file from server";
1735                                 secondButtonText = @"Delete file on server";
1736                             } else {
1737                                 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.", 
1738                                                    [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1739                                 firstButtonText = @"Keep server version";
1740                                 secondButtonText = @"Keep local version";
1741                             }
1742                             __block NSInteger choice;
1743                             dispatch_sync(dispatch_get_main_queue(), ^{
1744                                 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1745                                 [alert setMessageText:@"Conflict"];
1746                                 [alert setInformativeText:informativeText];
1747                                 [alert addButtonWithTitle:firstButtonText];
1748                                 [alert addButtonWithTitle:secondButtonText];
1749                                 [alert addButtonWithTitle:@"Do nothing"];
1750                                 choice = [alert runModal];
1751                             });
1752                             if (choice == NSAlertFirstButtonReturn) {
1753                                 object.bytes = remoteObject.bytes;
1754                                 object.version = remoteObject.version;
1755                                 object.contentType = remoteObject.contentType;
1756                                 object.objectHash = remoteObject.objectHash;
1757                                 [self updateLocalStateWithObject:object localFilePath:filePath 
1758                                                  accountName:accountName pithosContainer:pithosContainer];
1759                             } if (choice == NSAlertSecondButtonReturn) {
1760                                 if (currentLocalObjectState.isDirectory)
1761                                     object.contentType = @"application/directory";
1762                                 else
1763                                     object.objectHash = currentLocalObjectState.hash;
1764                                 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath 
1765                                                             accountName:accountName pithosContainer:pithosContainer];
1766                             }
1767                         }
1768                     }
1769                 }
1770             }
1771         }
1772         }
1773         [self syncOperationFinishedWithSuccess:YES];
1774     } else {
1775         [self listRequestFailed:containerRequest];
1776     }
1777     [pool drain];
1778 }
1779
1780 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1781     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1782     NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1783     if (operation.isCancelled) {
1784         [objects release];
1785         objects = nil;
1786         [pool drain];
1787         return;        
1788     }
1789     if (containerRequest.isCancelled) {
1790         dispatch_async(dispatch_get_main_queue(), ^{
1791             [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1792                               withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1793         });
1794         [objects release];
1795         objects = nil;
1796         [self syncOperationFinishedWithSuccess:NO];
1797         [pool drain];
1798         return;
1799     }
1800     // If the server listing fails, the sync should start over, so just retrying is enough
1801     NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1802     if (retries > 0) {
1803         ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1804         [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1805         [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1806     } else {
1807         dispatch_async(dispatch_get_main_queue(), ^{
1808             [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1809                               withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1810         });
1811         [objects release];
1812         objects = nil;
1813         // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1814         [self syncOperationFinishedWithSuccess:NO];
1815     }
1816     [pool drain];
1817 }
1818
1819 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1820     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1821     NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1822     NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1823     if (operation.isCancelled) {
1824         [self requestFailed:objectRequest];
1825     } else if (objectRequest.responseStatusCode == 206) {
1826         NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1827         ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1828         ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1829         NSFileManager *fileManager = [NSFileManager defaultManager];
1830         NSError *error;
1831         PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1832         
1833         NSString *downloadsDirPath = self.tempDownloadsDirPath;
1834         if (!downloadsDirPath) {
1835             dispatch_async(dispatch_get_main_queue(), ^{
1836                 [activityFacility endActivity:activity 
1837                                   withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1838             });
1839             [self syncOperationFinishedWithSuccess:NO];
1840             [pool drain];
1841             return;
1842         }
1843         
1844         PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName] 
1845                                                 objectForKey:pithosContainer.name] 
1846                                                objectForKey:object.name];
1847         if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1848             NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1849             const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1850             char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1851             strcpy(tempFileNameCString, tempFileTemplateCString);
1852             int fileDescriptor = mkstemp(tempFileNameCString);
1853             NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1854             free(tempFileNameCString);
1855             if (fileDescriptor == -1) {
1856                 dispatch_async(dispatch_get_main_queue(), ^{
1857                     [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error" 
1858                                                             message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath] 
1859                                                               error:nil];
1860                     [activityFacility endActivity:activity 
1861                                       withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1862                 });
1863                 [self syncOperationFinishedWithSuccess:NO];
1864                 [pool drain];
1865                 return;
1866             }
1867             close(fileDescriptor);
1868             storedState.tmpFilePath = tempFilePath;
1869             [self saveLocalState];
1870         }
1871
1872         NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1873         NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1874         [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
1875         [tempFileHandle writeData:[objectRequest responseData]];
1876         [tempFileHandle closeFile];
1877
1878         NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1879         missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1880         if (missingBlockIndex == NSNotFound) {
1881             NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1882             NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1883             if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath 
1884                                                                           accountName:accountName 
1885                                                                       pithosContainer:pithosContainer]) {
1886                 dispatch_async(dispatch_get_main_queue(), ^{
1887                     [activityFacility endActivity:activity 
1888                                       withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1889                 });
1890                 [self syncOperationFinishedWithSuccess:NO];
1891                 [pool drain];
1892                 return;
1893             } else if (![fileManager fileExistsAtPath:dirPath]) {
1894                 // File doesn't exist but also the containing directory doesn't exist
1895                 // In most cases this should have been resolved as an update of the corresponding local object,
1896                 // but it never hurts to check
1897                 error = nil;
1898                 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1899                 if (error != nil) {
1900                     dispatch_async(dispatch_get_main_queue(), ^{
1901                         [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
1902                                                                 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath] 
1903                                                                   error:error];
1904                         [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1905                                           withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1906                     });
1907                     [self syncOperationFinishedWithSuccess:NO];
1908                     [pool drain];
1909                     return;
1910                 }
1911             }
1912             // Move file from tmp download
1913             error = nil;
1914             [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1915             if (error != nil) {
1916                 dispatch_async(dispatch_get_main_queue(), ^{
1917                     [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
1918                                                             message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath] 
1919                                                               error:error];
1920                     [activityFacility endActivity:activity 
1921                                       withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1922                 });
1923                 [self syncOperationFinishedWithSuccess:NO];
1924                 [pool drain];
1925                 return;
1926             }
1927             dispatch_async(dispatch_get_main_queue(), ^{
1928                 [activityFacility endActivity:activity 
1929                                   withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1930                                    totalBytes:activity.totalBytes 
1931                                  currentBytes:activity.totalBytes];
1932             });
1933
1934             storedState.filePath = filePath;
1935             storedState.hash = object.objectHash;
1936             storedState.tmpFilePath = nil;
1937             [self saveLocalState];
1938             [self syncOperationFinishedWithSuccess:YES];
1939             [pool drain];
1940             return;
1941         } else {
1942             if (newSyncRequested || syncLate || operation.isCancelled) {
1943                 [self requestFailed:objectRequest];
1944             } else {
1945                 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
1946                                                                                                        containerName:pithosContainer.name 
1947                                                                                                               object:object 
1948                                                                                                           blockIndex:missingBlockIndex 
1949                                                                                                            blockSize:pithosContainer.blockSize];
1950                 if (![accountName isEqualToString:@""])
1951                     [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1952                 newObjectRequest.delegate = self;
1953                 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1954                 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1955                 newObjectRequest.userInfo = objectRequest.userInfo;
1956                 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1957                 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1958                 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1959                     [activityFacility updateActivity:activity 
1960                                          withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", 
1961                                                       [newObjectRequest.userInfo objectForKey:@"messagePrefix"], 
1962                                                       (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1963                                           totalBytes:activity.totalBytes 
1964                                         currentBytes:(activity.currentBytes + size)];
1965                 }];
1966                 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1967             }
1968         }
1969     } else if (objectRequest.responseStatusCode == 412) {
1970         // The object has changed on the server
1971         dispatch_async(dispatch_get_main_queue(), ^{
1972             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1973                               withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1974         });
1975         [self syncOperationFinishedWithSuccess:NO];
1976     } else {
1977         [self requestFailed:objectRequest];
1978     }
1979     [pool drain];
1980 }
1981
1982 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1983     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1984     NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1985     NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1986     if (operation.isCancelled) {
1987         [self requestFailed:objectRequest];
1988     } else if (objectRequest.responseStatusCode == 200) {
1989         if (newSyncRequested || syncLate || operation.isCancelled) {
1990             [self requestFailed:objectRequest];
1991         } else {
1992             NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1993             ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1994             ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1995             PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName] 
1996                                                     objectForKey:pithosContainer.name] 
1997                                                    objectForKey:object.name];
1998             if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1999                 [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
2000             PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
2001             NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath 
2002                                                                     blockSize:pithosContainer.blockSize 
2003                                                                     blockHash:pithosContainer.blockHash 
2004                                                                    withHashes:[objectRequest hashes]];
2005             NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2006             dispatch_async(dispatch_get_main_queue(), ^{
2007                 [activityFacility updateActivity:activity 
2008                                      withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", 
2009                                                   [objectRequest.userInfo objectForKey:@"messagePrefix"], 
2010                                                   (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))] 
2011                                       totalBytes:activity.totalBytes 
2012                                     currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
2013             });
2014
2015             __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
2016                                                                                                    containerName:pithosContainer.name 
2017                                                                                                           object:object 
2018                                                                                                       blockIndex:missingBlockIndex 
2019                                                                                                        blockSize:pithosContainer.blockSize];
2020             if (![accountName isEqualToString:@""])
2021                 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
2022             newObjectRequest.delegate = self;
2023             newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2024             newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2025             newObjectRequest.userInfo = objectRequest.userInfo;
2026             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2027             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2028             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2029             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
2030             [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
2031                 [activityFacility updateActivity:activity 
2032                                      withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", 
2033                                                   [newObjectRequest.userInfo objectForKey:@"messagePrefix"], 
2034                                                   (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
2035                                       totalBytes:activity.totalBytes 
2036                                     currentBytes:(activity.currentBytes + size)];
2037             }];
2038             [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2039         }
2040     } else {
2041         [self requestFailed:objectRequest];
2042     }
2043     [pool drain];
2044 }
2045
2046 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2047     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2048     NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2049     NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
2050     if (operation.isCancelled) {
2051         [self requestFailed:objectRequest];
2052     } else if (objectRequest.responseStatusCode == 201) {
2053         PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]] 
2054                                                 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
2055                                                objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2056         storedState.isDirectory = YES;
2057         [self saveLocalState];
2058         dispatch_async(dispatch_get_main_queue(), ^{
2059             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
2060                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2061         });
2062         [self syncOperationFinishedWithSuccess:YES];
2063     } else {
2064         [self requestFailed:objectRequest];
2065     }
2066     [pool drain];
2067 }
2068
2069 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
2070     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2071     NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2072     NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
2073     if (operation.isCancelled) {
2074         [self requestFailed:objectRequest];
2075     } else if (objectRequest.responseStatusCode == 201) {
2076         [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]] 
2077           objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
2078          removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2079         [self saveLocalState];
2080         dispatch_async(dispatch_get_main_queue(), ^{
2081             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
2082                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2083         });
2084         [self syncOperationFinishedWithSuccess:YES];
2085     } else {
2086         [self requestFailed:objectRequest];
2087     }
2088     [pool drain];
2089 }
2090
2091 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2092     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2093     NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2094     NSLog(@"Sync::delete object finished: %@", objectRequest.url);
2095     if (operation.isCancelled) {
2096         [self requestFailed:objectRequest];
2097     } else if (objectRequest.responseStatusCode == 204) {
2098         [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]] 
2099           objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
2100          removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2101         [self saveLocalState];
2102         dispatch_async(dispatch_get_main_queue(), ^{
2103             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
2104                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2105         });
2106         [self syncOperationFinishedWithSuccess:YES];
2107     } else {
2108         [self requestFailed:objectRequest];
2109     }
2110     [pool drain];
2111 }
2112
2113 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
2114     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2115     NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2116     NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
2117     NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
2118     ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
2119     ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
2120     PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName] 
2121                                             objectForKey:pithosContainer.name] 
2122                                            objectForKey:object.name];
2123     PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
2124     NSUInteger totalBytes = activity.totalBytes;
2125     NSUInteger currentBytes = activity.currentBytes;
2126     if (operation.isCancelled) {
2127         [self requestFailed:objectRequest];
2128     } else if (objectRequest.responseStatusCode == 201) {
2129         NSLog(@"Sync::object created: %@", objectRequest.url);
2130         storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
2131         storedState.hash = object.objectHash;
2132         [self saveLocalState];
2133         dispatch_async(dispatch_get_main_queue(), ^{
2134             [activityFacility endActivity:activity 
2135                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
2136                                totalBytes:totalBytes 
2137                              currentBytes:totalBytes];
2138         });
2139         [self syncOperationFinishedWithSuccess:YES];
2140     } else if (objectRequest.responseStatusCode == 409) {
2141         if (newSyncRequested || syncLate || operation.isCancelled) {
2142             [self requestFailed:objectRequest];
2143         } else {
2144             NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
2145             if (iteration == 0) {
2146                 NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
2147                 dispatch_async(dispatch_get_main_queue(), ^{
2148                     [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
2149                 });
2150                 [self syncOperationFinishedWithSuccess:NO];
2151                 [pool drain];
2152                 return;
2153             }
2154             NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
2155             NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
2156                                                               withMissingHashes:[objectRequest hashes]];
2157             if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
2158                 currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
2159             dispatch_async(dispatch_get_main_queue(), ^{
2160                 [activityFacility updateActivity:activity 
2161                                      withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", 
2162                                                   [objectRequest.userInfo objectForKey:@"messagePrefix"], 
2163                                                   (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
2164                                       totalBytes:totalBytes 
2165                                     currentBytes:currentBytes];
2166             });
2167             NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2168             __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
2169                                                                                                              containerName:pithosContainer.name 
2170                                                                                                                  blockSize:pithosContainer.blockSize 
2171                                                                                                                    forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
2172                                                                                                          missingBlockIndex:missingBlockIndex 
2173                                                                                                             sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2174             newContainerRequest.delegate = self;
2175             newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2176             newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2177             newContainerRequest.userInfo = objectRequest.userInfo;
2178             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
2179             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2180             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2181             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2182             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
2183             [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2184                 [activityFacility updateActivity:activity 
2185                                      withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", 
2186                                                   [newContainerRequest.userInfo objectForKey:@"messagePrefix"], 
2187                                                   (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
2188                                       totalBytes:activity.totalBytes 
2189                                     currentBytes:(activity.currentBytes + size)];
2190             }];
2191             [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2192         }
2193     } else {
2194         [self requestFailed:objectRequest];
2195     }
2196     [pool drain];
2197 }
2198
2199 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
2200     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2201     NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
2202     NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
2203     if (operation.isCancelled) {
2204         [self requestFailed:containerRequest];
2205     } else if (containerRequest.responseStatusCode == 202) {
2206         NSString *accountName = [containerRequest.userInfo objectForKey:@"accountName"];
2207         ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
2208         ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
2209         PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
2210         NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
2211         NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
2212         missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
2213         if (operation.isCancelled) {
2214             [self requestFailed:containerRequest];
2215         } else if (missingBlockIndex == NSNotFound) {
2216             NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
2217             ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
2218                                                                                            containerName:pithosContainer.name 
2219                                                                                               objectName:object.name 
2220                                                                                              contentType:object.contentType 
2221                                                                                                blockSize:pithosContainer.blockSize 
2222                                                                                                blockHash:pithosContainer.blockHash
2223                                                                                                  forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
2224                                                                                            checkIfExists:NO 
2225                                                                                                   hashes:&hashes 
2226                                                                                           sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2227             newObjectRequest.delegate = self;
2228             newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2229             newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2230             newObjectRequest.userInfo = containerRequest.userInfo;
2231             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2232             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
2233             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
2234             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
2235             [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2236         } else {
2237             if (newSyncRequested || syncLate || operation.isCancelled) {
2238                 [self requestFailed:containerRequest];
2239             } else {
2240                 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
2241                                                                                                                  containerName:pithosContainer.name
2242                                                                                                                      blockSize:pithosContainer.blockSize
2243                                                                                                                        forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
2244                                                                                                              missingBlockIndex:missingBlockIndex 
2245                                                                                                                 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2246                 newContainerRequest.delegate = self;
2247                 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2248                 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2249                 newContainerRequest.userInfo = containerRequest.userInfo;
2250                 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2251                 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2252                 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2253                     [activityFacility updateActivity:activity 
2254                                          withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", 
2255                                                       [newContainerRequest.userInfo objectForKey:@"messagePrefix"], 
2256                                                       (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
2257                                           totalBytes:activity.totalBytes 
2258                                         currentBytes:(activity.currentBytes + size)];
2259                 }];
2260                 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2261             }
2262         }
2263     } else {
2264         [self requestFailed:containerRequest];
2265     }
2266     [pool drain];
2267 }
2268
2269 - (void)requestFailed:(ASIPithosRequest *)request {
2270     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2271     NSOperation *operation = [request.userInfo objectForKey:@"operation"];
2272     NSLog(@"Sync::request failed: %@", request.url);
2273     if (operation.isCancelled) {
2274         [pool drain];
2275         return;        
2276     }
2277     if (request.isCancelled || newSyncRequested || syncLate) {
2278         dispatch_async(dispatch_get_main_queue(), ^{
2279             [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
2280                               withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
2281         });
2282         [self syncOperationFinishedWithSuccess:NO];
2283         [pool drain];
2284         return;
2285     }
2286     NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
2287     if (retries > 0) {
2288         ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
2289         [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
2290         [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
2291     } else {
2292         dispatch_async(dispatch_get_main_queue(), ^{
2293             [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
2294                               withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
2295         });
2296         [self syncOperationFinishedWithSuccess:NO];
2297     }
2298     [pool drain];
2299 }
2300
2301 @end