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 // Remote updated info
638 NSError *remoteError;
639 BOOL remoteIsDirectory;
640 BOOL remoteObjectExists = [PithosUtilities objectExistsAtContainerName:containerName
641 objectName:object.name
643 isDirectory:&remoteIsDirectory
645 if (!object || !object.hash) {
646 // Delete local object
647 if (remoteObjectExists) {
648 // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
649 syncIncomplete = YES;
651 NSLog(@"Sync::delete local object: %@", filePath);
652 if (!fileExists || [self moveToTempTrashFile:filePath]) {
653 [self startAndEndActivityWithType:PithosActivityOther
654 message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]];
655 [storedLocalObjectStates removeObjectForKey:object.name];
656 [self saveLocalState];
658 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
659 // Create local directory object
660 if (!remoteObjectExists || !remoteIsDirectory) {
661 // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
662 syncIncomplete = YES;
665 NSLog(@"Sync::create local directory object: %@", filePath);
666 BOOL directoryCreated = NO;
667 if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath])) {
668 NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
670 if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
671 [self fileActionFailedAlertWithTitle:@"Create Directory Error"
672 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
675 directoryCreated = YES;
676 storedState.isDirectory = YES;
677 [self saveLocalState];
680 NSLog(@"Sync::local directory object exists: %@", filePath);
681 directoryCreated = YES;
683 if (directoryCreated)
684 [self startAndEndActivityWithType:PithosActivityOther
685 message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name]];
686 } else if (object.bytes == 0) {
687 // Create local object with zero length
688 if (!remoteObjectExists || remoteIsDirectory) {
689 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
690 syncIncomplete = YES;
693 NSLog(@"Sync::create local zero length object: %@", filePath);
694 BOOL fileCreated = NO;
695 if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath])) {
696 NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
698 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
699 [self fileActionFailedAlertWithTitle:@"Create File Error"
700 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
704 storedState.hash = object.hash;
705 storedState.filePath = nil;
706 [self saveLocalState];
709 NSLog(@"Sync::local zero length object exists: %@", filePath);
713 [self startAndEndActivityWithType:PithosActivityOther
714 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
715 } else if (storedState.filePath == nil) {
716 // Create new local object
717 if (!remoteObjectExists || remoteIsDirectory) {
718 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
719 syncIncomplete = YES;
722 // Check first if a local copy exists
723 if ([self findLocalCopyForObjectWithHash:object.hash forFile:filePath]) {
724 [self startAndEndActivityWithType:PithosActivityOther
725 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
727 [self increaseSyncOperationCount];
728 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
731 blockSize:blockSize];
732 objectRequest.delegate = self;
733 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
734 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
735 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
736 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name]
737 totalBytes:object.bytes
739 [self updateActivity:activity withMessage:activity.message];
740 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
741 object, @"pithosObject",
742 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(blockSize + 0.0)))], @"missingBlocks",
743 [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
744 filePath, @"filePath",
745 activity, @"activity",
746 [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage",
747 [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage",
748 [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage",
749 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
750 [NSNumber numberWithUnsignedInteger:10], @"retries",
751 NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector",
752 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
754 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
755 [activityFacility updateActivity:activity
756 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
757 [[objectRequest.userInfo valueForKey:@"pithosObject"] name],
758 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
759 totalBytes:activity.totalBytes
760 currentBytes:(activity.currentBytes + size)];
762 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
765 // Resume local object download
766 if (!remoteObjectExists || remoteIsDirectory) {
767 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
768 syncIncomplete = YES;
771 // Check first if a local copy exists
772 if ([self findLocalCopyForObjectWithHash:object.hash forFile:filePath]) {
773 [self startAndEndActivityWithType:PithosActivityOther
774 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
775 // Delete incomplete temp download
777 if (![fileManager removeItemAtPath:storedState.filePath error:&error] || error) {
778 [self fileActionFailedAlertWithTitle:@"Remove File Error"
779 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", storedState.filePath]
783 [self increaseSyncOperationCount];
784 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithContainerName:containerName
785 objectName:object.name];
786 objectRequest.delegate = self;
787 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
788 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
789 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
790 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name]
791 totalBytes:object.bytes
793 [self updateActivity:activity withMessage:activity.message];
794 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
795 object, @"pithosObject",
796 filePath, @"filePath",
797 activity, @"activity",
798 [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage",
799 [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage",
800 [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage",
801 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
802 [NSNumber numberWithUnsignedInteger:10], @"retries",
803 NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector",
804 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
806 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
811 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
812 object:(ASIPithosObject *)object
813 localFilePath:(NSString *)filePath {
814 [self increaseSyncOperationCount];
815 NSFileManager *fileManager = [NSFileManager defaultManager];
817 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
818 if (currentState.isDirectory) {
819 // Create remote directory object
820 if (!fileExists || !isDirectory) {
821 // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
822 syncIncomplete = YES;
823 [self decreaseSyncOperationCount];
826 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName
827 objectName:object.name
829 contentType:@"application/directory"
831 contentDisposition:nil
834 isPublic:ASIPithosObjectRequestPublicIgnore
837 objectRequest.delegate = self;
838 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
839 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
840 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
841 message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]];
842 [self updateActivity:activity withMessage:activity.message];
843 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
844 object, @"pithosObject",
845 activity, @"activity",
846 [NSString stringWithFormat:@"Sync: Creating directory '%@' (stopped)", object.name], @"stoppedActivityMessage",
847 [NSString stringWithFormat:@"Sync: Creating directory '%@' (failed)", object.name], @"failedActivityMessage",
848 [NSString stringWithFormat:@"Sync: Creating directory '%@' (finished)", object.name], @"finishedActivityMessage",
849 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
850 [NSNumber numberWithUnsignedInteger:10], @"retries",
851 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
852 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
854 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
855 } else if (!currentState.exists) {
856 // Delete remote object
858 // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
859 syncIncomplete = YES;
862 if ([PithosUtilities isContentTypeDirectory:object.contentType])
863 safeName = [PithosUtilities safeSubdirNameForContainerName:@"trash"
864 subdirName:object.name];
866 safeName = [PithosUtilities safeObjectNameForContainerName:@"trash"
867 objectName:object.name];
869 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:containerName
870 objectName:object.name
871 destinationContainerName:@"trash"
872 destinationObjectName:safeName
875 objectRequest.delegate = self;
876 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
877 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
878 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
879 message:[NSString stringWithFormat:@"Sync: Moving to trash '%@'", object.name]];
880 [self updateActivity:activity withMessage:activity.message];
881 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
882 object, @"pithosObject",
883 activity, @"activity",
884 [NSString stringWithFormat:@"Sync: Moving to trash '%@' (stopped)", object.name], @"stoppedActivityMessage",
885 [NSString stringWithFormat:@"Sync: Moving to trash '%@' (failed)", object.name], @"failedActivityMessage",
886 [NSString stringWithFormat:@"Sync: Moving to trash '%@' (finished)", object.name], @"finishedActivityMessage",
887 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
888 [NSNumber numberWithUnsignedInteger:10], @"retries",
889 NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector",
890 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
892 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
894 syncIncomplete = YES;
895 [self decreaseSyncOperationCount];
898 syncIncomplete = YES;
899 [self decreaseSyncOperationCount];
902 // Upload file to remote object
903 if (!fileExists || isDirectory) {
904 // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
905 syncIncomplete = YES;
906 [self decreaseSyncOperationCount];
909 NSError *error = nil;
910 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
911 if (object.contentType == nil)
912 object.contentType = @"application/octet-stream";
914 NSLog(@"contentType detection error: %@", error);
915 NSArray *hashes = nil;
916 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName
917 objectName:object.name
918 contentType:object.contentType
926 objectRequest.delegate = self;
927 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
928 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
929 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
930 message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
931 totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
933 [self updateActivity:activity withMessage:activity.message];
934 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
935 [NSDictionary dictionaryWithObjectsAndKeys:
936 object, @"pithosObject",
937 filePath, @"filePath",
939 [NSNumber numberWithUnsignedInteger:10], @"iteration",
940 activity, @"activity",
941 [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage",
942 [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage",
943 [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage",
944 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
945 [NSNumber numberWithUnsignedInteger:10], @"retries",
946 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
947 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
949 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
951 syncIncomplete = YES;
952 [self decreaseSyncOperationCount];
959 #pragma mark ASIHTTPRequestDelegate
961 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
962 [self performSelectorInBackground:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) withObject:request];
965 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
966 [self performSelectorInBackground:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) withObject:request];
969 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
970 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
971 NSLog(@"Sync::list request finished: %@", containerRequest.url);
972 if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
973 if (containerRequest.responseStatusCode == 200) {
974 NSArray *someObjects = [containerRequest objects];
975 if (objects == nil) {
976 objects = [[NSMutableArray alloc] initWithArray:someObjects];
978 [objects addObjectsFromArray:someObjects];
980 if ([someObjects count] < 10000) {
981 self.blockHash = [containerRequest blockHash];
982 self.blockSize = [containerRequest blockSize];
983 self.lastModified = [containerRequest lastModified];
984 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
985 for (ASIPithosObject *object in objects) {
986 [remoteObjects setObject:object forKey:object.name];
991 // Do an additional request to fetch more objects
992 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName
994 marker:[[someObjects lastObject] name]
1001 ifModifiedSince:lastModified];
1002 newContainerRequest.delegate = self;
1003 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1004 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1005 newContainerRequest.userInfo = newContainerRequest.userInfo;
1006 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1007 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1011 [self endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1012 withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1013 NSFileManager *fileManager = [NSFileManager defaultManager];
1014 NSError *error = nil;
1015 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
1017 [self fileActionFailedAlertWithTitle:@"Directory Contents Error"
1018 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
1020 [self startAndEndActivityWithType:PithosActivityOther message:@"Sync: Failed to read contents of sync directory"];
1021 @synchronized(self) {
1022 // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccessfully
1023 syncOperationCount = 0;
1024 if (newSyncRequested)
1029 self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:[subPaths count]];
1030 for (NSString *objectName in subPaths) {
1031 if (![storedLocalObjectStates objectForKey:objectName]) {
1032 [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1034 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1035 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
1037 blockSize:blockSize]
1040 [self saveLocalState];
1042 for (NSString *objectName in remoteObjects) {
1043 if (![storedLocalObjectStates objectForKey:objectName])
1044 [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1047 for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1048 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1049 if ([objectName hasSuffix:@"/"])
1050 filePath = [filePath stringByAppendingString:@":"];
1051 ASIPithosObject *object = [ASIPithosObject object];
1052 object.name = objectName;
1053 NSLog(@"Sync::object name: %@", objectName);
1055 PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
1056 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1057 if (!currentLocalObjectState)
1058 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
1060 blockSize:blockSize];
1061 if (currentLocalObjectState.isDirectory)
1062 object.contentType = @"application/directory";
1064 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1065 ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
1067 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1068 remoteObjectState.isDirectory = YES;
1069 object.contentType = @"application/directory";
1071 remoteObjectState.hash = remoteObject.hash;
1075 BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1076 BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1077 NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1078 if (!localStateHasChanged) {
1079 // Local state hasn't changed
1080 if (serverStateHasChanged) {
1081 // Server state has changed
1082 // Update local state to match that of the server
1083 object.bytes = remoteObject.bytes;
1084 object.version = remoteObject.version;
1085 object.contentType = remoteObject.contentType;
1086 object.hash = remoteObject.hash;
1087 [self updateLocalStateWithObject:object localFilePath:filePath];
1088 } else if (!remoteObject && !currentLocalObjectState.exists) {
1089 // Server state hasn't changed
1090 // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1091 [storedLocalObjectStates removeObjectForKey:objectName];
1092 [self saveLocalState];
1095 // Local state has changed
1096 if (!serverStateHasChanged) {
1097 // Server state hasn't changed
1098 [self updateServerStateWithCurrentState:currentLocalObjectState
1100 localFilePath:filePath];
1102 // Server state has also changed
1103 if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1104 // Both did the same change (directory)
1105 storedLocalObjectState.isDirectory = YES;
1106 [self saveLocalState];
1107 } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1108 // Both did the same change (object edit or delete)
1109 if (!remoteObjectState.exists)
1110 [storedLocalObjectStates removeObjectForKey:object.name];
1112 storedLocalObjectState.hash = remoteObjectState.hash;
1113 [self saveLocalState];
1115 // Conflict, we ask the user which change to keep
1116 NSString *informativeText;
1117 NSString *firstButtonText;
1118 NSString *secondButtonText;
1120 if (!remoteObjectState.exists) {
1121 // Remote object has been deleted
1122 informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
1123 firstButtonText = @"Delete local file";
1124 secondButtonText = @"Upload file to server";
1125 } else if (!currentLocalObjectState.exists) {
1126 informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
1127 firstButtonText = @"Download file from server";
1128 secondButtonText = @"Delete file on server";
1130 informativeText = [NSString stringWithFormat:@"'%@' has been modifed both locally and on the server.", object.name];
1131 firstButtonText = @"Keep server version";
1132 secondButtonText = @"Keep local version";
1134 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1135 [alert setMessageText:@"Conflict"];
1136 [alert setInformativeText:informativeText];
1137 [alert addButtonWithTitle:firstButtonText];
1138 [alert addButtonWithTitle:secondButtonText];
1139 [alert addButtonWithTitle:@"Do nothing"];
1140 NSInteger choice = [alert runModal];
1141 if (choice == NSAlertFirstButtonReturn) {
1142 object.bytes = remoteObject.bytes;
1143 object.version = remoteObject.version;
1144 object.contentType = remoteObject.contentType;
1145 object.hash = remoteObject.hash;
1146 [self updateLocalStateWithObject:object localFilePath:filePath];
1147 } if (choice == NSAlertSecondButtonReturn) {
1148 [self updateServerStateWithCurrentState:currentLocalObjectState
1150 localFilePath:filePath];
1156 @synchronized(self) {
1157 [self decreaseSyncOperationCount];
1158 if (newSyncRequested && !syncOperationCount)
1162 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1164 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1165 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1166 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1168 [self endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1169 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1170 @synchronized(self) {
1171 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1172 syncOperationCount = 0;
1173 if (newSyncRequested)
1181 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1182 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1183 if ([containerRequest isCancelled]) {
1184 [self endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1185 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1188 @synchronized(self) {
1189 syncOperationCount = 0;
1193 // If the server listing fails, the sync should start over, so just retrying is enough
1194 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1196 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1197 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1198 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1200 [self endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1201 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1204 @synchronized(self) {
1205 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1206 syncOperationCount = 0;
1207 if (newSyncRequested)
1214 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1215 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1216 NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1217 if (objectRequest.responseStatusCode == 206) {
1218 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1219 NSFileManager *fileManager = [NSFileManager defaultManager];
1221 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1223 NSString *downloadsDirPath = self.tempDownloadsDirPath;
1224 if (!downloadsDirPath) {
1225 [self endActivity:activity
1226 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1227 @synchronized(self) {
1228 syncIncomplete = YES;
1229 [self decreaseSyncOperationCount];
1230 if (newSyncRequested && !syncOperationCount)
1236 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1237 if ((storedState.filePath == nil) || ![fileManager fileExistsAtPath:storedState.filePath]) {
1238 NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1239 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1240 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1241 strcpy(tempFileNameCString, tempFileTemplateCString);
1242 int fileDescriptor = mkstemp(tempFileNameCString);
1243 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1244 free(tempFileNameCString);
1245 if (fileDescriptor == -1) {
1246 [self fileActionFailedAlertWithTitle:@"Create Temporary File Error"
1247 message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", storedState.filePath]
1249 [self endActivity:activity
1250 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1251 @synchronized(self) {
1252 syncIncomplete = YES;
1253 [self decreaseSyncOperationCount];
1254 if (newSyncRequested && !syncOperationCount)
1259 close(fileDescriptor);
1260 storedState.filePath = tempFilePath;
1261 [self saveLocalState];
1265 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1266 NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.filePath];
1267 [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
1268 [tempFileHandle writeData:[objectRequest responseData]];
1269 [tempFileHandle closeFile];
1271 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1272 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1273 if (missingBlockIndex == NSNotFound) {
1274 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1275 NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1276 if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath]) {
1277 [self endActivity:activity
1278 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1279 @synchronized(self) {
1280 syncIncomplete = YES;
1281 [self decreaseSyncOperationCount];
1282 if (newSyncRequested && !syncOperationCount)
1286 } else if (![fileManager fileExistsAtPath:dirPath]) {
1287 // File doesn't exist but also the containing directory doesn't exist
1288 // In most cases this should have been resolved as an update of the corresponding local object,
1289 // but it never hurts to check
1291 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1293 [self fileActionFailedAlertWithTitle:@"Create Directory Error"
1294 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
1296 [self endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1297 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1298 @synchronized(self) {
1299 syncIncomplete = YES;
1300 [self decreaseSyncOperationCount];
1301 if (newSyncRequested && !syncOperationCount)
1307 // Move file from tmp download
1309 [fileManager moveItemAtPath:storedState.filePath toPath:filePath error:&error];
1311 [self fileActionFailedAlertWithTitle:@"Move File Error"
1312 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.filePath, filePath]
1314 [self endActivity:activity
1315 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1316 @synchronized(self) {
1317 syncIncomplete = YES;
1318 [self decreaseSyncOperationCount];
1319 if (newSyncRequested && !syncOperationCount)
1324 [self endActivity:activity
1325 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1326 totalBytes:activity.totalBytes
1327 currentBytes:activity.totalBytes];
1329 storedState.hash = object.hash;
1330 storedState.filePath = nil;
1331 [self saveLocalState];
1333 @synchronized(self) {
1334 [self decreaseSyncOperationCount];
1335 if (newSyncRequested && !syncOperationCount)
1340 if (newSyncRequested) {
1341 [self endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1342 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1343 @synchronized(self) {
1344 syncIncomplete = YES;
1345 [self decreaseSyncOperationCount];
1346 if (!syncOperationCount)
1351 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
1353 blockIndex:missingBlockIndex
1354 blockSize:blockSize];
1355 newObjectRequest.delegate = self;
1356 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1357 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1358 newObjectRequest.userInfo = objectRequest.userInfo;
1359 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1360 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1361 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1362 [activityFacility updateActivity:activity
1363 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1365 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1366 totalBytes:activity.totalBytes
1367 currentBytes:(activity.currentBytes + size)];
1369 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1372 } else if (objectRequest.responseStatusCode == 412) {
1373 // The object has changed on the server
1374 [self endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1375 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1376 @synchronized(self) {
1377 syncIncomplete = YES;
1378 [self decreaseSyncOperationCount];
1379 if (newSyncRequested && !syncOperationCount)
1383 [self requestFailed:objectRequest];
1388 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1389 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1390 NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1391 if (objectRequest.responseStatusCode == 200) {
1392 if (newSyncRequested) {
1393 [self endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1394 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1395 @synchronized(self) {
1396 syncIncomplete = YES;
1397 [self decreaseSyncOperationCount];
1398 if (!syncOperationCount)
1403 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1404 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1405 if ([PithosUtilities bytesOfFile:storedState.filePath] > object.bytes)
1406 [[NSFileHandle fileHandleForWritingAtPath:storedState.filePath] truncateFileAtOffset:object.bytes];
1407 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1408 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.filePath
1411 withHashes:[objectRequest hashes]];
1412 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1413 [self endActivity:activity
1414 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1416 (100*(activity.totalBytes - [missingBlocks count]*blockSize + 0.0)/(activity.totalBytes + 0.0))]
1417 totalBytes:activity.totalBytes
1418 currentBytes:(activity.totalBytes - [missingBlocks count]*blockSize)];
1420 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
1422 blockIndex:missingBlockIndex
1423 blockSize:blockSize];
1424 newObjectRequest.delegate = self;
1425 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1426 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1427 newObjectRequest.userInfo = objectRequest.userInfo;
1428 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1429 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1430 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1431 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1432 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1433 [activityFacility updateActivity:activity
1434 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1436 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1437 totalBytes:activity.totalBytes
1438 currentBytes:(activity.currentBytes + size)];
1440 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1443 [self requestFailed:objectRequest];
1448 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1449 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1450 NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1451 if (objectRequest.responseStatusCode == 201) {
1452 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1453 storedState.isDirectory = YES;
1454 [self saveLocalState];
1455 [self endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1456 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1457 @synchronized(self) {
1458 [self decreaseSyncOperationCount];
1459 if (newSyncRequested && !syncOperationCount)
1463 [self requestFailed:objectRequest];
1468 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1469 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1470 NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1471 if (objectRequest.responseStatusCode == 201) {
1472 [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1473 [self saveLocalState];
1474 [self endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1475 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1476 @synchronized(self) {
1477 [self decreaseSyncOperationCount];
1478 if (newSyncRequested && !syncOperationCount)
1482 [self requestFailed:objectRequest];
1487 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1488 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1489 NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1490 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1491 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1492 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1493 NSUInteger totalBytes = activity.totalBytes;
1494 NSUInteger currentBytes = activity.currentBytes;
1495 if (objectRequest.responseStatusCode == 201) {
1496 NSLog(@"Sync::object created: %@", objectRequest.url);
1497 storedState.hash = [objectRequest eTag];
1498 [self saveLocalState];
1499 [self endActivity:activity
1500 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1501 totalBytes:totalBytes
1502 currentBytes:totalBytes];
1503 @synchronized(self) {
1504 [self decreaseSyncOperationCount];
1505 if (newSyncRequested && !syncOperationCount)
1508 } else if (objectRequest.responseStatusCode == 409) {
1509 if (newSyncRequested) {
1510 [self endActivity:activity
1511 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1512 @synchronized(self) {
1513 syncIncomplete = YES;
1514 [self decreaseSyncOperationCount];
1515 if (!syncOperationCount)
1520 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1521 if (iteration == 0) {
1522 NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
1523 [self endActivity:activity
1524 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1525 syncIncomplete = YES;
1526 [self decreaseSyncOperationCount];
1529 NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
1530 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1531 withMissingHashesResponse:[objectRequest responseString]];
1532 if (totalBytes >= [missingBlocks count]*blockSize)
1533 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1534 [self updateActivity:activity
1535 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
1536 totalBytes:totalBytes
1537 currentBytes:currentBytes];
1538 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1539 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
1541 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1542 missingBlockIndex:missingBlockIndex
1543 sharingAccount:nil];
1544 newContainerRequest.delegate = self;
1545 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1546 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1547 newContainerRequest.userInfo = objectRequest.userInfo;
1548 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1549 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1550 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1551 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1552 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1553 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1554 [activityFacility updateActivity:activity
1555 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1556 totalBytes:activity.totalBytes
1557 currentBytes:(activity.currentBytes + size)];
1559 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1562 [self requestFailed:objectRequest];
1567 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1568 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1569 NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
1570 if (containerRequest.responseStatusCode == 202) {
1571 ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
1572 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1573 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1574 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1575 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1576 if (missingBlockIndex == NSNotFound) {
1577 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1578 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName
1579 objectName:object.name
1580 contentType:object.contentType
1583 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1586 sharingAccount:nil];
1587 newObjectRequest.delegate = self;
1588 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1589 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1590 newObjectRequest.userInfo = containerRequest.userInfo;
1591 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1592 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1593 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1594 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1595 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1597 if (newSyncRequested) {
1598 [self endActivity:activity
1599 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1600 @synchronized(self) {
1601 syncIncomplete = YES;
1602 [self decreaseSyncOperationCount];
1603 if (!syncOperationCount)
1608 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
1610 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1611 missingBlockIndex:missingBlockIndex
1612 sharingAccount:nil];
1613 newContainerRequest.delegate = self;
1614 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1615 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1616 newContainerRequest.userInfo = containerRequest.userInfo;
1617 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1618 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1619 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1620 [activityFacility updateActivity:activity
1621 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1622 totalBytes:activity.totalBytes
1623 currentBytes:(activity.currentBytes + size)];
1625 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1629 [self requestFailed:containerRequest];
1634 - (void)requestFailed:(ASIPithosRequest *)request {
1635 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1636 if ([request isCancelled]) {
1637 [self endActivity:[request.userInfo objectForKey:@"activity"]
1638 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1639 syncIncomplete = YES;
1640 [self decreaseSyncOperationCount];
1643 if (newSyncRequested) {
1644 [self endActivity:[request.userInfo objectForKey:@"activity"]
1645 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1646 @synchronized(self) {
1647 syncIncomplete = YES;
1648 [self decreaseSyncOperationCount];
1649 if (!syncOperationCount)
1654 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1656 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1657 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1658 [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1660 [self endActivity:[request.userInfo objectForKey:@"activity"]
1661 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1662 syncIncomplete = YES;
1663 [self decreaseSyncOperationCount];