5 // Copyright 2011 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
15 // 2. Redistributions in binary form must reproduce the above
16 // copyright notice, this list of conditions and the following
17 // disclaimer in the documentation and/or other materials
18 // provided with the distribution.
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
38 #import "PithosSyncDaemon.h"
39 #import "PithosLocalObjectState.h"
40 #import "PithosActivityFacility.h"
41 #import "PithosUtilities.h"
42 #import "ASINetworkQueue.h"
43 #import "ASIPithosRequest.h"
44 #import "ASIPithosContainerRequest.h"
45 #import "ASIPithosObjectRequest.h"
46 #import "ASIPithosObject.h"
48 @interface PithosSyncDaemon (Private)
49 - (NSString *)pithosStateFilePath;
50 - (void)saveLocalState;
52 - (BOOL)moveToTempTrashFile:(NSString *)filePath;
53 - (void)emptyTempTrash;
54 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
56 - (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath;
57 - (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
58 object:(ASIPithosObject *)object
59 localFilePath:(NSString *)filePath;
60 - (void)requestFailed:(ASIPithosRequest *)request;
62 - (void)increaseSyncOperationCount;
63 - (void)decreaseSyncOperationCount;
67 @implementation PithosSyncDaemon
68 @synthesize blockHash, blockSize, lastModified, lastCompletedSync, remoteObjects, storedLocalObjectStates, currentLocalObjectStates;
69 @synthesize directoryPath, containerDirectoryPath, pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
72 #pragma Object Lifecycle
74 - (id)initWithDirectoryPath:(NSString *)aDirectoryPath
75 containerName:(NSString *)aContainerName
76 timeInterval:(NSTimeInterval)aTimeInterval
77 resetLocalState:(BOOL)resetLocalState {
78 if ((self = [super init])) {
79 directoryPath = [aDirectoryPath copy];
80 containerName = [aContainerName copy];
81 timeInterval = aTimeInterval;
83 syncOperationCount = 0;
84 newSyncRequested = NO;
85 containerDirectoryPath = [[directoryPath stringByAppendingPathComponent:containerName] copy];
87 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
89 NSFileManager *fileManager = [NSFileManager defaultManager];
90 if (resetLocalState) {
92 if ([fileManager fileExistsAtPath:self.pithosStateFilePath] &&
93 (![fileManager removeItemAtPath:self.pithosStateFilePath error:&error] || error))
94 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
95 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", self.pithosStateFilePath]
97 if (self.tempDownloadsDirPath) {
99 for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
101 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
102 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath]
106 NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
107 if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
108 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
109 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
116 if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath]) {
117 NSData *data = [NSData dataWithContentsOfFile:self.pithosStateFilePath];
118 NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
119 self.storedLocalObjectStates = [unarchiver decodeObjectForKey:containerName];
120 [unarchiver finishDecoding];
122 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
125 queue = [[ASINetworkQueue alloc] init];
126 queue.showAccurateProgress = YES;
127 queue.shouldCancelAllRequestsOnFailure = NO;
130 [[NSNotificationCenter defaultCenter] addObserver:self
131 selector:@selector(applicationWillTerminate:)
132 name:NSApplicationWillTerminateNotification
133 object:[NSApplication sharedApplication]];
135 timer = [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(sync) userInfo:nil repeats:YES] retain];
143 [[NSNotificationCenter defaultCenter] removeObserver:self];
144 [queue cancelAllOperations];
148 [tempTrashDirPath release];
149 [tempDownloadsDirPath release];
150 [pithosStateFilePath release];
151 [containerDirectoryPath release];
152 [currentLocalObjectStates release];
153 [storedLocalObjectStates release];
154 [remoteObjects release];
156 [lastCompletedSync release];
157 [lastModified release];
159 [containerName release];
160 [directoryPath release];
165 #pragma mark Observers
167 - (void)applicationWillTerminate:(NSNotification *)notification {
168 [self saveLocalState];
172 #pragma mark Properties
174 - (NSString *)pithosStateFilePath {
175 if (!pithosStateFilePath)
176 pithosStateFilePath = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"PithosLocalObjectStates.archive"] retain];
177 return [pithosStateFilePath copy];
180 - (NSString *)tempDownloadsDirPath {
181 NSFileManager *fileManager = [NSFileManager defaultManager];
182 if (!tempDownloadsDirPath || ![fileManager fileExistsAtPath:tempDownloadsDirPath]) {
183 // Get the path from user defaults
184 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
185 tempDownloadsDirPath = [userDefaults stringForKey:@"PithosSyncTempDownloadsDirPath"];
186 if (tempDownloadsDirPath) {
187 // Check if the path exists
189 BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
190 NSError *error = nil;
191 if (fileExists && !isDirectory)
192 [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
193 if (!error & !fileExists)
194 [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
196 tempDownloadsDirPath = nil;
198 if (!tempDownloadsDirPath) {
199 NSString *tempDirTemplate = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Temp Downloads XXXXXX"];
200 const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
201 char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
202 strcpy(tempDirNameCString, tempDirTemplateCString);
203 tempDirNameCString = mkdtemp(tempDirNameCString);
204 if (tempDirNameCString != NULL)
205 tempDownloadsDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
206 free(tempDirNameCString);
208 if (tempDownloadsDirPath)
209 [userDefaults setObject:tempDownloadsDirPath forKey:@"PithosSyncTempDownloadsDirPath"];
210 [tempDownloadsDirPath retain];
212 return [tempDownloadsDirPath copy];
215 - (NSString *)tempTrashDirPath {
216 NSFileManager *fileManager = [NSFileManager defaultManager];
217 if (!tempTrashDirPath || ![fileManager fileExistsAtPath:tempTrashDirPath]) {
218 // Get the path from user defaults
219 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
220 tempTrashDirPath = [userDefaults stringForKey:@"PithosSyncTempTrashDirPath"];
221 if (tempTrashDirPath) {
222 // Check if the path exists
224 BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
225 NSError *error = nil;
226 if (fileExists && !isDirectory)
227 [fileManager removeItemAtPath:tempTrashDirPath error:&error];
228 if (!error & !fileExists)
229 [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
231 tempTrashDirPath = nil;
233 if (!tempTrashDirPath) {
234 NSString *tempDirTemplate = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Temp Trash XXXXXX"];
235 const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
236 char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
237 strcpy(tempDirNameCString, tempDirTemplateCString);
238 tempDirNameCString = mkdtemp(tempDirNameCString);
239 if (tempDirNameCString != NULL)
240 tempTrashDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
241 free(tempDirNameCString);
243 if (tempTrashDirPath)
244 [userDefaults setObject:tempTrashDirPath forKey:@"PithosSyncTempTrashDirPath"];
245 [tempTrashDirPath retain];
247 return [tempTrashDirPath copy];
253 - (void)saveLocalState {
254 NSMutableData *data = [NSMutableData data];
255 NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
256 [archiver encodeObject:storedLocalObjectStates forKey:containerName];
257 [archiver finishEncoding];
258 [data writeToFile:self.pithosStateFilePath atomically:YES];
261 - (void)increaseSyncOperationCount {
262 @synchronized(self) {
263 syncOperationCount++;
267 - (void)decreaseSyncOperationCount {
268 @synchronized(self) {
269 syncOperationCount--;
270 if (!syncOperationCount) {
271 if (!syncIncomplete) {
272 self.lastCompletedSync = [NSDate date];
273 [activityFacility startAndEndActivityWithType:PithosActivityOther
274 message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]];
276 [self emptyTempTrash];
282 @synchronized(self) {
283 if (syncOperationCount) {
284 // If at least one operation is running return
285 newSyncRequested = YES;
288 // The first operation is the server listing
289 syncOperationCount = 1;
290 newSyncRequested = NO;
295 NSFileManager *fileManager = [NSFileManager defaultManager];
297 NSError *error = nil;
298 if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
299 if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] ||
301 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
302 message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", containerDirectoryPath]
304 @synchronized(self) {
305 syncOperationCount = 0;
309 } else if (!isDirectory) {
310 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
311 message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", containerDirectoryPath]
313 @synchronized(self) {
314 syncOperationCount = 0;
319 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName
328 ifModifiedSince:lastModified];
329 containerRequest.delegate = self;
330 containerRequest.didFinishSelector = @selector(listRequestFinished:);
331 containerRequest.didFailSelector = @selector(listRequestFailed:);
332 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther
333 message:@"Sync: Getting server listing"];
334 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
335 activity, @"activity",
336 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage",
337 @"Sync: Getting server listing (failed)", @"failedActivityMessage",
338 @"Sync: Getting server listing (finished)", @"finishedActivityMessage",
339 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority",
340 [NSNumber numberWithUnsignedInteger:10], @"retries",
342 [queue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh]];
345 - (void)emptyTempTrash {
346 NSString *trashDirPath = self.tempTrashDirPath;
348 NSFileManager *fileManager = [NSFileManager defaultManager];
349 NSError *error = nil;
350 // NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
351 NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
353 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
354 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
358 if ([subPaths count]) {
359 // NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
360 // for (NSString *subPath in subPaths) {
361 // [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
363 // syncOperationCount = 1;
364 // [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
366 // [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
367 // message:@"Cannot move files to Trash"
370 // syncOperationCount = 0;
372 for (NSString *subPath in subPaths) {
373 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
375 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
376 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
377 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
384 - (BOOL)moveToTempTrashFile:(NSString *)filePath {
385 NSString *trashDirPath = self.tempTrashDirPath;
386 if (!tempTrashDirPath)
388 NSFileManager *fileManager = [NSFileManager defaultManager];
390 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
391 NSError *error = nil;
392 NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath
393 withString:trashDirPath];
394 NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
395 if (fileExists && isDirectory) {
396 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
398 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
399 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
403 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
404 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
405 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
409 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
410 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
411 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
412 filePath, newFilePath]
416 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
418 currentState.filePath = newFilePath;
419 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
420 [currentLocalObjectStates removeObjectForKey:filePath];
422 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
427 for (NSString *subPath in subPaths) {
428 NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
429 NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath
430 withString:trashDirPath];
431 currentState = [currentLocalObjectStates objectForKey:subFilePath];
433 currentState.filePath = newSubFilePath;
434 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
435 [currentLocalObjectStates removeObjectForKey:subFilePath];
437 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
440 forKey:newSubFilePath];
443 } else if (fileExists && !isDirectory) {
444 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
445 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
446 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
450 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
451 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
452 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
453 filePath, newFilePath]
457 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
459 currentState.filePath = newFilePath;
460 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
461 [currentLocalObjectStates removeObjectForKey:filePath];
463 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
472 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
473 NSUInteger hashLength = [hash length];
474 if ((hashLength != 32) && (hashLength != 64))
476 PithosLocalObjectState *localState;
477 NSFileManager *fileManager = [NSFileManager defaultManager];
479 NSError *error = nil;
480 for (NSString *localFilePath in [currentLocalObjectStates allKeys]) {
481 localState = [currentLocalObjectStates objectForKey:localFilePath];
482 if (!localState.isDirectory && ([hash isEqualToString:localState.md5] || [hash isEqualToString:localState.hashMapHash]) &&
483 [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
484 if ([localFilePath hasPrefix:containerDirectoryPath]) {
485 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
486 [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
487 message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
488 localFilePath, filePath]
493 } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
494 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
495 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
496 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
497 localFilePath, filePath]
500 localState.filePath = filePath;
501 [currentLocalObjectStates setObject:localState forKey:filePath];
502 [currentLocalObjectStates removeObjectForKey:localFilePath];
511 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
512 localFilePath:(NSString *)filePath {
513 NSFileManager *fileManager = [NSFileManager defaultManager];
516 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
517 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
518 if (!object || !object.hash) {
519 // Delete local object
520 NSLog(@"Sync::delete local object: %@", filePath);
521 if (!fileExists || [self moveToTempTrashFile:filePath]) {
522 [activityFacility startAndEndActivityWithType:PithosActivityOther
523 message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]];
524 [storedLocalObjectStates removeObjectForKey:object.name];
525 [self saveLocalState];
527 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
528 // Create local directory object
529 NSLog(@"Sync::create local directory object: %@", filePath);
530 BOOL directoryCreated = NO;
531 if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath])) {
532 NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
534 if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
535 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
536 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
539 directoryCreated = YES;
540 storedState.isDirectory = YES;
541 [self saveLocalState];
544 NSLog(@"Sync::local directory object exists: %@", filePath);
545 directoryCreated = YES;
547 if (directoryCreated)
548 [activityFacility startAndEndActivityWithType:PithosActivityOther
549 message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name]];
550 } else if (object.bytes == 0) {
551 // Create local object with zero length
552 NSLog(@"Sync::create local zero length object: %@", filePath);
553 BOOL fileCreated = NO;
554 if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath])) {
555 NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
557 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
558 [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
559 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
563 storedState.hash = object.hash;
564 storedState.filePath = nil;
565 [self saveLocalState];
568 NSLog(@"Sync::local zero length object exists: %@", filePath);
572 [activityFacility startAndEndActivityWithType:PithosActivityOther
573 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
574 } else if (storedState.filePath == nil) {
575 // Create new local object
576 // Check first if a local copy exists
577 if ([self findLocalCopyForObjectWithHash:object.hash forFile:filePath]) {
578 [activityFacility startAndEndActivityWithType:PithosActivityOther
579 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
581 [self increaseSyncOperationCount];
582 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
585 blockSize:blockSize];
586 objectRequest.delegate = self;
587 objectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
588 objectRequest.didFailSelector = @selector(requestFailed:);
589 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
590 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name]
591 totalBytes:object.bytes
593 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
594 object, @"pithosObject",
595 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(blockSize + 0.0)))], @"missingBlocks",
596 [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
597 filePath, @"filePath",
598 activity, @"activity",
599 [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage",
600 [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage",
601 [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage",
602 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
603 [NSNumber numberWithUnsignedInteger:10], @"retries",
605 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
606 [activityFacility updateActivity:activity
607 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
608 [[objectRequest.userInfo valueForKey:@"pithosObject"] name],
609 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
610 totalBytes:activity.totalBytes
611 currentBytes:(activity.currentBytes + size)];
613 [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
616 // Resume local object download
617 // Check first if a local copy exists
618 if ([self findLocalCopyForObjectWithHash:object.hash forFile:filePath]) {
619 [activityFacility startAndEndActivityWithType:PithosActivityOther
620 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
621 // Delete incomplete temp download
623 if (![fileManager removeItemAtPath:storedState.filePath error:&error] || error) {
624 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
625 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", storedState.filePath]
629 [self increaseSyncOperationCount];
630 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithContainerName:containerName
631 objectName:object.name];
632 objectRequest.delegate = self;
633 objectRequest.didFinishSelector = @selector(downloadObjectHashMapFinished:);
634 // The fail method for block download does exactly what we want
635 objectRequest.didFailSelector = @selector(requestFailed:);
636 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
637 message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name]
638 totalBytes:object.bytes
640 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
641 object, @"pithosObject",
642 filePath, @"filePath",
643 activity, @"activity",
644 [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage",
645 [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage",
646 [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage",
647 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
648 [NSNumber numberWithUnsignedInteger:10], @"retries",
650 [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
655 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
656 object:(ASIPithosObject *)object
657 localFilePath:(NSString *)filePath {
658 [self increaseSyncOperationCount];
659 if (currentState.isDirectory) {
660 // Create remote directory object
661 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName
662 objectName:object.name
664 contentType:@"application/directory"
666 contentDisposition:nil
669 isPublic:ASIPithosObjectRequestPublicIgnore
672 objectRequest.delegate = self;
673 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
674 objectRequest.didFailSelector = @selector(requestFailed:);
675 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
676 message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]];
677 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
678 object, @"pithosObject",
679 activity, @"activity",
680 [NSString stringWithFormat:@"Sync: Creating directory '%@' (stopped)", object.name], @"stoppedActivityMessage",
681 [NSString stringWithFormat:@"Sync: Creating directory '%@' (failed)", object.name], @"failedActivityMessage",
682 [NSString stringWithFormat:@"Sync: Creating directory '%@' (finished)", object.name], @"finishedActivityMessage",
683 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
684 [NSNumber numberWithUnsignedInteger:10], @"retries",
686 [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
687 } else if (!currentState.exists) {
688 // Delete remote object
689 NSString *safeObjectName = [PithosUtilities safeObjectNameForContainerName:@"trash"
690 objectName:object.name];
691 if (safeObjectName) {
692 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:containerName
693 objectName:object.name
694 destinationContainerName:@"trash"
695 destinationObjectName:safeObjectName
698 objectRequest.delegate = self;
699 objectRequest.didFinishSelector = @selector(moveObjectToTrashFinished:);
700 objectRequest.didFailSelector = @selector(requestFailed:);
701 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
702 message:[NSString stringWithFormat:@"Sync: Moving to trash '%@'", object.name]];
703 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
704 object, @"pithosObject",
705 activity, @"activity",
706 [NSString stringWithFormat:@"Sync: Moving to trash '%@' (stopped)", object.name], @"stoppedActivityMessage",
707 [NSString stringWithFormat:@"Sync: Moving to trash '%@' (failed)", object.name], @"failedActivityMessage",
708 [NSString stringWithFormat:@"Sync: Moving to trash '%@' (finished)", object.name], @"finishedActivityMessage",
709 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
710 [NSNumber numberWithUnsignedInteger:10], @"retries",
712 [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
714 syncIncomplete = YES;
715 [self decreaseSyncOperationCount];
718 syncIncomplete = YES;
719 [self decreaseSyncOperationCount];
722 // Upload file to remote object
723 NSError *error = nil;
724 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
725 if (object.contentType == nil)
726 object.contentType = @"application/octet-stream";
728 NSLog(@"contentType detection error: %@", error);
729 NSArray *hashes = nil;
730 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName
731 objectName:object.name
732 contentType:object.contentType
740 objectRequest.delegate = self;
741 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
742 objectRequest.didFailSelector = @selector(requestFailed:);
743 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
744 message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
745 totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
747 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
748 [NSDictionary dictionaryWithObjectsAndKeys:
749 object, @"pithosObject",
750 filePath, @"filePath",
752 [NSNumber numberWithUnsignedInteger:10], @"iteration",
753 activity, @"activity",
754 [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage",
755 [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage",
756 [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage",
757 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
758 [NSNumber numberWithUnsignedInteger:10], @"retries",
760 [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
762 syncIncomplete = YES;
763 [self decreaseSyncOperationCount];
770 #pragma mark ASIHTTPRequestDelegate
772 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
773 NSLog(@"Sync::list request finished: %@", containerRequest.url);
774 if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
775 if (containerRequest.responseStatusCode == 200) {
776 NSArray *someObjects = [containerRequest objects];
777 if (objects == nil) {
778 objects = [[NSMutableArray alloc] initWithArray:someObjects];
780 [objects addObjectsFromArray:someObjects];
782 if ([someObjects count] < 10000) {
783 self.blockHash = [containerRequest blockHash];
784 self.blockSize = [containerRequest blockSize];
785 self.lastModified = [containerRequest lastModified];
786 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
787 for (ASIPithosObject *object in objects) {
788 [remoteObjects setObject:object forKey:object.name];
793 // Do an additional request to fetch more objects
794 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName
796 marker:[[someObjects lastObject] name]
803 ifModifiedSince:lastModified];
804 newContainerRequest.delegate = self;
805 newContainerRequest.didFinishSelector = @selector(listRequestFinished:);
806 newContainerRequest.didFailSelector = @selector(listRequestFailed:);
807 newContainerRequest.userInfo = newContainerRequest.userInfo;
808 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
809 [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
813 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
814 withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
815 NSFileManager *fileManager = [NSFileManager defaultManager];
816 NSError *error = nil;
817 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
819 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
820 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
822 [activityFacility startAndEndActivityWithType:PithosActivityOther message:@"Sync: Failed to read contents of sync directory"];
823 @synchronized(self) {
824 // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccesfully
825 syncOperationCount = 0;
826 if (newSyncRequested)
831 self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:[subPaths count]];
832 for (NSString *objectName in subPaths) {
833 if (![storedLocalObjectStates objectForKey:objectName]) {
834 [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
836 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
837 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
842 [self saveLocalState];
844 for (NSString *objectName in remoteObjects) {
845 if (![storedLocalObjectStates objectForKey:objectName])
846 [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
849 for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
850 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
851 if ([objectName hasSuffix:@"/"])
852 filePath = [filePath stringByAppendingString:@":"];
853 ASIPithosObject *object = [ASIPithosObject object];
854 object.name = objectName;
855 NSLog(@"Sync::object name: %@", objectName);
857 PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
858 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
859 if (!currentLocalObjectState)
860 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
862 blockSize:blockSize];
863 if (currentLocalObjectState.isDirectory)
864 object.contentType = @"application/directory";
866 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
867 ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
869 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
870 remoteObjectState.isDirectory = YES;
871 object.contentType = @"application/directory";
873 remoteObjectState.hash = remoteObject.hash;
877 BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
878 BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
879 NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
880 if (!localStateHasChanged) {
881 // Local state hasn't changed
882 if (serverStateHasChanged) {
883 // Server state has changed
884 // Update local state to match that of the server
885 object.bytes = remoteObject.bytes;
886 object.version = remoteObject.version;
887 object.contentType = remoteObject.contentType;
888 object.hash = remoteObject.hash;
889 [self updateLocalStateWithObject:object localFilePath:filePath];
890 } else if (!remoteObject && !currentLocalObjectState.exists) {
891 // Server state hasn't changed
892 // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
893 [storedLocalObjectStates removeObjectForKey:objectName];
894 [self saveLocalState];
897 // Local state has changed
898 if (!serverStateHasChanged) {
899 // Server state hasn't changed
900 [self updateServerStateWithCurrentState:currentLocalObjectState
902 localFilePath:filePath];
904 // Server state has also changed
905 if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
906 // Both did the same change (directory)
907 storedLocalObjectState.isDirectory = YES;
908 [self saveLocalState];
909 } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
910 // Both did the same change (object edit or delete)
911 if (!remoteObjectState.exists)
912 [storedLocalObjectStates removeObjectForKey:object.name];
914 storedLocalObjectState.hash = remoteObjectState.hash;
915 [self saveLocalState];
917 // Conflict, we ask the user which change to keep
918 NSString *informativeText;
919 NSString *firstButtonText;
920 NSString *secondButtonText;
922 if (!remoteObjectState.exists) {
923 // Remote object has been deleted
924 informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
925 firstButtonText = @"Delete local file";
926 secondButtonText = @"Upload file to server";
927 } else if (!currentLocalObjectState.exists) {
928 informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
929 firstButtonText = @"Download file from server";
930 secondButtonText = @"Delete file on server";
932 informativeText = [NSString stringWithFormat:@"'%@' has been modifed both locally and on the server.", object.name];
933 firstButtonText = @"Keep server version";
934 secondButtonText = @"Keep local version";
936 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
937 [alert setMessageText:@"Conflict"];
938 [alert setInformativeText:informativeText];
939 [alert addButtonWithTitle:firstButtonText];
940 [alert addButtonWithTitle:secondButtonText];
941 [alert addButtonWithTitle:@"Do nothing"];
942 NSInteger choice = [alert runModal];
943 if (choice == NSAlertFirstButtonReturn) {
944 object.bytes = remoteObject.bytes;
945 object.version = remoteObject.version;
946 object.contentType = remoteObject.contentType;
947 object.hash = remoteObject.hash;
948 [self updateLocalStateWithObject:object localFilePath:filePath];
949 } if (choice == NSAlertSecondButtonReturn) {
950 [self updateServerStateWithCurrentState:currentLocalObjectState
952 localFilePath:filePath];
958 @synchronized(self) {
959 [self decreaseSyncOperationCount];
960 if (newSyncRequested && !syncOperationCount)
964 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
966 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
967 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
968 [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
970 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
971 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
972 @synchronized(self) {
973 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
974 syncOperationCount = 0;
975 if (newSyncRequested)
982 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
983 if ([containerRequest isCancelled]) {
984 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
985 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
988 @synchronized(self) {
989 syncOperationCount = 0;
993 // If the server listing fails, the sync should start over, so just retrying is enough
994 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
996 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
997 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
998 [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1000 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1001 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1004 @synchronized(self) {
1005 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1006 syncOperationCount = 0;
1007 if (newSyncRequested)
1013 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1014 NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1015 if (objectRequest.responseStatusCode == 206) {
1016 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1017 NSFileManager *fileManager = [NSFileManager defaultManager];
1019 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1021 NSString *downloadsDirPath = self.tempDownloadsDirPath;
1022 if (!downloadsDirPath) {
1023 [activityFacility endActivity:activity
1024 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1025 @synchronized(self) {
1026 syncIncomplete = YES;
1027 [self decreaseSyncOperationCount];
1028 if (newSyncRequested && !syncOperationCount)
1034 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1035 if ((storedState.filePath == nil) || ![fileManager fileExistsAtPath:storedState.filePath]) {
1036 NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1037 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1038 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1039 strcpy(tempFileNameCString, tempFileTemplateCString);
1040 int fileDescriptor = mkstemp(tempFileNameCString);
1041 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1042 free(tempFileNameCString);
1043 if (fileDescriptor == -1) {
1044 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
1045 message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", storedState.filePath]
1047 [activityFacility endActivity:activity
1048 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1049 @synchronized(self) {
1050 syncIncomplete = YES;
1051 [self decreaseSyncOperationCount];
1052 if (newSyncRequested && !syncOperationCount)
1057 close(fileDescriptor);
1058 storedState.filePath = tempFilePath;
1059 [self saveLocalState];
1063 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1064 NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.filePath];
1065 [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
1066 [tempFileHandle writeData:[objectRequest responseData]];
1067 [tempFileHandle closeFile];
1069 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1070 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1071 if (missingBlockIndex == NSNotFound) {
1072 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1073 NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1074 if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath]) {
1075 [activityFacility endActivity:activity
1076 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1077 @synchronized(self) {
1078 syncIncomplete = YES;
1079 [self decreaseSyncOperationCount];
1080 if (newSyncRequested && !syncOperationCount)
1084 } else if (![fileManager fileExistsAtPath:dirPath]) {
1085 // File doesn't exist but also the containing directory doesn't exist
1086 // In most cases this should have been resolved as an update of the corresponding local object,
1087 // but it never hurts to check
1089 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1091 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1092 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
1094 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1095 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1096 @synchronized(self) {
1097 syncIncomplete = YES;
1098 [self decreaseSyncOperationCount];
1099 if (newSyncRequested && !syncOperationCount)
1105 // Move file from tmp download
1107 [fileManager moveItemAtPath:storedState.filePath toPath:filePath error:&error];
1109 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
1110 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.filePath, filePath]
1112 [activityFacility endActivity:activity
1113 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1114 @synchronized(self) {
1115 syncIncomplete = YES;
1116 [self decreaseSyncOperationCount];
1117 if (newSyncRequested && !syncOperationCount)
1122 [activityFacility endActivity:activity
1123 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1124 totalBytes:activity.totalBytes
1125 currentBytes:activity.totalBytes];
1127 storedState.hash = object.hash;
1128 storedState.filePath = nil;
1129 [self saveLocalState];
1131 @synchronized(self) {
1132 [self decreaseSyncOperationCount];
1133 if (newSyncRequested && !syncOperationCount)
1138 if (newSyncRequested) {
1139 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1140 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1141 @synchronized(self) {
1142 syncIncomplete = YES;
1143 [self decreaseSyncOperationCount];
1144 if (!syncOperationCount)
1149 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
1151 blockIndex:missingBlockIndex
1152 blockSize:blockSize];
1153 newObjectRequest.delegate = self;
1154 newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
1155 newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
1156 newObjectRequest.userInfo = objectRequest.userInfo;
1157 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1158 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1159 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1160 [activityFacility updateActivity:activity
1161 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1163 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1164 totalBytes:activity.totalBytes
1165 currentBytes:(activity.currentBytes + size)];
1167 [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1170 } else if (objectRequest.responseStatusCode == 412) {
1171 // The object has changed on the server
1172 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1173 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1174 @synchronized(self) {
1175 syncIncomplete = YES;
1176 [self decreaseSyncOperationCount];
1177 if (newSyncRequested && !syncOperationCount)
1181 [self requestFailed:objectRequest];
1185 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1186 NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1187 if (objectRequest.responseStatusCode == 200) {
1188 if (newSyncRequested) {
1189 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1190 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1191 @synchronized(self) {
1192 syncIncomplete = YES;
1193 [self decreaseSyncOperationCount];
1194 if (!syncOperationCount)
1199 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1200 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1201 if ([PithosUtilities bytesOfFile:storedState.filePath] > object.bytes)
1202 [[NSFileHandle fileHandleForWritingAtPath:storedState.filePath] truncateFileAtOffset:object.bytes];
1203 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1204 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.filePath
1207 withHashes:[objectRequest hashes]];
1208 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1209 [activityFacility endActivity:activity
1210 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1212 (100*(activity.totalBytes - [missingBlocks count]*blockSize + 0.0)/(activity.totalBytes + 0.0))]
1213 totalBytes:activity.totalBytes
1214 currentBytes:(activity.totalBytes - [missingBlocks count]*blockSize)];
1216 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
1218 blockIndex:missingBlockIndex
1219 blockSize:blockSize];
1220 newObjectRequest.delegate = self;
1221 newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
1222 newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
1223 newObjectRequest.userInfo = objectRequest.userInfo;
1224 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1225 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1226 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1227 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1228 [activityFacility updateActivity:activity
1229 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
1231 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1232 totalBytes:activity.totalBytes
1233 currentBytes:(activity.currentBytes + size)];
1235 [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1238 [self requestFailed:objectRequest];
1242 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1243 NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1244 if (objectRequest.responseStatusCode == 201) {
1245 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1246 storedState.isDirectory = YES;
1247 [self saveLocalState];
1248 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1249 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1250 @synchronized(self) {
1251 [self decreaseSyncOperationCount];
1252 if (newSyncRequested && !syncOperationCount)
1256 [self requestFailed:objectRequest];
1260 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1261 NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1262 if (objectRequest.responseStatusCode == 201) {
1263 [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1264 [self saveLocalState];
1265 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1266 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1267 @synchronized(self) {
1268 [self decreaseSyncOperationCount];
1269 if (newSyncRequested && !syncOperationCount)
1273 [self requestFailed:objectRequest];
1277 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1278 NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1279 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1280 PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1281 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1282 NSUInteger totalBytes = activity.totalBytes;
1283 NSUInteger currentBytes = activity.currentBytes;
1284 if (objectRequest.responseStatusCode == 201) {
1285 NSLog(@"Sync::object created: %@", objectRequest.url);
1286 storedState.hash = [objectRequest eTag];
1287 [self saveLocalState];
1288 [activityFacility endActivity:activity
1289 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1290 totalBytes:totalBytes
1291 currentBytes:totalBytes];
1292 @synchronized(self) {
1293 [self decreaseSyncOperationCount];
1294 if (newSyncRequested && !syncOperationCount)
1297 } else if (objectRequest.responseStatusCode == 409) {
1298 if (newSyncRequested) {
1299 [activityFacility endActivity:activity
1300 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1301 @synchronized(self) {
1302 syncIncomplete = YES;
1303 [self decreaseSyncOperationCount];
1304 if (!syncOperationCount)
1309 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1310 if (iteration == 0) {
1311 NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
1312 [activityFacility endActivity:activity
1313 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1314 syncIncomplete = YES;
1315 [self decreaseSyncOperationCount];
1318 NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
1319 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1320 withMissingHashesResponse:[objectRequest responseString]];
1321 if (totalBytes >= [missingBlocks count]*blockSize)
1322 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1323 [activityFacility updateActivity:activity
1324 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
1325 totalBytes:totalBytes
1326 currentBytes:currentBytes];
1327 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1328 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
1330 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1331 missingBlockIndex:missingBlockIndex
1332 sharingAccount:nil];
1333 newContainerRequest.delegate = self;
1334 newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1335 newContainerRequest.didFailSelector = @selector(requestFailed:);
1336 newContainerRequest.userInfo = objectRequest.userInfo;
1337 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1338 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1339 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1340 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1341 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1342 [activityFacility updateActivity:activity
1343 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1344 totalBytes:activity.totalBytes
1345 currentBytes:(activity.currentBytes + size)];
1347 [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1350 [self requestFailed:objectRequest];
1354 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1355 NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
1356 if (containerRequest.responseStatusCode == 202) {
1357 ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
1358 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1359 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1360 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1361 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1362 if (missingBlockIndex == NSNotFound) {
1363 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1364 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName
1365 objectName:object.name
1366 contentType:object.contentType
1369 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1372 sharingAccount:nil];
1373 newObjectRequest.delegate = self;
1374 newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
1375 newObjectRequest.didFailSelector = @selector(requestFailed:);
1376 newObjectRequest.userInfo = containerRequest.userInfo;
1377 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1378 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1379 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1380 [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1382 if (newSyncRequested) {
1383 [activityFacility endActivity:activity
1384 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1385 @synchronized(self) {
1386 syncIncomplete = YES;
1387 [self decreaseSyncOperationCount];
1388 if (!syncOperationCount)
1393 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
1395 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1396 missingBlockIndex:missingBlockIndex
1397 sharingAccount:nil];
1398 newContainerRequest.delegate = self;
1399 newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1400 newContainerRequest.didFailSelector = @selector(requestFailed:);
1401 newContainerRequest.userInfo = containerRequest.userInfo;
1402 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1403 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1404 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1405 [activityFacility updateActivity:activity
1406 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1407 totalBytes:activity.totalBytes
1408 currentBytes:(activity.currentBytes + size)];
1410 [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1414 [self requestFailed:containerRequest];
1418 - (void)requestFailed:(ASIPithosRequest *)request {
1419 if ([request isCancelled]) {
1420 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1421 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1422 syncIncomplete = YES;
1423 [self decreaseSyncOperationCount];
1426 if (newSyncRequested) {
1427 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1428 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1429 @synchronized(self) {
1430 syncIncomplete = YES;
1431 [self decreaseSyncOperationCount];
1432 if (!syncOperationCount)
1437 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1439 ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
1440 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1441 [queue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1443 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1444 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1445 syncIncomplete = YES;
1446 [self decreaseSyncOperationCount];