Directory marker objects can be of content type "application/directory" or "applicati...
[pithos-macos] / pithos-macos / PithosSyncDaemon.m
1 //
2 //  PithosSyncDaemon.m
3 //  pithos-macos
4 //
5 // Copyright 2011 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 "PithosLocalObjectState.h"
40 #import "PithosActivityFacility.h"
41 #import "PithosUtilities.h"
42 #import "ASINetworkQueue.h"
43 #import "ASIPithosRequest.h"
44 #import "ASIPithosContainerRequest.h"
45 #import "ASIPithosObjectRequest.h"
46 #import "ASIPithosObject.h"
47
48 @interface PithosSyncDaemon (Private)
49 - (NSString *)pithosStateFilePath;
50 - (void)saveLocalState;
51
52 - (BOOL)moveToTempTrashFile:(NSString *)filePath;
53 - (void)emptyTempTrash;
54 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
55
56 - (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath;
57 - (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
58                                    object:(ASIPithosObject *)object 
59                             localFilePath:(NSString *)filePath;
60 - (void)requestFailed:(ASIPithosRequest *)request;
61
62 - (void)increaseSyncOperationCount;
63 - (void)decreaseSyncOperationCount;
64
65 @end
66
67 @implementation PithosSyncDaemon
68 @synthesize blockHash, blockSize, lastModified, lastCompletedSync, remoteObjects, storedLocalObjectStates, currentLocalObjectStates;
69 @synthesize directoryPath, containerDirectoryPath, pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
70
71 #pragma mark -
72 #pragma Object Lifecycle
73
74 - (id)initWithDirectoryPath:(NSString *)aDirectoryPath 
75               containerName:(NSString *)aContainerName 
76                timeInterval:(NSTimeInterval)aTimeInterval 
77             resetLocalState:(BOOL)resetLocalState {
78     if ((self = [super init])) {
79         directoryPath = [aDirectoryPath copy];
80         containerName = [aContainerName copy];
81         timeInterval = aTimeInterval;
82         
83         syncOperationCount = 0;
84         newSyncRequested = NO;
85         containerDirectoryPath = [[directoryPath stringByAppendingPathComponent:containerName] copy];
86         
87         activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
88         
89         NSFileManager *fileManager = [NSFileManager defaultManager];
90         if (resetLocalState) {
91             NSError *error = nil;
92             if ([fileManager fileExistsAtPath:self.pithosStateFilePath] && 
93                 (![fileManager removeItemAtPath:self.pithosStateFilePath error:&error] || error))
94                 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
95                                                         message:[NSString stringWithFormat:@"Cannot remove file at '%@'", self.pithosStateFilePath] 
96                                                           error:error];
97             if (self.tempDownloadsDirPath) {
98                 error = nil;
99                 for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
100                     if (error) {
101                         [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
102                                                                 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath] 
103                                                                   error:error];
104                         break;
105                     }
106                     NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
107                     if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
108                         [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
109                                                                 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
110                                                                   error:error];
111                     }
112                     error = nil;
113                 }
114             }
115         }
116         if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath]) {
117             NSData *data = [NSData dataWithContentsOfFile:self.pithosStateFilePath];
118             NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
119             self.storedLocalObjectStates = [unarchiver decodeObjectForKey:containerName];
120             [unarchiver finishDecoding];
121         } else {
122             self.storedLocalObjectStates = [NSMutableDictionary dictionary];
123         }
124         
125         queue = [[ASINetworkQueue alloc] init];
126         queue.showAccurateProgress = YES;
127         queue.shouldCancelAllRequestsOnFailure = NO;
128         [queue go];
129         
130         [[NSNotificationCenter defaultCenter] addObserver:self
131                                                  selector:@selector(applicationWillTerminate:)
132                                                      name:NSApplicationWillTerminateNotification
133                                                    object:[NSApplication sharedApplication]];
134     
135         timer = [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(sync) userInfo:nil repeats:YES] retain];
136         [timer fire];
137     }
138     
139     return self;
140 }
141
142 - (void)dealloc {
143     [[NSNotificationCenter defaultCenter] removeObserver:self];
144     [queue cancelAllOperations];
145     [queue release];
146     [timer invalidate];
147     [timer release];
148     [tempTrashDirPath release];
149     [tempDownloadsDirPath release];
150     [pithosStateFilePath release];
151     [containerDirectoryPath release];
152     [currentLocalObjectStates release];
153     [storedLocalObjectStates release];
154     [remoteObjects release];
155     [objects release];
156     [lastCompletedSync release];
157     [lastModified release];
158     [blockHash release];
159     [containerName release];
160     [directoryPath release];
161     [super dealloc];
162 }
163
164 #pragma mark -
165 #pragma mark Observers
166
167 - (void)applicationWillTerminate:(NSNotification *)notification {
168     [self saveLocalState];
169 }
170
171 #pragma mark -
172 #pragma mark Properties
173
174 - (NSString *)pithosStateFilePath {
175     if (!pithosStateFilePath)
176         pithosStateFilePath = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"PithosLocalObjectStates.archive"] retain];
177     return [pithosStateFilePath copy];
178 }
179
180 - (NSString *)tempDownloadsDirPath {
181     NSFileManager *fileManager = [NSFileManager defaultManager];
182     if (!tempDownloadsDirPath || ![fileManager fileExistsAtPath:tempDownloadsDirPath]) {
183         // Get the path from user defaults
184         NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
185         tempDownloadsDirPath = [userDefaults stringForKey:@"PithosSyncTempDownloadsDirPath"];
186         if (tempDownloadsDirPath) {
187             // Check if the path exists
188             BOOL isDirectory;
189             BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
190             NSError *error = nil;
191             if (fileExists && !isDirectory)
192                 [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
193             if (!error & !fileExists)
194                 [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
195             if (error)
196                 tempDownloadsDirPath = nil;
197             }
198         if (!tempDownloadsDirPath) {
199             NSString *tempDirTemplate = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Temp Downloads XXXXXX"];
200             const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
201             char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
202             strcpy(tempDirNameCString, tempDirTemplateCString);
203             tempDirNameCString = mkdtemp(tempDirNameCString);
204             if (tempDirNameCString != NULL)
205                 tempDownloadsDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
206             free(tempDirNameCString);
207         }
208         if (tempDownloadsDirPath)
209             [userDefaults setObject:tempDownloadsDirPath forKey:@"PithosSyncTempDownloadsDirPath"];
210         [tempDownloadsDirPath retain];
211     }
212     return [tempDownloadsDirPath copy];
213 }
214
215 - (NSString *)tempTrashDirPath {
216     NSFileManager *fileManager = [NSFileManager defaultManager];
217     if (!tempTrashDirPath || ![fileManager fileExistsAtPath:tempTrashDirPath]) {
218         // Get the path from user defaults
219         NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
220         tempTrashDirPath = [userDefaults stringForKey:@"PithosSyncTempTrashDirPath"];
221         if (tempTrashDirPath) {
222             // Check if the path exists
223             BOOL isDirectory;
224             BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
225             NSError *error = nil;
226             if (fileExists && !isDirectory)
227                 [fileManager removeItemAtPath:tempTrashDirPath error:&error];
228             if (!error & !fileExists)
229                 [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
230             if (error)
231                 tempTrashDirPath = nil;
232         }
233         if (!tempTrashDirPath) {
234             NSString *tempDirTemplate = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Temp Trash XXXXXX"];
235             const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
236             char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
237             strcpy(tempDirNameCString, tempDirTemplateCString);
238             tempDirNameCString = mkdtemp(tempDirNameCString);
239             if (tempDirNameCString != NULL)
240                 tempTrashDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
241             free(tempDirNameCString);
242         }
243         if (tempTrashDirPath)
244             [userDefaults setObject:tempTrashDirPath forKey:@"PithosSyncTempTrashDirPath"];
245         [tempTrashDirPath retain];
246     }
247     return [tempTrashDirPath copy];
248 }
249
250 #pragma mark -
251 #pragma mark Sync
252
253 - (void)saveLocalState {
254     NSMutableData *data = [NSMutableData data];
255     NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
256     [archiver encodeObject:storedLocalObjectStates forKey:containerName];
257     [archiver finishEncoding];
258     [data writeToFile:self.pithosStateFilePath atomically:YES];
259 }
260
261 - (void)increaseSyncOperationCount {
262     @synchronized(self) {
263         syncOperationCount++;
264     }
265 }
266
267 - (void)decreaseSyncOperationCount {
268     @synchronized(self) {
269         syncOperationCount--;
270         if (!syncOperationCount) {
271             if (!syncIncomplete) {
272                 self.lastCompletedSync = [NSDate date];
273                 [activityFacility startAndEndActivityWithType:PithosActivityOther 
274                                                       message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]];
275             }
276             [self emptyTempTrash];
277         }
278     }
279 }
280
281 - (void)sync {
282     @synchronized(self) {
283         if (syncOperationCount) {
284             // If at least one operation is running return
285             newSyncRequested = YES;
286             return;
287         } else {
288             // The first operation is the server listing
289             syncOperationCount = 1;
290             newSyncRequested = NO;
291             syncIncomplete = NO;
292         }
293     }
294
295     NSFileManager *fileManager = [NSFileManager defaultManager];
296     BOOL isDirectory;
297     NSError *error = nil;
298     if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
299         if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] || 
300             error) {
301             [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
302                                                     message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", containerDirectoryPath] 
303                                                       error:error];
304             @synchronized(self) {
305                 syncOperationCount = 0;
306             }
307             return;
308         }
309     } else if (!isDirectory) {
310         [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
311                                                 message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", containerDirectoryPath] 
312                                                   error:nil];
313         @synchronized(self) {
314             syncOperationCount = 0;
315         }
316         return;
317     }
318     
319     ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
320                                                                                                            limit:0 
321                                                                                                           marker:nil 
322                                                                                                           prefix:nil 
323                                                                                                        delimiter:nil 
324                                                                                                             path:nil 
325                                                                                                             meta:nil 
326                                                                                                           shared:NO 
327                                                                                                            until:nil 
328                                                                                                  ifModifiedSince:lastModified];
329     containerRequest.delegate = self;
330     containerRequest.didFinishSelector = @selector(listRequestFinished:);
331     containerRequest.didFailSelector = @selector(listRequestFailed:);
332     PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther 
333                                                                message:@"Sync: Getting server listing"];
334     containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
335                                  activity, @"activity", 
336                                  @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage", 
337                                  @"Sync: Getting server listing (failed)", @"failedActivityMessage", 
338                                  @"Sync: Getting server listing (finished)", @"finishedActivityMessage", 
339                                  [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
340                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
341                                  nil];
342     [queue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh]];
343 }
344
345 - (void)emptyTempTrash {
346     NSString *trashDirPath = self.tempTrashDirPath;
347     if (trashDirPath) {
348         NSFileManager *fileManager = [NSFileManager defaultManager];
349         NSError *error = nil;
350 //        NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
351         NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
352         if (error) {
353             [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
354                                                     message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
355                                                       error:error];
356             return;
357         }
358         if ([subPaths count]) {
359 //            NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
360 //            for (NSString *subPath in subPaths) {
361 //                [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
362 //            }
363 //            syncOperationCount = 1;
364 //            [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
365 //                if (error) {
366 //                    [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error" 
367 //                                                            message:@"Cannot move files to Trash" 
368 //                                                              error:error];
369 //                }
370 //                syncOperationCount = 0;
371 //            }];
372             for (NSString *subPath in subPaths) {
373                 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
374                 error = nil;
375                 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
376                     [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
377                                                             message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
378                                                               error:error];
379             }
380         }
381     }    
382 }
383
384 - (BOOL)moveToTempTrashFile:(NSString *)filePath {
385     NSString *trashDirPath = self.tempTrashDirPath;
386     if (!tempTrashDirPath)
387         return NO;
388     NSFileManager *fileManager = [NSFileManager defaultManager];
389     BOOL isDirectory;
390     BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
391     NSError *error = nil;
392     NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath 
393                                                                 withString:trashDirPath];
394     NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
395     if (fileExists && isDirectory) {
396         NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
397         if (error) {
398             [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
399                                                     message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
400                                                       error:error];
401             return NO;
402         }
403         if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
404             [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
405                                                     message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
406                                                       error:error];
407             return NO;
408         }
409         if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
410             [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
411                                                     message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
412                                                              filePath, newFilePath] 
413                                                       error:error];
414             return NO;
415         }
416         PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
417         if (currentState) {
418             currentState.filePath = newFilePath;
419             [currentLocalObjectStates setObject:currentState forKey:newFilePath];
420             [currentLocalObjectStates removeObjectForKey:filePath];        
421         } else {
422             [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
423                                                                                        blockHash:blockHash 
424                                                                                        blockSize:blockSize] 
425                                          forKey:newFilePath];
426         }
427         for (NSString *subPath in subPaths) {
428             NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
429             NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath 
430                                                                               withString:trashDirPath];
431             currentState = [currentLocalObjectStates objectForKey:subFilePath];
432             if (currentState) {
433                 currentState.filePath = newSubFilePath;
434                 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
435                 [currentLocalObjectStates removeObjectForKey:subFilePath];
436             } else {
437                 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath 
438                                                                                            blockHash:blockHash 
439                                                                                            blockSize:blockSize] 
440                                              forKey:newSubFilePath];
441             }        
442         }
443     } else if (fileExists && !isDirectory) {
444         if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
445             [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
446                                                     message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
447                                                       error:error];
448             return NO;
449         }
450         if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
451             [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
452                                                     message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
453                                                              filePath, newFilePath] 
454                                                       error:error];
455             return NO;
456         }
457         PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
458         if (currentState) {
459             currentState.filePath = newFilePath;
460             [currentLocalObjectStates setObject:currentState forKey:newFilePath];
461             [currentLocalObjectStates removeObjectForKey:filePath];        
462         } else {
463             [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
464                                                                                        blockHash:blockHash 
465                                                                                        blockSize:blockSize] 
466                                          forKey:newFilePath];
467         }
468     }
469     return YES;
470 }
471
472 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
473     NSUInteger hashLength = [hash length];
474     if ((hashLength != 32) && (hashLength != 64))
475         return NO;
476     PithosLocalObjectState *localState;
477     NSFileManager *fileManager = [NSFileManager defaultManager];
478     BOOL isDirectory;
479     NSError *error = nil;
480     for (NSString *localFilePath in [currentLocalObjectStates allKeys]) {
481         localState = [currentLocalObjectStates objectForKey:localFilePath];
482         if (!localState.isDirectory && ([hash isEqualToString:localState.md5] || [hash isEqualToString:localState.hashMapHash]) && 
483             [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
484             if ([localFilePath hasPrefix:containerDirectoryPath]) {
485                 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
486                     [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error" 
487                                                             message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'", 
488                                                                      localFilePath, filePath] 
489                                                               error:error];
490                 } else {
491                     return YES;
492                 }            
493             } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
494                 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
495                     [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
496                                                             message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
497                                                                      localFilePath, filePath] 
498                                                               error:error];
499                 } else {
500                     localState.filePath = filePath;
501                     [currentLocalObjectStates setObject:localState forKey:filePath];
502                     [currentLocalObjectStates removeObjectForKey:localFilePath];
503                     return YES;
504                 }
505             }
506         }
507     }
508     return NO;
509 }
510
511 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
512                      localFilePath:(NSString *)filePath {
513     NSFileManager *fileManager = [NSFileManager defaultManager];
514     NSError *error;
515     BOOL isDirectory;
516     BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
517     PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
518     if (!object || !object.hash) {
519         // Delete local object
520         NSLog(@"Sync::delete local object: %@", filePath);
521         if (!fileExists || [self moveToTempTrashFile:filePath]) {
522             [activityFacility startAndEndActivityWithType:PithosActivityOther 
523                                                   message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]];
524             [storedLocalObjectStates removeObjectForKey:object.name];
525             [self saveLocalState];
526         }
527     } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
528         // Create local directory object
529         NSLog(@"Sync::create local directory object: %@", filePath);
530         BOOL directoryCreated = NO;
531         if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath])) {
532             NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
533             error = nil;
534             if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
535                 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
536                                                         message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath] 
537                                                           error:error];
538             } else {
539                 directoryCreated = YES;
540                 storedState.isDirectory = YES;
541                 [self saveLocalState];
542             }
543         } else {
544             NSLog(@"Sync::local directory object exists: %@", filePath);
545             directoryCreated = YES;
546         }
547         if (directoryCreated)
548             [activityFacility startAndEndActivityWithType:PithosActivityOther 
549                                                   message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name]];
550     } else if (object.bytes == 0) {
551         // Create local object with zero length
552         NSLog(@"Sync::create local zero length object: %@", filePath);
553         BOOL fileCreated = NO;
554         if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath])) {
555             NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
556             error = nil;
557             if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
558                 [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error" 
559                                                         message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath] 
560                                                           error:error];
561             } else {
562                 fileCreated = YES;
563                 storedState.hash = object.hash;
564                 storedState.filePath = nil;
565                 [self saveLocalState];
566             }
567         } else {
568             NSLog(@"Sync::local zero length object exists: %@", filePath);
569             fileCreated = YES;
570         }
571         if (fileCreated)
572             [activityFacility startAndEndActivityWithType:PithosActivityOther 
573                                                   message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
574     } else if (storedState.filePath == nil) {
575         // Create new local object
576         // Check first if a local copy exists
577         if ([self findLocalCopyForObjectWithHash:object.hash forFile:filePath]) {
578             [activityFacility startAndEndActivityWithType:PithosActivityOther 
579                                                   message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
580         } else {
581             [self increaseSyncOperationCount];
582             __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
583                                                                                                               object:object 
584                                                                                                           blockIndex:0 
585                                                                                                            blockSize:blockSize];
586             objectRequest.delegate = self;
587             objectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
588             objectRequest.didFailSelector = @selector(requestFailed:);
589             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
590                                                                        message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
591                                                                     totalBytes:object.bytes 
592                                                                   currentBytes:0];
593             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
594                                       object, @"pithosObject", 
595                                       [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(blockSize + 0.0)))], @"missingBlocks", 
596                                       [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex", 
597                                       filePath, @"filePath", 
598                                       activity, @"activity", 
599                                       [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
600                                       [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage", 
601                                       [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage", 
602                                       [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
603                                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
604                                       nil];
605             [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
606                 [activityFacility updateActivity:activity 
607                                      withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
608                                                   [[objectRequest.userInfo valueForKey:@"pithosObject"] name], 
609                                                   (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
610                                       totalBytes:activity.totalBytes 
611                                     currentBytes:(activity.currentBytes + size)];
612             }];
613             [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
614         }
615     } else {
616         // Resume local object download
617         // Check first if a local copy exists
618         if ([self findLocalCopyForObjectWithHash:object.hash forFile:filePath]) {
619             [activityFacility startAndEndActivityWithType:PithosActivityOther 
620                                                   message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
621             // Delete incomplete temp download
622             error = nil;
623             if (![fileManager removeItemAtPath:storedState.filePath error:&error] || error) {
624                 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
625                                                         message:[NSString stringWithFormat:@"Cannot remove file at '%@'", storedState.filePath] 
626                                                           error:error];
627             }
628         } else {
629             [self increaseSyncOperationCount];
630             ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithContainerName:containerName 
631                                                                                                        objectName:object.name];
632             objectRequest.delegate = self;
633             objectRequest.didFinishSelector = @selector(downloadObjectHashMapFinished:);
634             // The fail method for block download does exactly what we want
635             objectRequest.didFailSelector = @selector(requestFailed:);
636             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
637                                                                        message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
638                                                                     totalBytes:object.bytes 
639                                                                   currentBytes:0];
640             objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
641                                       object, @"pithosObject", 
642                                       filePath, @"filePath", 
643                                       activity, @"activity", 
644                                       [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
645                                       [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage", 
646                                       [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage", 
647                                       [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
648                                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
649                                       nil];
650             [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
651         }
652     }
653 }
654
655 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
656                                   object:(ASIPithosObject *)object 
657                            localFilePath:(NSString *)filePath {
658     [self increaseSyncOperationCount];
659     if (currentState.isDirectory) {
660         // Create remote directory object
661         ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName 
662                                                                                                      objectName:object.name 
663                                                                                                            eTag:nil 
664                                                                                                     contentType:@"application/directory" 
665                                                                                                 contentEncoding:nil 
666                                                                                              contentDisposition:nil 
667                                                                                                        manifest:nil 
668                                                                                                         sharing:nil 
669                                                                                                        isPublic:ASIPithosObjectRequestPublicIgnore 
670                                                                                                        metadata:nil 
671                                                                                                            data:[NSData data]];
672         objectRequest.delegate = self;
673         objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
674         objectRequest.didFailSelector = @selector(requestFailed:);
675         PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
676                                                                    message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]];
677         objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
678                                   object, @"pithosObject", 
679                                   activity, @"activity", 
680                                   [NSString stringWithFormat:@"Sync: Creating directory '%@' (stopped)", object.name], @"stoppedActivityMessage", 
681                                   [NSString stringWithFormat:@"Sync: Creating directory '%@' (failed)", object.name], @"failedActivityMessage", 
682                                   [NSString stringWithFormat:@"Sync: Creating directory '%@' (finished)", object.name], @"finishedActivityMessage", 
683                                   [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
684                                   [NSNumber numberWithUnsignedInteger:10], @"retries", 
685                                   nil];
686         [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
687     } else if (!currentState.exists) {
688         // Delete remote object
689         NSString *safeObjectName = [PithosUtilities safeObjectNameForContainerName:@"trash" 
690                                                                         objectName:object.name];
691         if (safeObjectName) {
692             ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:containerName 
693                                                                                              objectName:object.name 
694                                                                                destinationContainerName:@"trash" 
695                                                                                   destinationObjectName:safeObjectName 
696                                                                                           checkIfExists:NO];
697             if (objectRequest) {
698                 objectRequest.delegate = self;
699                 objectRequest.didFinishSelector = @selector(moveObjectToTrashFinished:);
700                 objectRequest.didFailSelector = @selector(requestFailed:);
701                 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
702                                                                            message:[NSString stringWithFormat:@"Sync: Moving to trash '%@'", object.name]];
703                 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
704                                           object, @"pithosObject", 
705                                           activity, @"activity", 
706                                           [NSString stringWithFormat:@"Sync: Moving to trash '%@' (stopped)", object.name], @"stoppedActivityMessage", 
707                                           [NSString stringWithFormat:@"Sync: Moving to trash '%@' (failed)", object.name], @"failedActivityMessage", 
708                                           [NSString stringWithFormat:@"Sync: Moving to trash '%@' (finished)", object.name], @"finishedActivityMessage", 
709                                           [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
710                                           [NSNumber numberWithUnsignedInteger:10], @"retries", 
711                                           nil];
712                 [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
713             } else {
714                 syncIncomplete = YES;
715                 [self decreaseSyncOperationCount];
716             }
717         } else {
718             syncIncomplete = YES;
719             [self decreaseSyncOperationCount];
720         }
721     } else {
722         // Upload file to remote object
723         NSError *error = nil;
724         object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
725         if (object.contentType == nil)
726             object.contentType = @"application/octet-stream";
727         if (error)
728             NSLog(@"contentType detection error: %@", error);
729         NSArray *hashes = nil;
730         ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName 
731                                                                                               objectName:object.name 
732                                                                                              contentType:object.contentType 
733                                                                                                blockSize:blockSize 
734                                                                                                blockHash:blockHash 
735                                                                                                  forFile:filePath 
736                                                                                            checkIfExists:NO 
737                                                                                                   hashes:&hashes 
738                                                                                           sharingAccount:nil];
739         if (objectRequest) {
740             objectRequest.delegate = self;
741             objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
742             objectRequest.didFailSelector = @selector(requestFailed:);
743             PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
744                                                                        message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
745                                                                     totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
746                                                                   currentBytes:0];
747             [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
748              [NSDictionary dictionaryWithObjectsAndKeys:
749               object, @"pithosObject", 
750               filePath, @"filePath", 
751               hashes, @"hashes", 
752               [NSNumber numberWithUnsignedInteger:10], @"iteration", 
753               activity, @"activity", 
754               [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
755               [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage", 
756               [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage", 
757               [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
758               [NSNumber numberWithUnsignedInteger:10], @"retries", 
759               nil]];
760             [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
761         } else {
762             syncIncomplete = YES;
763             [self decreaseSyncOperationCount];
764         }
765     }
766
767 }
768
769 #pragma mark -
770 #pragma mark ASIHTTPRequestDelegate
771
772 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
773     NSLog(@"Sync::list request finished: %@", containerRequest.url);
774     if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
775         if (containerRequest.responseStatusCode == 200) {
776             NSArray *someObjects = [containerRequest objects];
777             if (objects == nil) {
778                 objects = [[NSMutableArray alloc] initWithArray:someObjects];
779             } else {
780                 [objects addObjectsFromArray:someObjects];
781             }
782             if ([someObjects count] < 10000) {
783                 self.blockHash = [containerRequest blockHash];
784                 self.blockSize = [containerRequest blockSize];
785                 self.lastModified = [containerRequest lastModified];
786                 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
787                 for (ASIPithosObject *object in objects) {
788                     [remoteObjects setObject:object forKey:object.name];
789                 }
790                 [objects release];
791                 objects = nil;
792             } else {
793                 // Do an additional request to fetch more objects
794                 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
795                                                                                                                           limit:0 
796                                                                                                                          marker:[[someObjects lastObject] name] 
797                                                                                                                          prefix:nil 
798                                                                                                                       delimiter:nil 
799                                                                                                                            path:nil 
800                                                                                                                            meta:nil 
801                                                                                                                          shared:NO 
802                                                                                                                           until:nil 
803                                                                                                                 ifModifiedSince:lastModified];
804                 newContainerRequest.delegate = self;
805                 newContainerRequest.didFinishSelector = @selector(listRequestFinished:);
806                 newContainerRequest.didFailSelector = @selector(listRequestFailed:);
807                 newContainerRequest.userInfo = newContainerRequest.userInfo;
808                 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
809                 [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
810                 return;
811             }
812         }
813         [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
814                           withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
815         NSFileManager *fileManager = [NSFileManager defaultManager];
816         NSError *error = nil;
817         NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
818         if (error) {
819             [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
820                                                     message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
821                                                       error:error];
822             [activityFacility startAndEndActivityWithType:PithosActivityOther message:@"Sync: Failed to read contents of sync directory"];
823             @synchronized(self) {
824                 // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccesfully
825                 syncOperationCount = 0;
826                 if (newSyncRequested)
827                     [self sync];
828             }
829             return;
830         }
831         self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:[subPaths count]];
832         for (NSString *objectName in subPaths) {
833             if (![storedLocalObjectStates objectForKey:objectName]) {
834                 [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
835             }
836             NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
837             [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath 
838                                                                                        blockHash:blockHash 
839                                                                                        blockSize:blockSize] 
840                                          forKey:filePath];
841         }
842         [self saveLocalState];
843
844         for (NSString *objectName in remoteObjects) {
845             if (![storedLocalObjectStates objectForKey:objectName])
846                 [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
847         }
848
849         for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
850             NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
851             if ([objectName hasSuffix:@"/"])
852                 filePath = [filePath stringByAppendingString:@":"];
853             ASIPithosObject *object = [ASIPithosObject object];
854             object.name = objectName;
855             NSLog(@"Sync::object name: %@", objectName);
856             
857             PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
858             PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
859             if (!currentLocalObjectState)
860                 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath 
861                                                                                  blockHash:blockHash 
862                                                                                  blockSize:blockSize];
863             if (currentLocalObjectState.isDirectory)
864                 object.contentType = @"application/directory";
865             
866             PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
867             ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
868             if (remoteObject) {
869                 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
870                     remoteObjectState.isDirectory = YES;
871                     object.contentType = @"application/directory";
872                 } else {
873                     remoteObjectState.hash = remoteObject.hash;
874                 }
875             }
876
877             BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
878             BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
879             NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
880             if (!localStateHasChanged) {
881                 // Local state hasn't changed
882                 if (serverStateHasChanged) {
883                     // Server state has changed
884                     // Update local state to match that of the server 
885                     object.bytes = remoteObject.bytes;
886                     object.version = remoteObject.version;
887                     object.contentType = remoteObject.contentType;
888                     object.hash = remoteObject.hash;
889                     [self updateLocalStateWithObject:object localFilePath:filePath];
890                 } else if (!remoteObject && !currentLocalObjectState.exists) {
891                     // Server state hasn't changed
892                     // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
893                     [storedLocalObjectStates removeObjectForKey:objectName];
894                     [self saveLocalState];
895                 }
896             } else {
897                 // Local state has changed
898                 if (!serverStateHasChanged) {
899                     // Server state hasn't changed
900                     [self updateServerStateWithCurrentState:currentLocalObjectState 
901                                                      object:object 
902                                               localFilePath:filePath];
903                 } else {
904                     // Server state has also changed
905                     if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
906                         // Both did the same change (directory)
907                         storedLocalObjectState.isDirectory = YES;
908                         [self saveLocalState];
909                     } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
910                         // Both did the same change (object edit or delete)
911                         if (!remoteObjectState.exists)
912                             [storedLocalObjectStates removeObjectForKey:object.name];
913                         else
914                             storedLocalObjectState.hash = remoteObjectState.hash;
915                         [self saveLocalState];
916                     } else {
917                         // Conflict, we ask the user which change to keep
918                         NSString *informativeText;
919                         NSString *firstButtonText;
920                         NSString *secondButtonText;
921                         
922                         if (!remoteObjectState.exists) {
923                             // Remote object has been deleted
924                             informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
925                             firstButtonText = @"Delete local file";
926                             secondButtonText = @"Upload file to server";
927                         } else if (!currentLocalObjectState.exists) {
928                             informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
929                             firstButtonText = @"Download file from server";
930                             secondButtonText = @"Delete file on server";
931                         } else {
932                             informativeText = [NSString stringWithFormat:@"'%@' has been modifed both locally and on the server.", object.name];
933                             firstButtonText = @"Keep server version";
934                             secondButtonText = @"Keep local version";
935                         }
936                         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
937                         [alert setMessageText:@"Conflict"];
938                         [alert setInformativeText:informativeText];
939                         [alert addButtonWithTitle:firstButtonText];
940                         [alert addButtonWithTitle:secondButtonText];
941                         [alert addButtonWithTitle:@"Do nothing"];
942                         NSInteger choice = [alert runModal];
943                         if (choice == NSAlertFirstButtonReturn) {
944                             object.bytes = remoteObject.bytes;
945                             object.version = remoteObject.version;
946                             object.contentType = remoteObject.contentType;
947                             object.hash = remoteObject.hash;
948                             [self updateLocalStateWithObject:object localFilePath:filePath];
949                         } if (choice == NSAlertSecondButtonReturn) {
950                             [self updateServerStateWithCurrentState:currentLocalObjectState 
951                                                              object:object 
952                                                       localFilePath:filePath];
953                         }
954                     }
955                 }
956             }
957         }
958         @synchronized(self) {
959             [self decreaseSyncOperationCount];
960             if (newSyncRequested && !syncOperationCount)
961                 [self sync];
962         }
963     } else {
964         NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
965         if (retries > 0) {
966             ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
967             [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
968             [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
969         } else {
970             [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
971                               withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
972             @synchronized(self) {
973                 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
974                 syncOperationCount = 0;
975                 if (newSyncRequested)
976                     [self sync];
977             }
978         }
979     }
980 }
981
982 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
983     if ([containerRequest isCancelled]) {
984         [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
985                           withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
986         [objects release];
987         objects = nil;
988         @synchronized(self) {
989             syncOperationCount = 0;
990         }
991         return;
992     }
993     // If the server listing fails, the sync should start over, so just retrying is enough
994     NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
995     if (retries > 0) {
996         ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
997         [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
998         [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
999     } else {
1000         [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1001                           withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1002         [objects release];
1003         objects = nil;
1004         @synchronized(self) {
1005             // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1006             syncOperationCount = 0;
1007             if (newSyncRequested)
1008                 [self sync];
1009         }
1010     }
1011 }
1012
1013 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1014     NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1015     if (objectRequest.responseStatusCode == 206) {
1016         ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1017         NSFileManager *fileManager = [NSFileManager defaultManager];
1018         NSError *error;
1019         PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1020         
1021         NSString *downloadsDirPath = self.tempDownloadsDirPath;
1022         if (!downloadsDirPath) {
1023             [activityFacility endActivity:activity 
1024                               withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1025             @synchronized(self) {
1026                 syncIncomplete = YES;
1027                 [self decreaseSyncOperationCount];
1028                 if (newSyncRequested && !syncOperationCount)
1029                     [self sync];
1030             }
1031             return;
1032         }
1033         
1034         PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1035         if ((storedState.filePath == nil) || ![fileManager fileExistsAtPath:storedState.filePath]) {
1036             NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1037             const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1038             char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1039             strcpy(tempFileNameCString, tempFileTemplateCString);
1040             int fileDescriptor = mkstemp(tempFileNameCString);
1041             NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1042             free(tempFileNameCString);
1043             if (fileDescriptor == -1) {
1044                 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error" 
1045                                                         message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", storedState.filePath] 
1046                                                           error:nil];
1047                 [activityFacility endActivity:activity 
1048                                   withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1049                 @synchronized(self) {
1050                     syncIncomplete = YES;
1051                     [self decreaseSyncOperationCount];
1052                     if (newSyncRequested && !syncOperationCount)
1053                         [self sync];
1054                 }
1055                 return;
1056             }
1057             close(fileDescriptor);
1058             storedState.filePath = tempFilePath;
1059             [self saveLocalState];
1060         }
1061         
1062
1063         NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1064         NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.filePath];
1065         [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
1066         [tempFileHandle writeData:[objectRequest responseData]];
1067         [tempFileHandle closeFile];
1068
1069         NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1070         missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1071         if (missingBlockIndex == NSNotFound) {
1072             NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1073             NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1074             if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath]) {
1075                 [activityFacility endActivity:activity 
1076                                   withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1077                 @synchronized(self) {
1078                     syncIncomplete = YES;
1079                     [self decreaseSyncOperationCount];
1080                     if (newSyncRequested && !syncOperationCount)
1081                         [self sync];
1082                 }
1083                 return;
1084             } else if (![fileManager fileExistsAtPath:dirPath]) {
1085                 // File doesn't exist but also the containing directory doesn't exist
1086                 // In most cases this should have been resolved as an update of the corresponding local object,
1087                 // but it never hurts to check
1088                 error = nil;
1089                 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1090                 if (error != nil) {
1091                     [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
1092                                                             message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath] 
1093                                                               error:error];
1094                     [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1095                                       withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1096                     @synchronized(self) {
1097                         syncIncomplete = YES;
1098                         [self decreaseSyncOperationCount];
1099                         if (newSyncRequested && !syncOperationCount)
1100                             [self sync];
1101                     }
1102                     return;
1103                 }
1104             }
1105             // Move file from tmp download
1106             error = nil;
1107             [fileManager moveItemAtPath:storedState.filePath toPath:filePath error:&error];
1108             if (error != nil) {
1109                 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
1110                                                         message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.filePath, filePath] 
1111                                                           error:error];
1112                 [activityFacility endActivity:activity 
1113                                   withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1114                 @synchronized(self) {
1115                     syncIncomplete = YES;
1116                     [self decreaseSyncOperationCount];
1117                     if (newSyncRequested && !syncOperationCount)
1118                         [self sync];
1119                 }
1120                 return;
1121             }
1122             [activityFacility endActivity:activity 
1123                               withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1124                                totalBytes:activity.totalBytes 
1125                              currentBytes:activity.totalBytes];
1126
1127             storedState.hash = object.hash;
1128             storedState.filePath = nil;
1129             [self saveLocalState];
1130             
1131             @synchronized(self) {
1132                 [self decreaseSyncOperationCount];
1133                 if (newSyncRequested && !syncOperationCount)
1134                     [self sync];
1135             }
1136             return;
1137         } else {
1138             if (newSyncRequested) {
1139                 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1140                                   withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1141                 @synchronized(self) {
1142                     syncIncomplete = YES;
1143                     [self decreaseSyncOperationCount];
1144                     if (!syncOperationCount)
1145                         [self sync];
1146                 }
1147                 return;
1148             } else {
1149                 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
1150                                                                                                                      object:object 
1151                                                                                                                  blockIndex:missingBlockIndex 
1152                                                                                                                   blockSize:blockSize];
1153                 newObjectRequest.delegate = self;
1154                 newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
1155                 newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
1156                 newObjectRequest.userInfo = objectRequest.userInfo;
1157                 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1158                 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1159                 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1160                     [activityFacility updateActivity:activity 
1161                                          withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1162                                                       object.name, 
1163                                                       (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1164                                           totalBytes:activity.totalBytes 
1165                                         currentBytes:(activity.currentBytes + size)];
1166                 }];
1167                 [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1168             }
1169         }
1170     } else if (objectRequest.responseStatusCode == 412) {
1171         // The object has changed on the server
1172         [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1173                           withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1174         @synchronized(self) {
1175             syncIncomplete = YES;
1176             [self decreaseSyncOperationCount];
1177             if (newSyncRequested && !syncOperationCount)
1178                 [self sync];
1179         }
1180     } else {
1181         [self requestFailed:objectRequest];
1182     }
1183 }
1184
1185 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1186     NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1187     if (objectRequest.responseStatusCode == 200) {
1188         if (newSyncRequested) {
1189             [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1190                               withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1191             @synchronized(self) {
1192                 syncIncomplete = YES;
1193                 [self decreaseSyncOperationCount];
1194                 if (!syncOperationCount)
1195                     [self sync];
1196             }
1197             return;
1198         } else {
1199             ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1200             PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1201             if ([PithosUtilities bytesOfFile:storedState.filePath] > object.bytes)
1202                 [[NSFileHandle fileHandleForWritingAtPath:storedState.filePath] truncateFileAtOffset:object.bytes];
1203             PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1204             NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.filePath 
1205                                                                     blockSize:blockSize 
1206                                                                     blockHash:blockHash 
1207                                                                    withHashes:[objectRequest hashes]];
1208             NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1209             [activityFacility endActivity:activity 
1210                               withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1211                                            object.name, 
1212                                            (100*(activity.totalBytes - [missingBlocks count]*blockSize + 0.0)/(activity.totalBytes + 0.0))] 
1213                                totalBytes:activity.totalBytes 
1214                              currentBytes:(activity.totalBytes - [missingBlocks count]*blockSize)];
1215
1216             __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
1217                                                                                                                  object:object 
1218                                                                                                              blockIndex:missingBlockIndex 
1219                                                                                                               blockSize:blockSize];
1220             newObjectRequest.delegate = self;
1221             newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
1222             newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
1223             newObjectRequest.userInfo = objectRequest.userInfo;
1224             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1225             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1226             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1227             [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1228                 [activityFacility updateActivity:activity 
1229                                      withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1230                                                   object.name, 
1231                                                   (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1232                                       totalBytes:activity.totalBytes 
1233                                     currentBytes:(activity.currentBytes + size)];
1234             }];
1235             [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1236         }
1237     } else {
1238         [self requestFailed:objectRequest];
1239     }
1240 }
1241
1242 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1243     NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1244     if (objectRequest.responseStatusCode == 201) {
1245         PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1246         storedState.isDirectory = YES;
1247         [self saveLocalState];
1248         [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1249                           withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1250         @synchronized(self) {
1251             [self decreaseSyncOperationCount];
1252             if (newSyncRequested && !syncOperationCount)
1253                 [self sync];
1254         }
1255     } else {
1256         [self requestFailed:objectRequest];
1257     }
1258 }
1259
1260 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1261     NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1262     if (objectRequest.responseStatusCode == 201) {
1263         [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1264         [self saveLocalState];
1265         [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1266                           withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1267         @synchronized(self) {
1268             [self decreaseSyncOperationCount];
1269             if (newSyncRequested && !syncOperationCount)
1270                 [self sync];
1271         }
1272     } else {
1273         [self requestFailed:objectRequest];
1274     }
1275 }
1276
1277 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1278     NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1279     ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1280     PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1281     PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1282     NSUInteger totalBytes = activity.totalBytes;
1283     NSUInteger currentBytes = activity.currentBytes;
1284     if (objectRequest.responseStatusCode == 201) {
1285         NSLog(@"Sync::object created: %@", objectRequest.url);
1286         storedState.hash = [objectRequest eTag];
1287         [self saveLocalState];
1288         [activityFacility endActivity:activity 
1289                           withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1290                            totalBytes:totalBytes 
1291                          currentBytes:totalBytes];
1292         @synchronized(self) {
1293             [self decreaseSyncOperationCount];
1294             if (newSyncRequested && !syncOperationCount)
1295                 [self sync];
1296         }
1297     } else if (objectRequest.responseStatusCode == 409) {
1298         if (newSyncRequested) {
1299             [activityFacility endActivity:activity 
1300                               withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1301             @synchronized(self) {
1302                 syncIncomplete = YES;
1303                 [self decreaseSyncOperationCount];
1304                 if (!syncOperationCount)
1305                     [self sync];
1306             }
1307             return;
1308         } else {
1309             NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1310             if (iteration == 0) {
1311                 NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
1312                 [activityFacility endActivity:activity 
1313                                   withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1314                 syncIncomplete = YES;
1315                 [self decreaseSyncOperationCount];
1316                 return;
1317             }
1318             NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
1319             NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1320                                                       withMissingHashesResponse:[objectRequest responseString]];
1321             if (totalBytes >= [missingBlocks count]*blockSize)
1322                 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1323             [activityFacility updateActivity:activity 
1324                                  withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
1325                                   totalBytes:totalBytes 
1326                                 currentBytes:currentBytes];
1327             NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1328             __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName 
1329                                                                                                                         blockSize:blockSize 
1330                                                                                                                           forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
1331                                                                                                                 missingBlockIndex:missingBlockIndex 
1332                                                                                                                    sharingAccount:nil];
1333             newContainerRequest.delegate = self;
1334             newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1335             newContainerRequest.didFailSelector = @selector(requestFailed:);
1336             newContainerRequest.userInfo = objectRequest.userInfo;
1337             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1338             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1339             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1340             [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1341             [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1342                 [activityFacility updateActivity:activity 
1343                                      withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1344                                       totalBytes:activity.totalBytes 
1345                                     currentBytes:(activity.currentBytes + size)];
1346             }];
1347             [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1348         }
1349     } else {
1350         [self requestFailed:objectRequest];
1351     }
1352 }
1353
1354 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1355     NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
1356     if (containerRequest.responseStatusCode == 202) {
1357         ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
1358         PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1359         NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1360         NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1361         missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1362         if (missingBlockIndex == NSNotFound) {
1363             NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1364             ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName 
1365                                                                                                      objectName:object.name 
1366                                                                                                     contentType:object.contentType 
1367                                                                                                       blockSize:blockSize 
1368                                                                                                       blockHash:blockHash
1369                                                                                                         forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1370                                                                                                   checkIfExists:NO 
1371                                                                                                          hashes:&hashes 
1372                                                                                                  sharingAccount:nil];
1373             newObjectRequest.delegate = self;
1374             newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
1375             newObjectRequest.didFailSelector = @selector(requestFailed:);
1376             newObjectRequest.userInfo = containerRequest.userInfo;
1377             [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1378             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1379             [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1380             [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1381         } else {
1382             if (newSyncRequested) {
1383                 [activityFacility endActivity:activity 
1384                                   withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1385                 @synchronized(self) {
1386                     syncIncomplete = YES;
1387                     [self decreaseSyncOperationCount];
1388                     if (!syncOperationCount)
1389                         [self sync];
1390                 }
1391                 return;
1392             } else {
1393                 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
1394                                                                                                                             blockSize:blockSize
1395                                                                                                                               forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1396                                                                                                                     missingBlockIndex:missingBlockIndex 
1397                                                                                                                        sharingAccount:nil];
1398                 newContainerRequest.delegate = self;
1399                 newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1400                 newContainerRequest.didFailSelector = @selector(requestFailed:);
1401                 newContainerRequest.userInfo = containerRequest.userInfo;
1402                 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1403                 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1404                 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1405                     [activityFacility updateActivity:activity 
1406                                          withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1407                                           totalBytes:activity.totalBytes 
1408                                         currentBytes:(activity.currentBytes + size)];
1409                 }];
1410                 [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1411             }
1412         }
1413     } else {
1414         [self requestFailed:containerRequest];
1415     }
1416 }
1417
1418 - (void)requestFailed:(ASIPithosRequest *)request {
1419     if ([request isCancelled]) {
1420         [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1421                           withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1422         syncIncomplete = YES;
1423         [self decreaseSyncOperationCount];
1424         return;
1425     }
1426     if (newSyncRequested) {
1427         [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1428                           withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1429         @synchronized(self) {
1430             syncIncomplete = YES;
1431             [self decreaseSyncOperationCount];
1432             if (!syncOperationCount)
1433                 [self sync];
1434         }
1435         return;
1436     }
1437     NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1438     if (retries > 0) {
1439         ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
1440         [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1441         [queue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1442     } else {
1443         [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1444                           withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1445         syncIncomplete = YES;
1446         [self decreaseSyncOperationCount];
1447     }
1448 }
1449
1450
1451 @end