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