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