5 // Copyright 2011-2012 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 "PithosAccount.h"
40 #import "PithosLocalObjectState.h"
41 #import "PithosActivityFacility.h"
42 #import "PithosUtilities.h"
43 #import "ASINetworkQueue.h"
44 #import "ASIPithosRequest.h"
46 #import "ASIPithosContainerRequest.h"
47 #import "ASIPithosObjectRequest.h"
48 #import "ASIPithosObject.h"
50 @interface PithosSyncDaemon (Private)
51 - (void)resetLocalState;
52 - (NSString *)pithosStateFilePath;
53 - (void)saveLocalState;
55 - (BOOL)moveToTempTrashFile:(NSString *)filePath;
56 - (void)emptyTempTrash;
57 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
59 - (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath;
60 - (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
61 object:(ASIPithosObject *)object
62 localFilePath:(NSString *)filePath;
63 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest;
64 - (void)requestFailed:(ASIPithosRequest *)request;
66 - (void)syncOperationStarted;
67 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull;
71 @implementation PithosSyncDaemon
72 @synthesize directoryPath, pithosAccount, containerName, pithos;
73 @synthesize blockHash, blockSize, lastModified, lastCompletedSync, remoteObjects, storedLocalObjectStates, currentLocalObjectStates;
74 @synthesize containerDirectoryPath, pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
77 #pragma Object Lifecycle
79 - (id)initWithDirectoryPath:(NSString *)aDirectoryPath
80 pithosAccount:(PithosAccount *)aPithosAccount
81 containerName:(NSString *)aContainerName
82 resetLocalState:(BOOL)resetLocalState {
83 if ((self = [super init])) {
84 directoryPath = [aDirectoryPath copy];
85 pithosAccount = [aPithosAccount retain];
86 containerName = [aContainerName copy];
87 self.pithos = pithosAccount.pithos;
89 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
92 [self resetLocalState];
94 networkQueue = [[ASINetworkQueue alloc] init];
95 networkQueue.showAccurateProgress = YES;
96 networkQueue.shouldCancelAllRequestsOnFailure = NO;
97 // networkQueue.maxConcurrentOperationCount = 1;
99 callbackQueue = [[NSOperationQueue alloc] init];
100 [callbackQueue setSuspended:YES];
101 callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
102 // callbackQueue.maxConcurrentOperationCount = 1;
104 [[NSNotificationCenter defaultCenter] addObserver:self
105 selector:@selector(applicationWillTerminate:)
106 name:NSApplicationWillTerminateNotification
107 object:[NSApplication sharedApplication]];
114 - (void)resetLocalState {
115 self.lastCompletedSync = nil;
116 NSFileManager *fileManager = [NSFileManager defaultManager];
117 NSError *error = nil;
118 if ([fileManager fileExistsAtPath:self.pithosStateFilePath] &&
119 (![fileManager removeItemAtPath:self.pithosStateFilePath error:&error] || error))
120 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
121 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", self.pithosStateFilePath]
123 if (self.tempDownloadsDirPath) {
125 for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
127 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
128 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath]
132 NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
133 if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
134 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
135 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
143 - (void)resetDaemon {
144 @synchronized(self) {
149 [networkQueue reset];
150 [callbackQueue cancelAllOperations];
151 [callbackQueue setSuspended:YES];
152 [self emptyTempTrash];
154 @synchronized(self) {
159 - (void)startDaemon {
160 @synchronized(self) {
165 syncOperationCount = 0;
166 newSyncRequested = NO;
170 self.containerDirectoryPath = [directoryPath stringByAppendingPathComponent:containerName];
172 if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath]) {
173 NSData *data = [NSData dataWithContentsOfFile:self.pithosStateFilePath];
174 NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
175 self.storedLocalObjectStates = [unarchiver decodeObjectForKey:containerName];
176 [unarchiver finishDecoding];
178 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
181 // In the improbable case of leftover operations
182 [networkQueue reset];
183 [callbackQueue cancelAllOperations];
186 [callbackQueue setSuspended:NO];
188 @synchronized(self) {
194 [[NSNotificationCenter defaultCenter] removeObserver:self];
196 [callbackQueue release];
197 [networkQueue release];
198 [tempTrashDirPath release];
199 [tempDownloadsDirPath release];
200 [pithosStateFilePath release];
201 [containerDirectoryPath release];
202 [currentLocalObjectStates release];
203 [storedLocalObjectStates release];
204 [remoteObjects release];
206 [lastCompletedSync release];
207 [lastModified release];
210 [containerName release];
211 [pithosAccount release];
212 [directoryPath release];
217 #pragma mark Observers
219 - (void)applicationWillTerminate:(NSNotification *)notification {
220 [self saveLocalState];
224 #pragma mark Properties
226 - (NSString *)pithosStateFilePath {
227 if (!pithosStateFilePath)
228 pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
229 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
230 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive",
231 pithosAccount.uniqueName]] retain];
232 return [[pithosStateFilePath copy] autorelease];
235 - (NSString *)tempDownloadsDirPath {
236 if (!tempDownloadsDirPath) {
237 tempDownloadsDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
238 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
239 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads",
240 pithosAccount.uniqueName]] retain];
241 NSFileManager *fileManager = [NSFileManager defaultManager];
243 BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
244 NSError *error = nil;
245 if (fileExists && !isDirectory)
246 [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
247 if (!error & !fileExists)
248 [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
250 // tempDownloadsDirPath = nil;
251 // XXX create a dir using mktmps?
253 return tempDownloadsDirPath;
256 - (NSString *)tempTrashDirPath {
257 if (!tempTrashDirPath) {
258 tempTrashDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
259 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
260 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash",
261 pithosAccount.uniqueName]] retain];
262 NSFileManager *fileManager = [NSFileManager defaultManager];
264 BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
265 NSError *error = nil;
266 if (fileExists && !isDirectory)
267 [fileManager removeItemAtPath:tempTrashDirPath error:&error];
268 if (!error & !fileExists)
269 [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
271 // tempTrashDirPath = nil;
272 // XXX create a dir using mktmps?
274 return tempTrashDirPath;
277 - (void)setDirectoryPath:(NSString *)aDirectoryPath {
278 if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
280 [self resetLocalState];
281 [directoryPath release];
282 directoryPath = [aDirectoryPath copy];
286 - (void)setContainerName:(NSString *)aContainerName {
287 if (aContainerName && ![aContainerName isEqualToString:containerName]) {
289 [self resetLocalState];
290 [containerName release];
291 containerName = [aContainerName copy];
295 - (void)setPithos:(ASIPithos *)aPithos {
297 pithos = [[ASIPithos pithos] retain];
298 pithos.authUser = [[aPithos.authUser copy] autorelease];
299 pithos.authToken = [[aPithos.authToken copy] autorelease];
300 pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
301 pithos.authURL = [[aPithos.authURL copy] autorelease];
302 pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
305 (![aPithos.authUser isEqualToString:pithos.authUser] ||
306 ![aPithos.authToken isEqualToString:pithos.authToken] ||
307 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
309 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
310 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
311 [self resetLocalState];
312 pithos.authUser = [[aPithos.authUser copy] autorelease];
313 pithos.authToken = [[aPithos.authToken copy] autorelease];
314 pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
315 pithos.authURL = [[aPithos.authURL copy] autorelease];
316 pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
323 - (void)saveLocalState {
324 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
325 NSMutableData *data = [NSMutableData data];
326 NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
327 [archiver encodeObject:storedLocalObjectStates forKey:containerName];
328 [archiver finishEncoding];
329 [data writeToFile:self.pithosStateFilePath atomically:YES];
333 - (void)syncOperationStarted {
334 @synchronized(self) {
335 syncOperationCount++;
339 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
340 @synchronized(self) {
341 if (!operationSuccessfull)
342 syncIncomplete = YES;
343 if (syncOperationCount == 0)
344 // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
346 syncOperationCount--;
347 if (syncOperationCount == 0) {
348 if (!syncIncomplete) {
349 self.lastCompletedSync = [NSDate date];
350 dispatch_async(dispatch_get_main_queue(), ^{
351 [activityFacility startAndEndActivityWithType:PithosActivityOther
352 message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]
353 pithosAccount:pithosAccount];
357 [self emptyTempTrash];
358 if (newSyncRequested && daemonActive)
365 @synchronized(self) {
366 return ((syncOperationCount > 0) && daemonActive);
371 @synchronized(self) {
372 if ([self isSyncing])
378 @synchronized(self) {
379 if ([self isSyncing]) {
380 // If at least one operation is running return
381 newSyncRequested = YES;
383 } else if (daemonActive) {
384 // The first operation is the server listing
385 [self syncOperationStarted];
386 newSyncRequested = NO;
394 NSFileManager *fileManager = [NSFileManager defaultManager];
396 NSError *error = nil;
397 if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
398 if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] ||
400 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
401 message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", containerDirectoryPath]
403 [self syncOperationFinishedWithSuccess:NO];
406 } else if (!isDirectory) {
407 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
408 message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", containerDirectoryPath]
410 [self syncOperationFinishedWithSuccess:NO];
414 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
415 containerName:containerName
424 ifModifiedSince:lastModified];
425 containerRequest.delegate = self;
426 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
427 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
428 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther
429 message:@"Sync: Getting server listing"
430 pithosAccount:pithosAccount];
431 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
432 activity, @"activity",
433 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage",
434 @"Sync: Getting server listing (failed)", @"failedActivityMessage",
435 @"Sync: Getting server listing (finished)", @"finishedActivityMessage",
436 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
437 [NSNumber numberWithUnsignedInteger:10], @"retries",
438 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector",
439 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector",
441 [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
444 - (void)emptyTempTrash {
445 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
446 NSString *trashDirPath = self.tempTrashDirPath;
448 NSFileManager *fileManager = [NSFileManager defaultManager];
449 NSError *error = nil;
450 // NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
451 NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
453 dispatch_async(dispatch_get_main_queue(), ^{
454 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
455 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
461 if ([subPaths count]) {
462 // NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
463 // for (NSString *subPath in subPaths) {
464 // [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
466 // [self syncOperationStarted];
467 // [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
469 // dispatch_async(dispatch_get_main_queue(), ^{
470 // [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
471 // message:@"Cannot move files to Trash"
475 // [self syncOperationFinishedWithSuccess:YES];
477 for (NSString *subPath in subPaths) {
478 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
480 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
481 dispatch_async(dispatch_get_main_queue(), ^{
482 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
483 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
492 - (BOOL)moveToTempTrashFile:(NSString *)filePath {
493 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
494 NSString *trashDirPath = self.tempTrashDirPath;
495 if (!tempTrashDirPath) {
499 NSFileManager *fileManager = [NSFileManager defaultManager];
501 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
502 NSError *error = nil;
503 NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath
504 withString:trashDirPath];
505 NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
506 if (fileExists && isDirectory) {
507 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
509 dispatch_async(dispatch_get_main_queue(), ^{
510 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
511 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
517 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
518 dispatch_async(dispatch_get_main_queue(), ^{
519 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
520 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
526 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
527 dispatch_async(dispatch_get_main_queue(), ^{
528 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
529 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
530 filePath, newFilePath]
536 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
538 currentState.filePath = newFilePath;
539 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
540 [currentLocalObjectStates removeObjectForKey:filePath];
542 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
547 for (NSString *subPath in subPaths) {
548 NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
549 NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath
550 withString:trashDirPath];
551 currentState = [currentLocalObjectStates objectForKey:subFilePath];
553 currentState.filePath = newSubFilePath;
554 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
555 [currentLocalObjectStates removeObjectForKey:subFilePath];
557 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
560 forKey:newSubFilePath];
563 } else if (fileExists && !isDirectory) {
564 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
565 dispatch_async(dispatch_get_main_queue(), ^{
566 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
567 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
573 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
574 dispatch_async(dispatch_get_main_queue(), ^{
575 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
576 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
577 filePath, newFilePath]
583 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
585 currentState.filePath = newFilePath;
586 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
587 [currentLocalObjectStates removeObjectForKey:filePath];
589 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
599 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
600 if ([hash length] != 64)
602 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
603 PithosLocalObjectState *localState;
604 NSFileManager *fileManager = [NSFileManager defaultManager];
606 NSError *error = nil;
607 for (NSString *localFilePath in [currentLocalObjectStates allKeys]) {
608 localState = [currentLocalObjectStates objectForKey:localFilePath];
609 if (!localState.isDirectory && [hash isEqualToString:localState.hash] &&
610 [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
611 if ([localFilePath hasPrefix:containerDirectoryPath]) {
612 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
613 dispatch_async(dispatch_get_main_queue(), ^{
614 [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
615 message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
616 localFilePath, filePath]
623 } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
624 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
625 dispatch_async(dispatch_get_main_queue(), ^{
626 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
627 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
628 localFilePath, filePath]
632 localState.filePath = filePath;
633 [currentLocalObjectStates setObject:localState forKey:filePath];
634 [currentLocalObjectStates removeObjectForKey:localFilePath];
645 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
646 localFilePath:(NSString *)filePath {
647 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
648 NSFileManager *fileManager = [NSFileManager defaultManager];
651 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
652 NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
653 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
654 // Remote updated info
655 NSError *remoteError;
656 BOOL remoteIsDirectory;
657 BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos
658 containerName:containerName
659 objectName:object.name
661 isDirectory:&remoteIsDirectory
663 if (!object || !object.objectHash) {
664 // Delete local object
665 if (remoteObjectExists) {
666 // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
667 syncIncomplete = YES;
669 NSLog(@"Sync::delete local object: %@", filePath);
670 if (!fileExists || [self moveToTempTrashFile:filePath]) {
671 dispatch_async(dispatch_get_main_queue(), ^{
672 [activityFacility startAndEndActivityWithType:PithosActivityOther
673 message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]
674 pithosAccount:pithosAccount];
676 [storedLocalObjectStates removeObjectForKey:object.name];
677 [self saveLocalState];
679 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
680 // Create local directory object
681 if (!remoteObjectExists || !remoteIsDirectory) {
682 // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
683 syncIncomplete = YES;
687 NSLog(@"Sync::create local directory object: %@", filePath);
688 BOOL directoryCreated = NO;
689 if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath])) {
690 NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
692 if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
693 dispatch_async(dispatch_get_main_queue(), ^{
694 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
695 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
699 directoryCreated = YES;
700 storedState.filePath = filePath;
701 storedState.isDirectory = YES;
702 [self saveLocalState];
705 NSLog(@"Sync::local directory object exists: %@", filePath);
706 directoryCreated = YES;
708 if (directoryCreated)
709 dispatch_async(dispatch_get_main_queue(), ^{
710 [activityFacility startAndEndActivityWithType:PithosActivityOther
711 message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name]
712 pithosAccount:pithosAccount];
714 } else if (object.bytes == 0) {
715 // Create local object with zero length
716 if (!remoteObjectExists || remoteIsDirectory) {
717 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
718 syncIncomplete = YES;
722 NSLog(@"Sync::create local zero length object: %@", filePath);
723 BOOL fileCreated = NO;
724 if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath])) {
725 NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
726 // Create directory of the file, if it doesn't exist
728 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
729 dispatch_async(dispatch_get_main_queue(), ^{
730 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
731 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
736 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
737 dispatch_async(dispatch_get_main_queue(), ^{
738 [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
739 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
744 storedState.filePath = filePath;
745 storedState.hash = object.objectHash;
746 storedState.tmpFilePath = nil;
747 [self saveLocalState];
750 NSLog(@"Sync::local zero length object exists: %@", filePath);
754 dispatch_async(dispatch_get_main_queue(), ^{
755 [activityFacility startAndEndActivityWithType:PithosActivityOther
756 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]
757 pithosAccount:pithosAccount];
759 } else if (storedState.tmpFilePath == nil) {
760 // Create new local object
761 if (!remoteObjectExists || remoteIsDirectory) {
762 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
763 syncIncomplete = YES;
767 // Create directory of the file, if it doesn't exist
769 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
770 dispatch_async(dispatch_get_main_queue(), ^{
771 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
772 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
776 // Check first if a local copy exists
777 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
778 storedState.filePath = filePath;
779 storedState.hash = object.objectHash;
780 [self saveLocalState];
781 dispatch_async(dispatch_get_main_queue(), ^{
782 [activityFacility startAndEndActivityWithType:PithosActivityOther
783 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]
784 pithosAccount:pithosAccount];
787 [self syncOperationStarted];
788 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
789 containerName:containerName
792 blockSize:blockSize];
793 objectRequest.delegate = self;
794 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
795 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
796 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
797 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name]
798 totalBytes:object.bytes
800 pithosAccount:pithosAccount];
801 dispatch_async(dispatch_get_main_queue(), ^{
802 [activityFacility updateActivity:activity withMessage:activity.message];
804 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
805 object, @"pithosObject",
806 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(blockSize + 0.0)))], @"missingBlocks",
807 [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
808 filePath, @"filePath",
809 activity, @"activity",
810 [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage",
811 [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage",
812 [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage",
813 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
814 [NSNumber numberWithUnsignedInteger:10], @"retries",
815 NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector",
816 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
818 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
819 [activityFacility updateActivity:activity
820 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
821 [[objectRequest.userInfo objectForKey:@"pithosObject"] name],
822 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
823 totalBytes:activity.totalBytes
824 currentBytes:(activity.currentBytes + size)];
826 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
829 // Resume local object download
830 if (!remoteObjectExists || remoteIsDirectory) {
831 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
832 syncIncomplete = YES;
836 // Create directory of the file, if it doesn't exist
838 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
839 dispatch_async(dispatch_get_main_queue(), ^{
840 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
841 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
845 // Check first if a local copy exists
846 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
847 storedState.filePath = filePath;
848 storedState.hash = object.objectHash;
849 // Delete incomplete temp download
850 storedState.tmpFilePath = nil;
851 [self saveLocalState];
852 dispatch_async(dispatch_get_main_queue(), ^{
853 [activityFacility startAndEndActivityWithType:PithosActivityOther
854 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]
855 pithosAccount:pithosAccount];
858 [self syncOperationStarted];
859 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
860 containerName:containerName
861 objectName:object.name];
862 objectRequest.delegate = self;
863 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
864 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
865 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
866 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name]
867 totalBytes:object.bytes
869 pithosAccount:pithosAccount];
870 dispatch_async(dispatch_get_main_queue(), ^{
871 [activityFacility updateActivity:activity withMessage:activity.message];
873 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
874 object, @"pithosObject",
875 filePath, @"filePath",
876 activity, @"activity",
877 [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage",
878 [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage",
879 [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage",
880 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
881 [NSNumber numberWithUnsignedInteger:10], @"retries",
882 NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector",
883 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
885 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
891 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
892 object:(ASIPithosObject *)object
893 localFilePath:(NSString *)filePath {
894 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
895 [self syncOperationStarted];
896 NSFileManager *fileManager = [NSFileManager defaultManager];
898 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
899 if (currentState.isDirectory) {
900 // Create remote directory object
901 if (!fileExists || !isDirectory) {
902 // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
903 [self syncOperationFinishedWithSuccess:NO];
907 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
908 containerName:containerName
909 objectName:object.name
911 contentType:@"application/directory"
913 contentDisposition:nil
916 isPublic:ASIPithosObjectRequestPublicIgnore
919 objectRequest.delegate = self;
920 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
921 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
922 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
923 message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]
924 pithosAccount:pithosAccount];
925 dispatch_async(dispatch_get_main_queue(), ^{
926 [activityFacility updateActivity:activity withMessage:activity.message];
928 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
929 object, @"pithosObject",
930 activity, @"activity",
931 [NSString stringWithFormat:@"Sync: Creating directory '%@' (stopped)", object.name], @"stoppedActivityMessage",
932 [NSString stringWithFormat:@"Sync: Creating directory '%@' (failed)", object.name], @"failedActivityMessage",
933 [NSString stringWithFormat:@"Sync: Creating directory '%@' (finished)", object.name], @"finishedActivityMessage",
934 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
935 [NSNumber numberWithUnsignedInteger:10], @"retries",
936 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
937 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
939 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
940 } else if (![currentState exists]) {
941 // Delete remote object
943 // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
944 syncIncomplete = YES;
947 if ([PithosUtilities isContentTypeDirectory:object.contentType])
948 safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
950 safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
952 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
953 containerName:containerName
954 objectName:object.name
955 destinationContainerName:@"trash"
956 destinationObjectName:safeName
959 objectRequest.delegate = self;
960 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
961 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
962 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
963 message:[NSString stringWithFormat:@"Sync: Moving to trash '%@'", object.name]
964 pithosAccount:pithosAccount];
965 dispatch_async(dispatch_get_main_queue(), ^{
966 [activityFacility updateActivity:activity withMessage:activity.message];
968 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
969 object, @"pithosObject",
970 activity, @"activity",
971 [NSString stringWithFormat:@"Sync: Moving to trash '%@' (stopped)", object.name], @"stoppedActivityMessage",
972 [NSString stringWithFormat:@"Sync: Moving to trash '%@' (failed)", object.name], @"failedActivityMessage",
973 [NSString stringWithFormat:@"Sync: Moving to trash '%@' (finished)", object.name], @"finishedActivityMessage",
974 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
975 [NSNumber numberWithUnsignedInteger:10], @"retries",
976 NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector",
977 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
979 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
981 [self syncOperationFinishedWithSuccess:NO];
984 [self syncOperationFinishedWithSuccess:NO];
987 // Upload file to remote object
988 if (!fileExists || isDirectory) {
989 // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
990 [self syncOperationFinishedWithSuccess:NO];
994 NSError *error = nil;
995 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
996 if (object.contentType == nil)
997 object.contentType = @"application/octet-stream";
999 NSLog(@"contentType detection error: %@", error);
1000 NSArray *hashes = nil;
1001 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1002 containerName:containerName
1003 objectName:object.name
1004 contentType:object.contentType
1010 sharingAccount:nil];
1011 if (objectRequest) {
1012 objectRequest.delegate = self;
1013 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1014 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1015 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1016 message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
1017 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1019 pithosAccount:pithosAccount];
1020 dispatch_async(dispatch_get_main_queue(), ^{
1021 [activityFacility updateActivity:activity withMessage:activity.message];
1023 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1024 [NSDictionary dictionaryWithObjectsAndKeys:
1025 object, @"pithosObject",
1026 filePath, @"filePath",
1028 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1029 activity, @"activity",
1030 [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage",
1031 [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage",
1032 [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage",
1033 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1034 [NSNumber numberWithUnsignedInteger:10], @"retries",
1035 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1036 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1038 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1040 [self syncOperationFinishedWithSuccess:NO];
1047 #pragma mark ASIHTTPRequestDelegate
1049 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1050 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1051 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1052 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1053 object:request] autorelease];
1054 operation.completionBlock = ^{
1055 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1056 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1057 dispatch_async(dispatch_get_main_queue(), ^{
1058 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1059 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1061 [self syncOperationFinishedWithSuccess:NO];
1065 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1066 [callbackQueue addOperation:operation];
1069 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1070 if (request.isCancelled) {
1071 // Request has been cancelled
1072 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1073 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1074 withObject:request];
1076 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1077 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1078 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1079 object:request] autorelease];
1080 operation.completionBlock = ^{
1081 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1082 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1083 dispatch_async(dispatch_get_main_queue(), ^{
1084 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1085 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1087 [self syncOperationFinishedWithSuccess:NO];
1091 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1092 [callbackQueue addOperation:operation];
1096 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1097 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1098 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1099 NSLog(@"Sync::list request finished: %@", containerRequest.url);
1100 if (operation.isCancelled) {
1101 [self listRequestFailed:containerRequest];
1102 } else if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
1103 if (containerRequest.responseStatusCode == 200) {
1104 NSArray *someObjects = [containerRequest objects];
1105 if (objects == nil) {
1106 objects = [[NSMutableArray alloc] initWithArray:someObjects];
1108 [objects addObjectsFromArray:someObjects];
1110 if ([someObjects count] < 10000) {
1111 self.blockHash = [containerRequest blockHash];
1112 self.blockSize = [containerRequest blockSize];
1113 self.lastModified = [containerRequest lastModified];
1114 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1115 for (ASIPithosObject *object in objects) {
1116 [remoteObjects setObject:object forKey:object.name];
1121 // Do an additional request to fetch more objects
1122 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1123 containerName:containerName
1125 marker:[[someObjects lastObject] name]
1132 ifModifiedSince:lastModified];
1133 newContainerRequest.delegate = self;
1134 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1135 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1136 newContainerRequest.userInfo = containerRequest.userInfo;
1137 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1138 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1144 if (operation.isCancelled) {
1145 [self listRequestFailed:containerRequest];
1150 dispatch_async(dispatch_get_main_queue(), ^{
1151 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1152 withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1154 NSFileManager *fileManager = [NSFileManager defaultManager];
1155 NSError *error = nil;
1156 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
1158 dispatch_async(dispatch_get_main_queue(), ^{
1159 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
1160 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
1162 [activityFacility startAndEndActivityWithType:PithosActivityOther
1163 message:@"Sync: Failed to read contents of sync directory"
1164 pithosAccount:pithosAccount];
1166 // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccessfully
1167 [self syncOperationFinishedWithSuccess:NO];
1172 if (operation.isCancelled) {
1173 operation.completionBlock = nil;
1174 [self syncOperationFinishedWithSuccess:NO];
1179 self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:[subPaths count]];
1180 for (NSString *objectName in subPaths) {
1181 if (operation.isCancelled) {
1182 operation.completionBlock = nil;
1183 [self saveLocalState];
1184 [self syncOperationFinishedWithSuccess:NO];
1189 PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:objectName];
1190 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1191 if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1192 // New or modified existing local object, compute current state
1193 if (!storedLocalObjectState)
1194 // For new local object, also create empty stored state
1195 [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1196 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
1198 blockSize:blockSize]
1201 // Local object hasn't changed, set stored state also to current
1202 [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1205 [self saveLocalState];
1207 if (operation.isCancelled) {
1208 operation.completionBlock = nil;
1209 [self syncOperationFinishedWithSuccess:NO];
1214 for (NSString *objectName in remoteObjects) {
1215 if (operation.isCancelled) {
1216 operation.completionBlock = nil;
1217 [self saveLocalState];
1218 [self syncOperationFinishedWithSuccess:NO];
1223 if (![storedLocalObjectStates objectForKey:objectName])
1224 [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1226 [self saveLocalState];
1228 if (operation.isCancelled) {
1229 operation.completionBlock = nil;
1230 [self syncOperationFinishedWithSuccess:NO];
1235 for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1236 if (operation.isCancelled) {
1237 operation.completionBlock = nil;
1238 [self syncOperationFinishedWithSuccess:NO];
1243 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1244 if ([objectName hasSuffix:@"/"])
1245 filePath = [filePath stringByAppendingString:@":"];
1246 ASIPithosObject *object = [ASIPithosObject object];
1247 object.name = objectName;
1248 NSLog(@"Sync::object name: %@", objectName);
1250 PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
1251 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1252 if (!currentLocalObjectState)
1253 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
1255 blockSize:blockSize];
1256 if (currentLocalObjectState.isDirectory)
1257 object.contentType = @"application/directory";
1259 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1260 ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
1262 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1263 remoteObjectState.isDirectory = YES;
1264 object.contentType = @"application/directory";
1266 remoteObjectState.hash = remoteObject.objectHash;
1270 BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1271 BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1272 NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1273 if (!localStateHasChanged) {
1274 // Local state hasn't changed
1275 if (serverStateHasChanged) {
1276 // Server state has changed
1277 // Update local state to match that of the server
1278 object.bytes = remoteObject.bytes;
1279 object.version = remoteObject.version;
1280 object.contentType = remoteObject.contentType;
1281 object.objectHash = remoteObject.objectHash;
1282 [self updateLocalStateWithObject:object localFilePath:filePath];
1283 } else if (!remoteObject && ![currentLocalObjectState exists]) {
1284 // Server state hasn't changed
1285 // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1286 [storedLocalObjectStates removeObjectForKey:objectName];
1287 [self saveLocalState];
1290 // Local state has changed
1291 if (!serverStateHasChanged) {
1292 // Server state hasn't changed
1293 [self updateServerStateWithCurrentState:currentLocalObjectState
1295 localFilePath:filePath];
1297 // Server state has also changed
1298 if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1299 // Both did the same change (directory)
1300 storedLocalObjectState.filePath = filePath;
1301 storedLocalObjectState.isDirectory = YES;
1302 [self saveLocalState];
1303 } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1304 // Both did the same change (object edit or delete)
1305 if (![remoteObjectState exists]) {
1306 [storedLocalObjectStates removeObjectForKey:object.name];
1308 storedLocalObjectState.filePath = filePath;
1309 storedLocalObjectState.hash = remoteObjectState.hash;
1311 [self saveLocalState];
1313 // Conflict, we ask the user which change to keep
1314 NSString *informativeText;
1315 NSString *firstButtonText;
1316 NSString *secondButtonText;
1318 if (![remoteObjectState exists]) {
1319 // Remote object has been deleted
1320 informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
1321 firstButtonText = @"Delete local file";
1322 secondButtonText = @"Upload file to server";
1323 } else if (![currentLocalObjectState exists]) {
1324 informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
1325 firstButtonText = @"Download file from server";
1326 secondButtonText = @"Delete file on server";
1328 informativeText = [NSString stringWithFormat:@"'%@' has been modifed both locally and on the server.", object.name];
1329 firstButtonText = @"Keep server version";
1330 secondButtonText = @"Keep local version";
1332 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1333 [alert setMessageText:@"Conflict"];
1334 [alert setInformativeText:informativeText];
1335 [alert addButtonWithTitle:firstButtonText];
1336 [alert addButtonWithTitle:secondButtonText];
1337 [alert addButtonWithTitle:@"Do nothing"];
1338 NSInteger choice = [alert runModal];
1339 if (choice == NSAlertFirstButtonReturn) {
1340 object.bytes = remoteObject.bytes;
1341 object.version = remoteObject.version;
1342 object.contentType = remoteObject.contentType;
1343 object.objectHash = remoteObject.objectHash;
1344 [self updateLocalStateWithObject:object localFilePath:filePath];
1345 } if (choice == NSAlertSecondButtonReturn) {
1346 [self updateServerStateWithCurrentState:currentLocalObjectState
1348 localFilePath:filePath];
1354 [self syncOperationFinishedWithSuccess:YES];
1356 [self listRequestFailed:containerRequest];
1361 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1362 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1363 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1364 if (operation.isCancelled) {
1370 if (containerRequest.isCancelled) {
1371 dispatch_async(dispatch_get_main_queue(), ^{
1372 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1373 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1377 [self syncOperationFinishedWithSuccess:NO];
1381 // If the server listing fails, the sync should start over, so just retrying is enough
1382 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1384 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1385 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1386 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1388 dispatch_async(dispatch_get_main_queue(), ^{
1389 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1390 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1394 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1395 [self syncOperationFinishedWithSuccess:NO];
1400 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1401 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1402 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1403 NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1404 if (operation.isCancelled) {
1405 [self requestFailed:objectRequest];
1406 } else if (objectRequest.responseStatusCode == 206) {
1407 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1408 NSFileManager *fileManager = [NSFileManager defaultManager];
1410 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1412 NSString *downloadsDirPath = self.tempDownloadsDirPath;
1413 if (!downloadsDirPath) {
1414 dispatch_async(dispatch_get_main_queue(), ^{
1415 [activityFacility endActivity:activity
1416 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1418 [self syncOperationFinishedWithSuccess:NO];
1423 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1424 if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1425 NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1426 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1427 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1428 strcpy(tempFileNameCString, tempFileTemplateCString);
1429 int fileDescriptor = mkstemp(tempFileNameCString);
1430 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1431 free(tempFileNameCString);
1432 if (fileDescriptor == -1) {
1433 dispatch_async(dispatch_get_main_queue(), ^{
1434 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
1435 message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]
1437 [activityFacility endActivity:activity
1438 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1440 [self syncOperationFinishedWithSuccess:NO];
1444 close(fileDescriptor);
1445 storedState.tmpFilePath = tempFilePath;
1446 [self saveLocalState];
1450 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1451 NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1452 [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
1453 [tempFileHandle writeData:[objectRequest responseData]];
1454 [tempFileHandle closeFile];
1456 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1457 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1458 if (missingBlockIndex == NSNotFound) {
1459 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1460 NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1461 if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath]) {
1462 dispatch_async(dispatch_get_main_queue(), ^{
1463 [activityFacility endActivity:activity
1464 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1466 [self syncOperationFinishedWithSuccess:NO];
1469 } else if (![fileManager fileExistsAtPath:dirPath]) {
1470 // File doesn't exist but also the containing directory doesn't exist
1471 // In most cases this should have been resolved as an update of the corresponding local object,
1472 // but it never hurts to check
1474 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1476 dispatch_async(dispatch_get_main_queue(), ^{
1477 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1478 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
1480 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1481 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1483 [self syncOperationFinishedWithSuccess:NO];
1488 // Move file from tmp download
1490 [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1492 dispatch_async(dispatch_get_main_queue(), ^{
1493 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
1494 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath]
1496 [activityFacility endActivity:activity
1497 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1499 [self syncOperationFinishedWithSuccess:NO];
1503 dispatch_async(dispatch_get_main_queue(), ^{
1504 [activityFacility endActivity:activity
1505 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1506 totalBytes:activity.totalBytes
1507 currentBytes:activity.totalBytes];
1510 storedState.filePath = filePath;
1511 storedState.hash = object.objectHash;
1512 storedState.tmpFilePath = nil;
1513 [self saveLocalState];
1514 [self syncOperationFinishedWithSuccess:YES];
1518 if (newSyncRequested || syncLate || operation.isCancelled) {
1519 [self requestFailed:objectRequest];
1521 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1522 containerName:containerName
1524 blockIndex:missingBlockIndex
1525 blockSize:blockSize];
1526 newObjectRequest.delegate = self;
1527 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1528 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1529 newObjectRequest.userInfo = objectRequest.userInfo;
1530 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1531 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1532 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1533 [activityFacility updateActivity:activity
1534 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1536 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1537 totalBytes:activity.totalBytes
1538 currentBytes:(activity.currentBytes + size)];
1540 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1543 } else if (objectRequest.responseStatusCode == 412) {
1544 // The object has changed on the server
1545 dispatch_async(dispatch_get_main_queue(), ^{
1546 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1547 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1549 [self syncOperationFinishedWithSuccess:NO];
1551 [self requestFailed:objectRequest];
1556 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1557 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1558 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1559 NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1560 if (operation.isCancelled) {
1561 [self requestFailed:objectRequest];
1562 } else if (objectRequest.responseStatusCode == 200) {
1563 if (newSyncRequested || syncLate || operation.isCancelled) {
1564 [self requestFailed:objectRequest];
1566 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1567 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1568 if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1569 [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
1570 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1571 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath
1574 withHashes:[objectRequest hashes]];
1575 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1576 dispatch_async(dispatch_get_main_queue(), ^{
1577 [activityFacility endActivity:activity
1578 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1580 (100*(activity.totalBytes - [missingBlocks count]*blockSize + 0.0)/(activity.totalBytes + 0.0))]
1581 totalBytes:activity.totalBytes
1582 currentBytes:(activity.totalBytes - [missingBlocks count]*blockSize)];
1585 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1586 containerName:containerName
1588 blockIndex:missingBlockIndex
1589 blockSize:blockSize];
1590 newObjectRequest.delegate = self;
1591 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1592 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1593 newObjectRequest.userInfo = objectRequest.userInfo;
1594 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1595 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1596 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1597 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1598 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1599 [activityFacility updateActivity:activity
1600 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1602 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1603 totalBytes:activity.totalBytes
1604 currentBytes:(activity.currentBytes + size)];
1606 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1609 [self requestFailed:objectRequest];
1614 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1615 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1616 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1617 NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1618 if (operation.isCancelled) {
1619 [self requestFailed:objectRequest];
1620 } else if (objectRequest.responseStatusCode == 201) {
1621 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1622 storedState.isDirectory = YES;
1623 [self saveLocalState];
1624 dispatch_async(dispatch_get_main_queue(), ^{
1625 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1626 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1628 [self syncOperationFinishedWithSuccess:YES];
1630 [self requestFailed:objectRequest];
1635 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1636 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1637 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1638 NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1639 if (operation.isCancelled) {
1640 [self requestFailed:objectRequest];
1641 } else if (objectRequest.responseStatusCode == 201) {
1642 [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1643 [self saveLocalState];
1644 dispatch_async(dispatch_get_main_queue(), ^{
1645 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1646 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1648 [self syncOperationFinishedWithSuccess:YES];
1650 [self requestFailed:objectRequest];
1655 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1656 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1657 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1658 NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1659 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1660 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1661 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1662 NSUInteger totalBytes = activity.totalBytes;
1663 NSUInteger currentBytes = activity.currentBytes;
1664 if (operation.isCancelled) {
1665 [self requestFailed:objectRequest];
1666 } else if (objectRequest.responseStatusCode == 201) {
1667 NSLog(@"Sync::object created: %@", objectRequest.url);
1668 storedState.hash = [objectRequest objectHash];
1669 [self saveLocalState];
1670 dispatch_async(dispatch_get_main_queue(), ^{
1671 [activityFacility endActivity:activity
1672 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1673 totalBytes:totalBytes
1674 currentBytes:totalBytes];
1676 [self syncOperationFinishedWithSuccess:YES];
1677 } else if (objectRequest.responseStatusCode == 409) {
1678 if (newSyncRequested || syncLate || operation.isCancelled) {
1679 [self requestFailed:objectRequest];
1681 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1682 if (iteration == 0) {
1683 NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
1684 dispatch_async(dispatch_get_main_queue(), ^{
1685 [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1687 [self syncOperationFinishedWithSuccess:NO];
1691 NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
1692 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1693 withMissingHashes:[objectRequest hashes]];
1694 if (totalBytes >= [missingBlocks count]*blockSize)
1695 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1696 dispatch_async(dispatch_get_main_queue(), ^{
1697 [activityFacility updateActivity:activity
1698 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
1699 totalBytes:totalBytes
1700 currentBytes:currentBytes];
1702 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1703 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1704 containerName:containerName
1706 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1707 missingBlockIndex:missingBlockIndex
1708 sharingAccount:nil];
1709 newContainerRequest.delegate = self;
1710 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1711 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1712 newContainerRequest.userInfo = objectRequest.userInfo;
1713 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1714 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1715 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1716 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1717 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1718 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1719 [activityFacility updateActivity:activity
1720 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1721 totalBytes:activity.totalBytes
1722 currentBytes:(activity.currentBytes + size)];
1724 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1727 [self requestFailed:objectRequest];
1732 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1733 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1734 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1735 NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
1736 if (operation.isCancelled) {
1737 [self requestFailed:containerRequest];
1738 } else if (containerRequest.responseStatusCode == 202) {
1739 ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
1740 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1741 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1742 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1743 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1744 if (operation.isCancelled) {
1745 [self requestFailed:containerRequest];
1746 } else if (missingBlockIndex == NSNotFound) {
1747 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1748 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1749 containerName:containerName
1750 objectName:object.name
1751 contentType:object.contentType
1754 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1757 sharingAccount:nil];
1758 newObjectRequest.delegate = self;
1759 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1760 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1761 newObjectRequest.userInfo = containerRequest.userInfo;
1762 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1763 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1764 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1765 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1766 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1768 if (newSyncRequested || syncLate || operation.isCancelled) {
1769 [self requestFailed:containerRequest];
1771 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1772 containerName:containerName
1774 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1775 missingBlockIndex:missingBlockIndex
1776 sharingAccount:nil];
1777 newContainerRequest.delegate = self;
1778 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1779 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1780 newContainerRequest.userInfo = containerRequest.userInfo;
1781 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1782 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1783 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1784 [activityFacility updateActivity:activity
1785 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1786 totalBytes:activity.totalBytes
1787 currentBytes:(activity.currentBytes + size)];
1789 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1793 [self requestFailed:containerRequest];
1798 - (void)requestFailed:(ASIPithosRequest *)request {
1799 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1800 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1801 if (operation.isCancelled) {
1805 if (request.isCancelled || newSyncRequested || syncLate) {
1806 dispatch_async(dispatch_get_main_queue(), ^{
1807 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1808 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1810 [self syncOperationFinishedWithSuccess:NO];
1814 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1816 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1817 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1818 [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1820 dispatch_async(dispatch_get_main_queue(), ^{
1821 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1822 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1824 [self syncOperationFinishedWithSuccess:NO];