5 // Copyright 2011 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
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.
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.
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.
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"
48 @interface PithosSyncDaemon (Private)
49 - (NSString *)pithosStateFilePath;
50 - (void)saveLocalState;
52 - (BOOL)moveToTempTrashFile:(NSString *)filePath;
53 - (void)emptyTempTrash;
54 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
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;
62 - (void)increaseSyncOperationCount;
63 - (void)decreaseSyncOperationCount;
67 @implementation PithosSyncDaemon
68 @synthesize blockHash, blockSize, lastModified, lastCompletedSync, remoteObjects, storedLocalObjectStates, currentLocalObjectStates;
69 @synthesize directoryPath, containerDirectoryPath, pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
72 #pragma Object Lifecycle
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;
83 syncOperationCount = 0;
84 newSyncRequested = NO;
85 containerDirectoryPath = [[directoryPath stringByAppendingPathComponent:containerName] copy];
87 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
89 NSFileManager *fileManager = [NSFileManager defaultManager];
90 if (resetLocalState) {
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]
97 if (self.tempDownloadsDirPath) {
99 for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
101 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
102 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath]
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]
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];
122 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
125 networkQueue = [[ASINetworkQueue alloc] init];
126 networkQueue.showAccurateProgress = YES;
127 networkQueue.shouldCancelAllRequestsOnFailure = NO;
130 [[NSNotificationCenter defaultCenter] addObserver:self
131 selector:@selector(applicationWillTerminate:)
132 name:NSApplicationWillTerminateNotification
133 object:[NSApplication sharedApplication]];
135 timer = [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(sync) userInfo:nil repeats:YES] retain];
143 [[NSNotificationCenter defaultCenter] removeObserver:self];
144 [networkQueue cancelAllOperations];
145 [networkQueue release];
148 [tempTrashDirPath release];
149 [tempDownloadsDirPath release];
150 [pithosStateFilePath release];
151 [containerDirectoryPath release];
152 [currentLocalObjectStates release];
153 [storedLocalObjectStates release];
154 [remoteObjects release];
156 [lastCompletedSync release];
157 [lastModified release];
159 [containerName release];
160 [directoryPath release];
165 #pragma mark Background
167 - (void)fileActionFailedAlert:(NSDictionary *)args {
168 [PithosUtilities fileActionFailedAlertWithTitle:[args objectForKey:@"title"]
169 message:[args objectForKey:@"message"]
170 error:[args objectForKey:@"error"]];
173 - (void)fileActionFailedAlertWithTitle:(NSString *)title
174 message:(NSString *)message
175 error:(NSError *)error {
176 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithObjectsAndKeys:
181 [args setObject:error forKey:@"error"];
182 [self performSelectorOnMainThread:@selector(fileActionFailedAlert:)
187 - (void)startAndEndActivity:(NSDictionary *)args {
188 [activityFacility startAndEndActivityWithType:[[args objectForKey:@"type"] intValue] message:[args objectForKey:@"message"]];
191 - (void)startAndEndActivityWithType:(PithosActivityType)type message:(NSString *)message {
192 NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
193 [NSNumber numberWithInt:type], @"type",
196 [self performSelectorOnMainThread:@selector(startAndEndActivity:)
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]];
210 [activityFacility updateActivity:[args objectForKey:@"activity"]
211 withMessage:[args objectForKey:@"message"]];
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",
224 [args setObject:message forKey:@"message"];
225 [self performSelectorOnMainThread:@selector(updateActivity:)
230 - (void)updateActivity:(PithosActivity *)activity withMessage:(NSString *)message {
231 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithObjectsAndKeys:
232 activity, @"activity",
235 [args setObject:message forKey:@"message"];
236 [self performSelectorOnMainThread:@selector(updateActivity:)
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]];
250 [activityFacility endActivity:[args objectForKey:@"activity"]
251 withMessage:[args objectForKey:@"message"]];
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",
264 [args setObject:message forKey:@"message"];
265 [self performSelectorOnMainThread:@selector(endActivity:)
270 - (void)endActivity:(PithosActivity *)activity withMessage:(NSString *)message {
271 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithObjectsAndKeys:
272 activity, @"activity",
275 [args setObject:message forKey:@"message"];
276 [self performSelectorOnMainThread:@selector(endActivity:)
282 #pragma mark Observers
284 - (void)applicationWillTerminate:(NSNotification *)notification {
285 [self saveLocalState];
289 #pragma mark Properties
291 - (NSString *)pithosStateFilePath {
292 if (!pithosStateFilePath)
293 pithosStateFilePath = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"PithosLocalObjectStates.archive"] retain];
294 return [[pithosStateFilePath copy] autorelease];
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
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];
313 tempDownloadsDirPath = nil;
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);
325 if (tempDownloadsDirPath)
326 [userDefaults setObject:tempDownloadsDirPath forKey:@"PithosSyncTempDownloadsDirPath"];
327 [tempDownloadsDirPath retain];
329 return [[tempDownloadsDirPath copy] autorelease];
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
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];
348 tempTrashDirPath = nil;
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);
360 if (tempTrashDirPath)
361 [userDefaults setObject:tempTrashDirPath forKey:@"PithosSyncTempTrashDirPath"];
362 [tempTrashDirPath retain];
364 return [[tempTrashDirPath copy] autorelease];
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];
378 - (void)increaseSyncOperationCount {
379 @synchronized(self) {
380 syncOperationCount++;
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]];
393 [self emptyTempTrash];
399 @synchronized(self) {
400 if (syncOperationCount) {
401 // If at least one operation is running return
402 newSyncRequested = YES;
405 // The first operation is the server listing
406 syncOperationCount = 1;
407 newSyncRequested = NO;
412 NSFileManager *fileManager = [NSFileManager defaultManager];
414 NSError *error = nil;
415 if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
416 if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] ||
418 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
419 message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", containerDirectoryPath]
421 @synchronized(self) {
422 syncOperationCount = 0;
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]
430 @synchronized(self) {
431 syncOperationCount = 0;
436 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName
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",
461 [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh]];
464 - (void)emptyTempTrash {
465 NSString *trashDirPath = self.tempTrashDirPath;
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];
472 [self fileActionFailedAlertWithTitle:@"Directory Contents Error"
473 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
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]]];
482 // syncOperationCount = 1;
483 // [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
485 // [self fileActionFailedAlertWithTitle:@"Move to Trash Error"
486 // message:@"Cannot move files to Trash"
489 // syncOperationCount = 0;
491 for (NSString *subPath in subPaths) {
492 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
494 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
495 [self fileActionFailedAlertWithTitle:@"Remove File Error"
496 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
503 - (BOOL)moveToTempTrashFile:(NSString *)filePath {
504 NSString *trashDirPath = self.tempTrashDirPath;
505 if (!tempTrashDirPath)
507 NSFileManager *fileManager = [NSFileManager defaultManager];
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];
517 [self fileActionFailedAlertWithTitle:@"Directory Contents Error"
518 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
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]
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]
535 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
537 currentState.filePath = newFilePath;
538 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
539 [currentLocalObjectStates removeObjectForKey:filePath];
541 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
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];
552 currentState.filePath = newSubFilePath;
553 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
554 [currentLocalObjectStates removeObjectForKey:subFilePath];
556 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
559 forKey:newSubFilePath];
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]
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]
576 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
578 currentState.filePath = newFilePath;
579 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
580 [currentLocalObjectStates removeObjectForKey:filePath];
582 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
591 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
592 NSUInteger hashLength = [hash length];
593 if ((hashLength != 32) && (hashLength != 64))
595 PithosLocalObjectState *localState;
596 NSFileManager *fileManager = [NSFileManager defaultManager];
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]
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]
619 localState.filePath = filePath;
620 [currentLocalObjectStates setObject:localState forKey:filePath];
621 [currentLocalObjectStates removeObjectForKey:localFilePath];
630 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
631 localFilePath:(NSString *)filePath {
632 NSFileManager *fileManager = [NSFileManager defaultManager];
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];
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);
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]
658 directoryCreated = YES;
659 storedState.isDirectory = YES;
660 [self saveLocalState];
663 NSLog(@"Sync::local directory object exists: %@", filePath);
664 directoryCreated = YES;
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);
676 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
677 [self fileActionFailedAlertWithTitle:@"Create File Error"
678 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
682 storedState.hash = object.hash;
683 storedState.filePath = nil;
684 [self saveLocalState];
687 NSLog(@"Sync::local zero length object exists: %@", filePath);
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]];
700 [self increaseSyncOperationCount];
701 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
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
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",
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)];
735 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
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
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]
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
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",
774 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
779 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
780 object:(ASIPithosObject *)object
781 localFilePath:(NSString *)filePath {
782 [self increaseSyncOperationCount];
783 NSFileManager *fileManager = [NSFileManager defaultManager];
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];
794 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName
795 objectName:object.name
797 contentType:@"application/directory"
799 contentDisposition:nil
802 isPublic:ASIPithosObjectRequestPublicIgnore
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",
822 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
823 } else if (!currentState.exists) {
824 // Delete remote object
826 // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
827 syncIncomplete = YES;
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
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",
855 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
857 syncIncomplete = YES;
858 [self decreaseSyncOperationCount];
861 syncIncomplete = YES;
862 [self decreaseSyncOperationCount];
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];
872 NSError *error = nil;
873 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
874 if (object.contentType == nil)
875 object.contentType = @"application/octet-stream";
877 NSLog(@"contentType detection error: %@", error);
878 NSArray *hashes = nil;
879 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName
880 objectName:object.name
881 contentType:object.contentType
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]
896 [self updateActivity:activity withMessage:activity.message];
897 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
898 [NSDictionary dictionaryWithObjectsAndKeys:
899 object, @"pithosObject",
900 filePath, @"filePath",
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",
912 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
914 syncIncomplete = YES;
915 [self decreaseSyncOperationCount];
922 #pragma mark ASIHTTPRequestDelegate
924 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
925 [self performSelectorInBackground:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) withObject:request];
928 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
929 [self performSelectorInBackground:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) withObject:request];
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];
941 [objects addObjectsFromArray:someObjects];
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];
954 // Do an additional request to fetch more objects
955 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName
957 marker:[[someObjects lastObject] name]
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]]];
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];
980 [self fileActionFailedAlertWithTitle:@"Directory Contents Error"
981 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
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)
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];
997 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
998 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
1000 blockSize:blockSize]
1003 [self saveLocalState];
1005 for (NSString *objectName in remoteObjects) {
1006 if (![storedLocalObjectStates objectForKey:objectName])
1007 [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
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);
1018 PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
1019 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1020 if (!currentLocalObjectState)
1021 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
1023 blockSize:blockSize];
1024 if (currentLocalObjectState.isDirectory)
1025 object.contentType = @"application/directory";
1027 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1028 ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
1030 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1031 remoteObjectState.isDirectory = YES;
1032 object.contentType = @"application/directory";
1034 remoteObjectState.hash = remoteObject.hash;
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];
1058 // Local state has changed
1059 if (!serverStateHasChanged) {
1060 // Server state hasn't changed
1061 [self updateServerStateWithCurrentState:currentLocalObjectState
1063 localFilePath:filePath];
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];
1075 storedLocalObjectState.hash = remoteObjectState.hash;
1076 [self saveLocalState];
1078 // Conflict, we ask the user which change to keep
1079 NSString *informativeText;
1080 NSString *firstButtonText;
1081 NSString *secondButtonText;
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";
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";
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
1113 localFilePath:filePath];
1119 @synchronized(self) {
1120 [self decreaseSyncOperationCount];
1121 if (newSyncRequested && !syncOperationCount)
1125 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
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]]];
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)
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"]];
1151 @synchronized(self) {
1152 syncOperationCount = 0;
1156 // If the server listing fails, the sync should start over, so just retrying is enough
1157 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
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]]];
1163 [self endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1164 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
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)
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];
1184 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
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)
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]
1212 [self endActivity:activity
1213 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1214 @synchronized(self) {
1215 syncIncomplete = YES;
1216 [self decreaseSyncOperationCount];
1217 if (newSyncRequested && !syncOperationCount)
1222 close(fileDescriptor);
1223 storedState.filePath = tempFilePath;
1224 [self saveLocalState];
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];
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)
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
1254 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1256 [self fileActionFailedAlertWithTitle:@"Create Directory Error"
1257 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
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)
1270 // Move file from tmp download
1272 [fileManager moveItemAtPath:storedState.filePath toPath:filePath error:&error];
1274 [self fileActionFailedAlertWithTitle:@"Move File Error"
1275 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.filePath, filePath]
1277 [self endActivity:activity
1278 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1279 @synchronized(self) {
1280 syncIncomplete = YES;
1281 [self decreaseSyncOperationCount];
1282 if (newSyncRequested && !syncOperationCount)
1287 [self endActivity:activity
1288 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1289 totalBytes:activity.totalBytes
1290 currentBytes:activity.totalBytes];
1292 storedState.hash = object.hash;
1293 storedState.filePath = nil;
1294 [self saveLocalState];
1296 @synchronized(self) {
1297 [self decreaseSyncOperationCount];
1298 if (newSyncRequested && !syncOperationCount)
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)
1314 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
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%%)",
1328 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1329 totalBytes:activity.totalBytes
1330 currentBytes:(activity.currentBytes + size)];
1332 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
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)
1346 [self requestFailed:objectRequest];
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)
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
1374 withHashes:[objectRequest hashes]];
1375 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1376 [self endActivity:activity
1377 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1379 (100*(activity.totalBytes - [missingBlocks count]*blockSize + 0.0)/(activity.totalBytes + 0.0))]
1380 totalBytes:activity.totalBytes
1381 currentBytes:(activity.totalBytes - [missingBlocks count]*blockSize)];
1383 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
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%%)",
1399 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1400 totalBytes:activity.totalBytes
1401 currentBytes:(activity.currentBytes + size)];
1403 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1406 [self requestFailed:objectRequest];
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)
1426 [self requestFailed:objectRequest];
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)
1445 [self requestFailed:objectRequest];
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)
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)
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];
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
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)];
1522 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1525 [self requestFailed:objectRequest];
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
1546 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
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]]];
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)
1571 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
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)];
1588 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1592 [self requestFailed:containerRequest];
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];
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)
1617 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
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]]];
1623 [self endActivity:[request.userInfo objectForKey:@"activity"]
1624 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1625 syncIncomplete = YES;
1626 [self decreaseSyncOperationCount];