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 "PithosLocalObjectState.h"
40 #import "PithosActivityFacility.h"
41 #import "PithosUtilities.h"
42 #import "ASINetworkQueue.h"
43 #import "ASIPithosRequest.h"
45 #import "ASIPithosContainerRequest.h"
46 #import "ASIPithosObjectRequest.h"
47 #import "ASIPithosObject.h"
49 @interface PithosSyncDaemon (Private)
50 - (NSString *)pithosStateFilePath;
51 - (void)saveLocalState;
53 - (BOOL)moveToTempTrashFile:(NSString *)filePath;
54 - (void)emptyTempTrash;
55 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
57 - (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath;
58 - (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
59 object:(ASIPithosObject *)object
60 localFilePath:(NSString *)filePath;
61 - (void)requestFailed:(ASIPithosRequest *)request;
63 - (void)increaseSyncOperationCount;
64 - (void)decreaseSyncOperationCount;
68 @implementation PithosSyncDaemon
69 @synthesize blockHash, blockSize, lastModified, lastCompletedSync, remoteObjects, storedLocalObjectStates, currentLocalObjectStates;
70 @synthesize directoryPath, containerDirectoryPath, pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
73 #pragma Object Lifecycle
75 - (id)initWithDirectoryPath:(NSString *)aDirectoryPath
76 pithos:(ASIPithos *)aPithos
77 containerName:(NSString *)aContainerName
78 timeInterval:(NSTimeInterval)aTimeInterval
79 resetLocalState:(BOOL)resetLocalState {
80 if ((self = [super init])) {
81 directoryPath = [aDirectoryPath copy];
82 pithos = [aPithos retain];
83 containerName = [aContainerName copy];
84 timeInterval = aTimeInterval;
86 syncOperationCount = 0;
87 newSyncRequested = NO;
88 containerDirectoryPath = [[directoryPath stringByAppendingPathComponent:containerName] copy];
90 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
92 NSFileManager *fileManager = [NSFileManager defaultManager];
93 if (resetLocalState) {
95 if ([fileManager fileExistsAtPath:self.pithosStateFilePath] &&
96 (![fileManager removeItemAtPath:self.pithosStateFilePath error:&error] || error))
97 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
98 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", self.pithosStateFilePath]
100 if (self.tempDownloadsDirPath) {
102 for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
104 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
105 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath]
109 NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
110 if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
111 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
112 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
119 if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath]) {
120 NSData *data = [NSData dataWithContentsOfFile:self.pithosStateFilePath];
121 NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
122 self.storedLocalObjectStates = [unarchiver decodeObjectForKey:containerName];
123 [unarchiver finishDecoding];
125 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
128 networkQueue = [[ASINetworkQueue alloc] init];
129 networkQueue.showAccurateProgress = YES;
130 networkQueue.shouldCancelAllRequestsOnFailure = NO;
133 queue = dispatch_queue_create("gr.grnet.pithos.SyncQueue", NULL);
135 [[NSNotificationCenter defaultCenter] addObserver:self
136 selector:@selector(applicationWillTerminate:)
137 name:NSApplicationWillTerminateNotification
138 object:[NSApplication sharedApplication]];
140 timer = [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(sync) userInfo:nil repeats:YES] retain];
148 [[NSNotificationCenter defaultCenter] removeObserver:self];
149 dispatch_release(queue);
150 [networkQueue cancelAllOperations];
151 [networkQueue release];
154 [tempTrashDirPath release];
155 [tempDownloadsDirPath release];
156 [pithosStateFilePath release];
157 [containerDirectoryPath release];
158 [currentLocalObjectStates release];
159 [storedLocalObjectStates release];
160 [remoteObjects release];
162 [lastCompletedSync release];
163 [lastModified release];
165 [containerName release];
167 [directoryPath release];
172 #pragma mark Observers
174 - (void)applicationWillTerminate:(NSNotification *)notification {
175 [self saveLocalState];
179 #pragma mark Properties
181 - (NSString *)pithosStateFilePath {
182 if (!pithosStateFilePath)
183 pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
184 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
185 stringByAppendingPathComponent:@"PithosLocalObjectStates.archive"] retain];
186 return [[pithosStateFilePath copy] autorelease];
189 - (NSString *)tempDownloadsDirPath {
190 NSFileManager *fileManager = [NSFileManager defaultManager];
191 if (!tempDownloadsDirPath || ![fileManager fileExistsAtPath:tempDownloadsDirPath]) {
192 // Get the path from user defaults
193 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
194 tempDownloadsDirPath = [userDefaults stringForKey:@"PithosSyncTempDownloadsDirPath"];
195 if (tempDownloadsDirPath) {
196 // Check if the path exists
198 BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
199 NSError *error = nil;
200 if (fileExists && !isDirectory)
201 [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
202 if (!error & !fileExists)
203 [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
205 tempDownloadsDirPath = nil;
207 if (!tempDownloadsDirPath) {
208 NSString *tempDirTemplate = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
209 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
210 stringByAppendingPathComponent:@"Temp Downloads XXXXXX"];
211 const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
212 char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
213 strcpy(tempDirNameCString, tempDirTemplateCString);
214 tempDirNameCString = mkdtemp(tempDirNameCString);
215 if (tempDirNameCString != NULL)
216 tempDownloadsDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
217 free(tempDirNameCString);
219 if (tempDownloadsDirPath)
220 [userDefaults setObject:tempDownloadsDirPath forKey:@"PithosSyncTempDownloadsDirPath"];
221 [tempDownloadsDirPath retain];
223 return [[tempDownloadsDirPath copy] autorelease];
226 - (NSString *)tempTrashDirPath {
227 NSFileManager *fileManager = [NSFileManager defaultManager];
228 if (!tempTrashDirPath || ![fileManager fileExistsAtPath:tempTrashDirPath]) {
229 // Get the path from user defaults
230 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
231 tempTrashDirPath = [userDefaults stringForKey:@"PithosSyncTempTrashDirPath"];
232 if (tempTrashDirPath) {
233 // Check if the path exists
235 BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
236 NSError *error = nil;
237 if (fileExists && !isDirectory)
238 [fileManager removeItemAtPath:tempTrashDirPath error:&error];
239 if (!error & !fileExists)
240 [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
242 tempTrashDirPath = nil;
244 if (!tempTrashDirPath) {
245 NSString *tempDirTemplate = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
246 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
247 stringByAppendingPathComponent:@"Temp Trash XXXXXX"];
248 const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
249 char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
250 strcpy(tempDirNameCString, tempDirTemplateCString);
251 tempDirNameCString = mkdtemp(tempDirNameCString);
252 if (tempDirNameCString != NULL)
253 tempTrashDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
254 free(tempDirNameCString);
256 if (tempTrashDirPath)
257 [userDefaults setObject:tempTrashDirPath forKey:@"PithosSyncTempTrashDirPath"];
258 [tempTrashDirPath retain];
260 return [[tempTrashDirPath copy] autorelease];
266 - (void)saveLocalState {
267 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
268 NSMutableData *data = [NSMutableData data];
269 NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
270 [archiver encodeObject:storedLocalObjectStates forKey:containerName];
271 [archiver finishEncoding];
272 [data writeToFile:self.pithosStateFilePath atomically:YES];
276 - (void)increaseSyncOperationCount {
277 @synchronized(self) {
278 syncOperationCount++;
282 - (void)decreaseSyncOperationCount {
283 @synchronized(self) {
284 syncOperationCount--;
285 if (!syncOperationCount) {
286 if (!syncIncomplete) {
287 self.lastCompletedSync = [NSDate date];
288 dispatch_async(dispatch_get_main_queue(), ^{
289 [activityFacility startAndEndActivityWithType:PithosActivityOther
290 message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]];
294 [self emptyTempTrash];
300 @synchronized(self) {
301 if (syncOperationCount) {
302 // If at least one operation is running return
303 newSyncRequested = YES;
306 // The first operation is the server listing
307 syncOperationCount = 1;
308 newSyncRequested = NO;
313 NSFileManager *fileManager = [NSFileManager defaultManager];
315 NSError *error = nil;
316 if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
317 if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] ||
319 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
320 message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", containerDirectoryPath]
322 @synchronized(self) {
323 syncOperationCount = 0;
327 } else if (!isDirectory) {
328 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
329 message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", containerDirectoryPath]
331 @synchronized(self) {
332 syncOperationCount = 0;
337 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
338 containerName:containerName
347 ifModifiedSince:lastModified];
348 containerRequest.delegate = self;
349 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
350 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
351 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther
352 message:@"Sync: Getting server listing"];
353 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
354 activity, @"activity",
355 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage",
356 @"Sync: Getting server listing (failed)", @"failedActivityMessage",
357 @"Sync: Getting server listing (finished)", @"finishedActivityMessage",
358 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
359 [NSNumber numberWithUnsignedInteger:10], @"retries",
360 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector",
361 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector",
363 [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
366 - (void)emptyTempTrash {
367 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
368 NSString *trashDirPath = self.tempTrashDirPath;
370 NSFileManager *fileManager = [NSFileManager defaultManager];
371 NSError *error = nil;
372 // NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
373 NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
375 dispatch_async(dispatch_get_main_queue(), ^{
376 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
377 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
383 if ([subPaths count]) {
384 // NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
385 // for (NSString *subPath in subPaths) {
386 // [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
388 // syncOperationCount = 1;
389 // [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
391 // dispatch_async(dispatch_get_main_queue(), ^{
392 // [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
393 // message:@"Cannot move files to Trash"
397 // syncOperationCount = 0;
399 for (NSString *subPath in subPaths) {
400 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
402 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
403 dispatch_async(dispatch_get_main_queue(), ^{
404 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
405 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
414 - (BOOL)moveToTempTrashFile:(NSString *)filePath {
415 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
416 NSString *trashDirPath = self.tempTrashDirPath;
417 if (!tempTrashDirPath) {
421 NSFileManager *fileManager = [NSFileManager defaultManager];
423 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
424 NSError *error = nil;
425 NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath
426 withString:trashDirPath];
427 NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
428 if (fileExists && isDirectory) {
429 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
431 dispatch_async(dispatch_get_main_queue(), ^{
432 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
433 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
439 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
440 dispatch_async(dispatch_get_main_queue(), ^{
441 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
442 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
448 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
449 dispatch_async(dispatch_get_main_queue(), ^{
450 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
451 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
452 filePath, newFilePath]
458 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
460 currentState.filePath = newFilePath;
461 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
462 [currentLocalObjectStates removeObjectForKey:filePath];
464 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
469 for (NSString *subPath in subPaths) {
470 NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
471 NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath
472 withString:trashDirPath];
473 currentState = [currentLocalObjectStates objectForKey:subFilePath];
475 currentState.filePath = newSubFilePath;
476 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
477 [currentLocalObjectStates removeObjectForKey:subFilePath];
479 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
482 forKey:newSubFilePath];
485 } else if (fileExists && !isDirectory) {
486 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
487 dispatch_async(dispatch_get_main_queue(), ^{
488 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
489 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
495 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
496 dispatch_async(dispatch_get_main_queue(), ^{
497 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
498 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
499 filePath, newFilePath]
505 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
507 currentState.filePath = newFilePath;
508 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
509 [currentLocalObjectStates removeObjectForKey:filePath];
511 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
521 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
522 if ([hash length] != 64)
524 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
525 PithosLocalObjectState *localState;
526 NSFileManager *fileManager = [NSFileManager defaultManager];
528 NSError *error = nil;
529 for (NSString *localFilePath in [currentLocalObjectStates allKeys]) {
530 localState = [currentLocalObjectStates objectForKey:localFilePath];
531 if (!localState.isDirectory && [hash isEqualToString:localState.hash] &&
532 [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
533 if ([localFilePath hasPrefix:containerDirectoryPath]) {
534 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
535 dispatch_async(dispatch_get_main_queue(), ^{
536 [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
537 message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
538 localFilePath, filePath]
545 } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
546 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
547 dispatch_async(dispatch_get_main_queue(), ^{
548 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
549 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
550 localFilePath, filePath]
554 localState.filePath = filePath;
555 [currentLocalObjectStates setObject:localState forKey:filePath];
556 [currentLocalObjectStates removeObjectForKey:localFilePath];
567 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
568 localFilePath:(NSString *)filePath {
569 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
570 NSFileManager *fileManager = [NSFileManager defaultManager];
573 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
574 NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
575 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
576 // Remote updated info
577 NSError *remoteError;
578 BOOL remoteIsDirectory;
579 BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos
580 containerName:containerName
581 objectName:object.name
583 isDirectory:&remoteIsDirectory
585 if (!object || !object.objectHash) {
586 // Delete local object
587 if (remoteObjectExists) {
588 // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
589 syncIncomplete = YES;
591 NSLog(@"Sync::delete local object: %@", filePath);
592 if (!fileExists || [self moveToTempTrashFile:filePath]) {
593 dispatch_async(dispatch_get_main_queue(), ^{
594 [activityFacility startAndEndActivityWithType:PithosActivityOther
595 message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]];
597 [storedLocalObjectStates removeObjectForKey:object.name];
598 [self saveLocalState];
600 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
601 // Create local directory object
602 if (!remoteObjectExists || !remoteIsDirectory) {
603 // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
604 syncIncomplete = YES;
608 NSLog(@"Sync::create local directory object: %@", filePath);
609 BOOL directoryCreated = NO;
610 if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath])) {
611 NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
613 if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
614 dispatch_async(dispatch_get_main_queue(), ^{
615 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
616 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
620 directoryCreated = YES;
621 storedState.isDirectory = YES;
622 [self saveLocalState];
625 NSLog(@"Sync::local directory object exists: %@", filePath);
626 directoryCreated = YES;
628 if (directoryCreated)
629 dispatch_async(dispatch_get_main_queue(), ^{
630 [activityFacility startAndEndActivityWithType:PithosActivityOther
631 message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name]];
633 } else if (object.bytes == 0) {
634 // Create local object with zero length
635 if (!remoteObjectExists || remoteIsDirectory) {
636 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
637 syncIncomplete = YES;
641 NSLog(@"Sync::create local zero length object: %@", filePath);
642 BOOL fileCreated = NO;
643 if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath])) {
644 NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
645 // Create directory of the file, if it doesn't exist
647 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
648 dispatch_async(dispatch_get_main_queue(), ^{
649 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
650 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
655 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
656 dispatch_async(dispatch_get_main_queue(), ^{
657 [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
658 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
663 storedState.hash = object.objectHash;
664 storedState.tmpFilePath = nil;
665 [self saveLocalState];
668 NSLog(@"Sync::local zero length object exists: %@", filePath);
672 dispatch_async(dispatch_get_main_queue(), ^{
673 [activityFacility startAndEndActivityWithType:PithosActivityOther
674 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
676 } else if (storedState.tmpFilePath == nil) {
677 // Create new local object
678 if (!remoteObjectExists || remoteIsDirectory) {
679 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
680 syncIncomplete = YES;
684 // Create directory of the file, if it doesn't exist
686 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
687 dispatch_async(dispatch_get_main_queue(), ^{
688 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
689 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
693 // Check first if a local copy exists
694 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
695 dispatch_async(dispatch_get_main_queue(), ^{
696 [activityFacility startAndEndActivityWithType:PithosActivityOther
697 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
700 [self increaseSyncOperationCount];
701 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
702 containerName:containerName
705 blockSize:blockSize];
706 objectRequest.delegate = self;
707 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
708 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
709 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
710 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name]
711 totalBytes:object.bytes
713 dispatch_async(dispatch_get_main_queue(), ^{
714 [activityFacility updateActivity:activity withMessage:activity.message];
716 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
717 object, @"pithosObject",
718 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(blockSize + 0.0)))], @"missingBlocks",
719 [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
720 filePath, @"filePath",
721 activity, @"activity",
722 [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage",
723 [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage",
724 [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage",
725 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
726 [NSNumber numberWithUnsignedInteger:10], @"retries",
727 NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector",
728 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
730 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
731 [activityFacility updateActivity:activity
732 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
733 [[objectRequest.userInfo objectForKey:@"pithosObject"] name],
734 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
735 totalBytes:activity.totalBytes
736 currentBytes:(activity.currentBytes + size)];
738 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
741 // Resume local object download
742 if (!remoteObjectExists || remoteIsDirectory) {
743 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
744 syncIncomplete = YES;
748 // Create directory of the file, if it doesn't exist
750 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
751 dispatch_async(dispatch_get_main_queue(), ^{
752 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
753 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
757 // Check first if a local copy exists
758 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
759 dispatch_async(dispatch_get_main_queue(), ^{
760 [activityFacility startAndEndActivityWithType:PithosActivityOther
761 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
763 // Delete incomplete temp download
765 storedState.tmpFilePath = nil;
767 [self increaseSyncOperationCount];
768 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
769 containerName:containerName
770 objectName:object.name];
771 objectRequest.delegate = self;
772 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
773 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
774 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
775 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name]
776 totalBytes:object.bytes
778 dispatch_async(dispatch_get_main_queue(), ^{
779 [activityFacility updateActivity:activity withMessage:activity.message];
781 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
782 object, @"pithosObject",
783 filePath, @"filePath",
784 activity, @"activity",
785 [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage",
786 [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage",
787 [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage",
788 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
789 [NSNumber numberWithUnsignedInteger:10], @"retries",
790 NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector",
791 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
793 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
799 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
800 object:(ASIPithosObject *)object
801 localFilePath:(NSString *)filePath {
802 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
803 [self increaseSyncOperationCount];
804 NSFileManager *fileManager = [NSFileManager defaultManager];
806 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
807 if (currentState.isDirectory) {
808 // Create remote directory object
809 if (!fileExists || !isDirectory) {
810 // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
811 syncIncomplete = YES;
812 [self decreaseSyncOperationCount];
816 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
817 containerName:containerName
818 objectName:object.name
820 contentType:@"application/directory"
822 contentDisposition:nil
825 isPublic:ASIPithosObjectRequestPublicIgnore
828 objectRequest.delegate = self;
829 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
830 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
831 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
832 message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]];
833 dispatch_async(dispatch_get_main_queue(), ^{
834 [activityFacility updateActivity:activity withMessage:activity.message];
836 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
837 object, @"pithosObject",
838 activity, @"activity",
839 [NSString stringWithFormat:@"Sync: Creating directory '%@' (stopped)", object.name], @"stoppedActivityMessage",
840 [NSString stringWithFormat:@"Sync: Creating directory '%@' (failed)", object.name], @"failedActivityMessage",
841 [NSString stringWithFormat:@"Sync: Creating directory '%@' (finished)", object.name], @"finishedActivityMessage",
842 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
843 [NSNumber numberWithUnsignedInteger:10], @"retries",
844 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
845 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
847 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
848 } else if (!currentState.exists) {
849 // Delete remote object
851 // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
852 syncIncomplete = YES;
855 if ([PithosUtilities isContentTypeDirectory:object.contentType])
856 safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
858 safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
860 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
861 containerName:containerName
862 objectName:object.name
863 destinationContainerName:@"trash"
864 destinationObjectName:safeName
867 objectRequest.delegate = self;
868 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
869 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
870 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
871 message:[NSString stringWithFormat:@"Sync: Moving to trash '%@'", object.name]];
872 dispatch_async(dispatch_get_main_queue(), ^{
873 [activityFacility updateActivity:activity withMessage:activity.message];
875 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
876 object, @"pithosObject",
877 activity, @"activity",
878 [NSString stringWithFormat:@"Sync: Moving to trash '%@' (stopped)", object.name], @"stoppedActivityMessage",
879 [NSString stringWithFormat:@"Sync: Moving to trash '%@' (failed)", object.name], @"failedActivityMessage",
880 [NSString stringWithFormat:@"Sync: Moving to trash '%@' (finished)", object.name], @"finishedActivityMessage",
881 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
882 [NSNumber numberWithUnsignedInteger:10], @"retries",
883 NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector",
884 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
886 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
888 syncIncomplete = YES;
889 [self decreaseSyncOperationCount];
892 syncIncomplete = YES;
893 [self decreaseSyncOperationCount];
896 // Upload file to remote object
897 if (!fileExists || isDirectory) {
898 // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
899 syncIncomplete = YES;
900 [self decreaseSyncOperationCount];
904 NSError *error = nil;
905 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
906 if (object.contentType == nil)
907 object.contentType = @"application/octet-stream";
909 NSLog(@"contentType detection error: %@", error);
910 NSArray *hashes = nil;
911 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
912 containerName:containerName
913 objectName:object.name
914 contentType:object.contentType
922 objectRequest.delegate = self;
923 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
924 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
925 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
926 message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
927 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
929 dispatch_async(dispatch_get_main_queue(), ^{
930 [activityFacility updateActivity:activity withMessage:activity.message];
932 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
933 [NSDictionary dictionaryWithObjectsAndKeys:
934 object, @"pithosObject",
935 filePath, @"filePath",
937 [NSNumber numberWithUnsignedInteger:10], @"iteration",
938 activity, @"activity",
939 [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage",
940 [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage",
941 [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage",
942 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
943 [NSNumber numberWithUnsignedInteger:10], @"retries",
944 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
945 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
947 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
949 syncIncomplete = YES;
950 [self decreaseSyncOperationCount];
957 #pragma mark ASIHTTPRequestDelegate
959 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
960 dispatch_async(queue, ^{
961 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) withObject:request];
965 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
966 dispatch_async(queue, ^{
967 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) withObject:request];
971 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
972 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
973 NSLog(@"Sync::list request finished: %@", containerRequest.url);
974 if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
975 if (containerRequest.responseStatusCode == 200) {
976 NSArray *someObjects = [containerRequest objects];
977 if (objects == nil) {
978 objects = [[NSMutableArray alloc] initWithArray:someObjects];
980 [objects addObjectsFromArray:someObjects];
982 if ([someObjects count] < 10000) {
983 self.blockHash = [containerRequest blockHash];
984 self.blockSize = [containerRequest blockSize];
985 self.lastModified = [containerRequest lastModified];
986 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
987 for (ASIPithosObject *object in objects) {
988 [remoteObjects setObject:object forKey:object.name];
993 // Do an additional request to fetch more objects
994 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
995 containerName:containerName
997 marker:[[someObjects lastObject] name]
1004 ifModifiedSince:lastModified];
1005 newContainerRequest.delegate = self;
1006 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1007 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1008 newContainerRequest.userInfo = newContainerRequest.userInfo;
1009 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1010 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1015 dispatch_async(dispatch_get_main_queue(), ^{
1016 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1017 withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1019 NSFileManager *fileManager = [NSFileManager defaultManager];
1020 NSError *error = nil;
1021 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
1023 dispatch_async(dispatch_get_main_queue(), ^{
1024 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
1025 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
1027 [activityFacility startAndEndActivityWithType:PithosActivityOther message:@"Sync: Failed to read contents of sync directory"];
1029 @synchronized(self) {
1030 // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccessfully
1031 syncOperationCount = 0;
1032 if (newSyncRequested)
1038 self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:[subPaths count]];
1039 for (NSString *objectName in subPaths) {
1040 if (![storedLocalObjectStates objectForKey:objectName]) {
1041 [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1043 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1044 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
1046 blockSize:blockSize]
1049 [self saveLocalState];
1051 for (NSString *objectName in remoteObjects) {
1052 if (![storedLocalObjectStates objectForKey:objectName])
1053 [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1056 for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1057 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1058 if ([objectName hasSuffix:@"/"])
1059 filePath = [filePath stringByAppendingString:@":"];
1060 ASIPithosObject *object = [ASIPithosObject object];
1061 object.name = objectName;
1062 NSLog(@"Sync::object name: %@", objectName);
1064 PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
1065 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1066 if (!currentLocalObjectState)
1067 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
1069 blockSize:blockSize];
1070 if (currentLocalObjectState.isDirectory)
1071 object.contentType = @"application/directory";
1073 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1074 ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
1076 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1077 remoteObjectState.isDirectory = YES;
1078 object.contentType = @"application/directory";
1080 remoteObjectState.hash = remoteObject.objectHash;
1084 BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1085 BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1086 NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1087 if (!localStateHasChanged) {
1088 // Local state hasn't changed
1089 if (serverStateHasChanged) {
1090 // Server state has changed
1091 // Update local state to match that of the server
1092 object.bytes = remoteObject.bytes;
1093 object.version = remoteObject.version;
1094 object.contentType = remoteObject.contentType;
1095 object.objectHash = remoteObject.objectHash;
1096 [self updateLocalStateWithObject:object localFilePath:filePath];
1097 } else if (!remoteObject && !currentLocalObjectState.exists) {
1098 // Server state hasn't changed
1099 // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1100 [storedLocalObjectStates removeObjectForKey:objectName];
1101 [self saveLocalState];
1104 // Local state has changed
1105 if (!serverStateHasChanged) {
1106 // Server state hasn't changed
1107 [self updateServerStateWithCurrentState:currentLocalObjectState
1109 localFilePath:filePath];
1111 // Server state has also changed
1112 if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1113 // Both did the same change (directory)
1114 storedLocalObjectState.isDirectory = YES;
1115 [self saveLocalState];
1116 } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1117 // Both did the same change (object edit or delete)
1118 if (!remoteObjectState.exists)
1119 [storedLocalObjectStates removeObjectForKey:object.name];
1121 storedLocalObjectState.hash = remoteObjectState.hash;
1122 [self saveLocalState];
1124 // Conflict, we ask the user which change to keep
1125 NSString *informativeText;
1126 NSString *firstButtonText;
1127 NSString *secondButtonText;
1129 if (!remoteObjectState.exists) {
1130 // Remote object has been deleted
1131 informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
1132 firstButtonText = @"Delete local file";
1133 secondButtonText = @"Upload file to server";
1134 } else if (!currentLocalObjectState.exists) {
1135 informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
1136 firstButtonText = @"Download file from server";
1137 secondButtonText = @"Delete file on server";
1139 informativeText = [NSString stringWithFormat:@"'%@' has been modifed both locally and on the server.", object.name];
1140 firstButtonText = @"Keep server version";
1141 secondButtonText = @"Keep local version";
1143 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1144 [alert setMessageText:@"Conflict"];
1145 [alert setInformativeText:informativeText];
1146 [alert addButtonWithTitle:firstButtonText];
1147 [alert addButtonWithTitle:secondButtonText];
1148 [alert addButtonWithTitle:@"Do nothing"];
1149 NSInteger choice = [alert runModal];
1150 if (choice == NSAlertFirstButtonReturn) {
1151 object.bytes = remoteObject.bytes;
1152 object.version = remoteObject.version;
1153 object.contentType = remoteObject.contentType;
1154 object.objectHash = remoteObject.objectHash;
1155 [self updateLocalStateWithObject:object localFilePath:filePath];
1156 } if (choice == NSAlertSecondButtonReturn) {
1157 [self updateServerStateWithCurrentState:currentLocalObjectState
1159 localFilePath:filePath];
1165 @synchronized(self) {
1166 [self decreaseSyncOperationCount];
1167 if (newSyncRequested && !syncOperationCount)
1171 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1173 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1174 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1175 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1177 dispatch_async(dispatch_get_main_queue(), ^{
1178 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1179 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1181 @synchronized(self) {
1182 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1183 syncOperationCount = 0;
1184 if (newSyncRequested)
1192 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1193 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1194 if ([containerRequest isCancelled]) {
1195 dispatch_async(dispatch_get_main_queue(), ^{
1196 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1197 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1201 @synchronized(self) {
1202 syncOperationCount = 0;
1207 // If the server listing fails, the sync should start over, so just retrying is enough
1208 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1210 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1211 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1212 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1214 dispatch_async(dispatch_get_main_queue(), ^{
1215 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1216 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1220 @synchronized(self) {
1221 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1222 syncOperationCount = 0;
1223 if (newSyncRequested)
1230 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1231 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1232 NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1233 if (objectRequest.responseStatusCode == 206) {
1234 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1235 NSFileManager *fileManager = [NSFileManager defaultManager];
1237 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1239 NSString *downloadsDirPath = self.tempDownloadsDirPath;
1240 if (!downloadsDirPath) {
1241 dispatch_async(dispatch_get_main_queue(), ^{
1242 [activityFacility endActivity:activity
1243 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1245 @synchronized(self) {
1246 syncIncomplete = YES;
1247 [self decreaseSyncOperationCount];
1248 if (newSyncRequested && !syncOperationCount)
1255 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1256 if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1257 NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1258 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1259 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1260 strcpy(tempFileNameCString, tempFileTemplateCString);
1261 int fileDescriptor = mkstemp(tempFileNameCString);
1262 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1263 free(tempFileNameCString);
1264 if (fileDescriptor == -1) {
1265 dispatch_async(dispatch_get_main_queue(), ^{
1266 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
1267 message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]
1269 [activityFacility endActivity:activity
1270 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1272 @synchronized(self) {
1273 syncIncomplete = YES;
1274 [self decreaseSyncOperationCount];
1275 if (newSyncRequested && !syncOperationCount)
1281 close(fileDescriptor);
1282 storedState.tmpFilePath = tempFilePath;
1283 [self saveLocalState];
1287 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1288 NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1289 [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
1290 [tempFileHandle writeData:[objectRequest responseData]];
1291 [tempFileHandle closeFile];
1293 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1294 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1295 if (missingBlockIndex == NSNotFound) {
1296 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1297 NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1298 if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath]) {
1299 dispatch_async(dispatch_get_main_queue(), ^{
1300 [activityFacility endActivity:activity
1301 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1303 @synchronized(self) {
1304 syncIncomplete = YES;
1305 [self decreaseSyncOperationCount];
1306 if (newSyncRequested && !syncOperationCount)
1311 } else if (![fileManager fileExistsAtPath:dirPath]) {
1312 // File doesn't exist but also the containing directory doesn't exist
1313 // In most cases this should have been resolved as an update of the corresponding local object,
1314 // but it never hurts to check
1316 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1318 dispatch_async(dispatch_get_main_queue(), ^{
1319 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1320 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
1322 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1323 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1325 @synchronized(self) {
1326 syncIncomplete = YES;
1327 [self decreaseSyncOperationCount];
1328 if (newSyncRequested && !syncOperationCount)
1335 // Move file from tmp download
1337 [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1339 dispatch_async(dispatch_get_main_queue(), ^{
1340 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
1341 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath]
1343 [activityFacility endActivity:activity
1344 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1346 @synchronized(self) {
1347 syncIncomplete = YES;
1348 [self decreaseSyncOperationCount];
1349 if (newSyncRequested && !syncOperationCount)
1355 dispatch_async(dispatch_get_main_queue(), ^{
1356 [activityFacility endActivity:activity
1357 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1358 totalBytes:activity.totalBytes
1359 currentBytes:activity.totalBytes];
1362 storedState.hash = object.objectHash;
1363 storedState.tmpFilePath = nil;
1364 [self saveLocalState];
1366 @synchronized(self) {
1367 [self decreaseSyncOperationCount];
1368 if (newSyncRequested && !syncOperationCount)
1374 if (newSyncRequested) {
1375 dispatch_async(dispatch_get_main_queue(), ^{
1376 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1377 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1379 @synchronized(self) {
1380 syncIncomplete = YES;
1381 [self decreaseSyncOperationCount];
1382 if (!syncOperationCount)
1388 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1389 containerName:containerName
1391 blockIndex:missingBlockIndex
1392 blockSize:blockSize];
1393 newObjectRequest.delegate = self;
1394 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1395 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1396 newObjectRequest.userInfo = objectRequest.userInfo;
1397 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1398 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1399 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1400 [activityFacility updateActivity:activity
1401 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1403 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1404 totalBytes:activity.totalBytes
1405 currentBytes:(activity.currentBytes + size)];
1407 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1410 } else if (objectRequest.responseStatusCode == 412) {
1411 // The object has changed on the server
1412 dispatch_async(dispatch_get_main_queue(), ^{
1413 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1414 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1416 @synchronized(self) {
1417 syncIncomplete = YES;
1418 [self decreaseSyncOperationCount];
1419 if (newSyncRequested && !syncOperationCount)
1423 [self requestFailed:objectRequest];
1428 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1429 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1430 NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1431 if (objectRequest.responseStatusCode == 200) {
1432 if (newSyncRequested) {
1433 dispatch_async(dispatch_get_main_queue(), ^{
1434 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1435 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1437 @synchronized(self) {
1438 syncIncomplete = YES;
1439 [self decreaseSyncOperationCount];
1440 if (!syncOperationCount)
1446 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1447 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1448 if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1449 [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
1450 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1451 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath
1454 withHashes:[objectRequest hashes]];
1455 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1456 dispatch_async(dispatch_get_main_queue(), ^{
1457 [activityFacility endActivity:activity
1458 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1460 (100*(activity.totalBytes - [missingBlocks count]*blockSize + 0.0)/(activity.totalBytes + 0.0))]
1461 totalBytes:activity.totalBytes
1462 currentBytes:(activity.totalBytes - [missingBlocks count]*blockSize)];
1465 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1466 containerName:containerName
1468 blockIndex:missingBlockIndex
1469 blockSize:blockSize];
1470 newObjectRequest.delegate = self;
1471 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1472 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1473 newObjectRequest.userInfo = objectRequest.userInfo;
1474 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1475 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1476 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1477 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1478 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1479 [activityFacility updateActivity:activity
1480 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1482 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1483 totalBytes:activity.totalBytes
1484 currentBytes:(activity.currentBytes + size)];
1486 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1489 [self requestFailed:objectRequest];
1494 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1495 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1496 NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1497 if (objectRequest.responseStatusCode == 201) {
1498 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1499 storedState.isDirectory = YES;
1500 [self saveLocalState];
1501 dispatch_async(dispatch_get_main_queue(), ^{
1502 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1503 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1505 @synchronized(self) {
1506 [self decreaseSyncOperationCount];
1507 if (newSyncRequested && !syncOperationCount)
1511 [self requestFailed:objectRequest];
1516 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1517 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1518 NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1519 if (objectRequest.responseStatusCode == 201) {
1520 [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1521 [self saveLocalState];
1522 dispatch_async(dispatch_get_main_queue(), ^{
1523 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1524 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1526 @synchronized(self) {
1527 [self decreaseSyncOperationCount];
1528 if (newSyncRequested && !syncOperationCount)
1532 [self requestFailed:objectRequest];
1537 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1538 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1539 NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1540 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1541 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1542 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1543 NSUInteger totalBytes = activity.totalBytes;
1544 NSUInteger currentBytes = activity.currentBytes;
1545 if (objectRequest.responseStatusCode == 201) {
1546 NSLog(@"Sync::object created: %@", objectRequest.url);
1547 storedState.hash = [objectRequest objectHash];
1548 [self saveLocalState];
1549 dispatch_async(dispatch_get_main_queue(), ^{
1550 [activityFacility endActivity:activity
1551 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1552 totalBytes:totalBytes
1553 currentBytes:totalBytes];
1555 @synchronized(self) {
1556 [self decreaseSyncOperationCount];
1557 if (newSyncRequested && !syncOperationCount)
1560 } else if (objectRequest.responseStatusCode == 409) {
1561 if (newSyncRequested) {
1562 dispatch_async(dispatch_get_main_queue(), ^{
1563 [activityFacility endActivity:activity
1564 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1566 @synchronized(self) {
1567 syncIncomplete = YES;
1568 [self decreaseSyncOperationCount];
1569 if (!syncOperationCount)
1575 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1576 if (iteration == 0) {
1577 NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
1578 dispatch_async(dispatch_get_main_queue(), ^{
1579 [activityFacility endActivity:activity
1580 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1582 syncIncomplete = YES;
1583 [self decreaseSyncOperationCount];
1587 NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
1588 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1589 withMissingHashes:[objectRequest hashes]];
1590 if (totalBytes >= [missingBlocks count]*blockSize)
1591 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1592 dispatch_async(dispatch_get_main_queue(), ^{
1593 [activityFacility updateActivity:activity
1594 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
1595 totalBytes:totalBytes
1596 currentBytes:currentBytes];
1598 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1599 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1600 containerName:containerName
1602 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1603 missingBlockIndex:missingBlockIndex
1604 sharingAccount:nil];
1605 newContainerRequest.delegate = self;
1606 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1607 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1608 newContainerRequest.userInfo = objectRequest.userInfo;
1609 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1610 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1611 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1612 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1613 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1614 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1615 [activityFacility updateActivity:activity
1616 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1617 totalBytes:activity.totalBytes
1618 currentBytes:(activity.currentBytes + size)];
1620 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1623 [self requestFailed:objectRequest];
1628 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1629 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1630 NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
1631 if (containerRequest.responseStatusCode == 202) {
1632 ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
1633 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1634 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1635 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1636 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1637 if (missingBlockIndex == NSNotFound) {
1638 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1639 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1640 containerName:containerName
1641 objectName:object.name
1642 contentType:object.contentType
1645 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1648 sharingAccount:nil];
1649 newObjectRequest.delegate = self;
1650 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1651 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1652 newObjectRequest.userInfo = containerRequest.userInfo;
1653 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1654 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1655 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1656 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1657 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1659 if (newSyncRequested) {
1660 dispatch_async(dispatch_get_main_queue(), ^{
1661 [activityFacility endActivity:activity
1662 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1664 @synchronized(self) {
1665 syncIncomplete = YES;
1666 [self decreaseSyncOperationCount];
1667 if (!syncOperationCount)
1673 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1674 containerName:containerName
1676 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1677 missingBlockIndex:missingBlockIndex
1678 sharingAccount:nil];
1679 newContainerRequest.delegate = self;
1680 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1681 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1682 newContainerRequest.userInfo = containerRequest.userInfo;
1683 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1684 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1685 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1686 [activityFacility updateActivity:activity
1687 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1688 totalBytes:activity.totalBytes
1689 currentBytes:(activity.currentBytes + size)];
1691 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1695 [self requestFailed:containerRequest];
1700 - (void)requestFailed:(ASIPithosRequest *)request {
1701 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1702 if ([request isCancelled]) {
1703 dispatch_async(dispatch_get_main_queue(), ^{
1704 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1705 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1707 syncIncomplete = YES;
1708 [self decreaseSyncOperationCount];
1712 if (newSyncRequested) {
1713 dispatch_async(dispatch_get_main_queue(), ^{
1714 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1715 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1717 @synchronized(self) {
1718 syncIncomplete = YES;
1719 [self decreaseSyncOperationCount];
1720 if (!syncOperationCount)
1726 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1728 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1729 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1730 [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1732 dispatch_async(dispatch_get_main_queue(), ^{
1733 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1734 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1736 syncIncomplete = YES;
1737 [self decreaseSyncOperationCount];