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