5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
15 // 2. Redistributions in binary form must reproduce the above
16 // copyright notice, this list of conditions and the following
17 // disclaimer in the documentation and/or other materials
18 // provided with the distribution.
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
38 #import "PithosSyncDaemon.h"
39 #import "PithosAccount.h"
40 #import "PithosLocalObjectState.h"
41 #import "PithosActivityFacility.h"
42 #import "PithosUtilities.h"
43 #import "ASINetworkQueue.h"
44 #import "ASIPithosRequest.h"
46 #import "ASIPithosContainer.h"
47 #import "ASIPithosContainerRequest.h"
48 #import "ASIPithosObjectRequest.h"
49 #import "ASIPithosObject.h"
51 @interface PithosSyncDaemon (Private)
52 - (void)loadLocalState;
53 - (void)resetLocalStateWithAll:(BOOL)all;
54 - (void)saveLocalState;
56 - (BOOL)createSyncDirectory:(NSString *)dirPath;
57 - (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName;
58 - (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName;
60 - (BOOL)moveToTempTrashFile:(NSString *)filePath
61 accountName:(NSString *)accountName
62 pithosContainer:(ASIPithosContainer *)pithosContainer;
63 - (void)emptyTempTrash;
64 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
66 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
67 localFilePath:(NSString *)filePath
68 accountName:(NSString *)accountName
69 pithosContainer:(ASIPithosContainer *)pithosContainer;
70 - (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
71 object:(ASIPithosObject *)object
72 localFilePath:(NSString *)filePath
73 accountName:(NSString *)accountName
74 pithosContainer:(ASIPithosContainer *)pithosContainer;
75 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest;
76 - (void)requestFailed:(ASIPithosRequest *)request;
78 - (void)syncOperationStarted;
79 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull;
83 @implementation PithosSyncDaemon
84 @synthesize directoryPath, accountsDictionary, skipHidden, pithos;
85 @synthesize accountsNames, accountsPithosContainers;
86 @synthesize lastCompletedSync, remoteObjects, previousRemoteObjects, storedLocalObjectStates, currentLocalObjectStates;
87 @synthesize pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
90 #pragma Object Lifecycle
92 - (id)initWithDirectoryPath:(NSString *)aDirectoryPath
93 pithosAccount:(PithosAccount *)aPithosAccount
94 accountsDictionary:(NSDictionary *)anAccountsDictionary
95 skipHidden:(BOOL)aSkipHidden
96 resetLocalState:(BOOL)resetLocalState {
97 if ((self = [super init])) {
98 directoryPath = [aDirectoryPath copy];
99 pithosAccount = aPithosAccount;
100 self.accountsDictionary = anAccountsDictionary;
101 skipHidden = aSkipHidden;
102 self.pithos = pithosAccount.pithos;
104 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
107 [self resetLocalStateWithAll:YES];
109 [self resetLocalStateWithAll:NO];
111 networkQueue = [[ASINetworkQueue alloc] init];
112 networkQueue.showAccurateProgress = YES;
113 networkQueue.shouldCancelAllRequestsOnFailure = NO;
114 // networkQueue.maxConcurrentOperationCount = 1;
116 callbackQueue = [[NSOperationQueue alloc] init];
117 [callbackQueue setSuspended:YES];
118 callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
119 // callbackQueue.maxConcurrentOperationCount = 1;
121 [[NSNotificationCenter defaultCenter] addObserver:self
122 selector:@selector(applicationWillTerminate:)
123 name:NSApplicationWillTerminateNotification
124 object:[NSApplication sharedApplication]];
129 - (void)loadLocalState {
131 if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath])
132 self.storedLocalObjectStates = [NSKeyedUnarchiver unarchiveObjectWithFile:self.pithosStateFilePath];
134 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
135 if (!storedLocalObjectStates)
136 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
137 for (NSString *accountName in accountsNames) {
138 NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
139 if (!accountStoredLocalObjectStates) {
140 accountStoredLocalObjectStates = [NSMutableDictionary dictionary];
141 [storedLocalObjectStates setObject:accountStoredLocalObjectStates forKey:accountName];
143 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
144 if (![accountStoredLocalObjectStates objectForKey:pithosContainer.name])
145 [accountStoredLocalObjectStates setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
151 - (void)resetLocalStateWithAll:(BOOL)all {
153 self.lastCompletedSync = nil;
155 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
156 [self saveLocalState]; // Save an empty dictionary
157 [self loadLocalState]; // Load to populate with containers
158 [self saveLocalState]; // Save again
159 if (self.tempDownloadsDirPath)
160 [PithosUtilities removeContentsAtPath:self.tempDownloadsDirPath];
162 // Remove containers that don't interest us anymore and save
163 if (!storedLocalObjectStates)
164 [self loadLocalState];
165 for (NSString *accountName in storedLocalObjectStates) {
166 NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
167 NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
168 if (!containersDictionary) {
169 if (self.tempDownloadsDirPath) {
170 if ([accountName isEqualToString:@""]) {
171 for (NSString *containerName in accountStoredLocalObjectStates) {
172 [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]];
175 [PithosUtilities removeContentsAtPath:[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared with me"]
176 stringByAppendingPathComponent:accountName]];
179 [storedLocalObjectStates removeObjectForKey:accountName];
181 // Check the account's containers
182 for (NSString *containerName in accountStoredLocalObjectStates) {
183 if (![containersDictionary objectForKey:containerName]) {
184 if ([accountName isEqualToString:@""])
185 [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]];
187 [PithosUtilities removeContentsAtPath:[[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared with me"]
188 stringByAppendingPathComponent:accountName]
189 stringByAppendingPathComponent:containerName]];
190 [accountStoredLocalObjectStates removeObjectForKey:containerName];
195 [self saveLocalState];
200 - (void)saveLocalState {
202 [NSKeyedArchiver archiveRootObject:storedLocalObjectStates toFile:self.pithosStateFilePath];
206 - (void)resetDaemon {
207 @synchronized(self) {
212 [networkQueue reset];
213 [callbackQueue cancelAllOperations];
214 [callbackQueue setSuspended:YES];
215 [self emptyTempTrash];
217 syncOperationCount = 0;
219 @synchronized(self) {
224 - (void)startDaemon {
225 @synchronized(self) {
230 // In the improbable case of leftover operations
231 [networkQueue reset];
232 [callbackQueue cancelAllOperations];
234 syncOperationCount = 0;
235 newSyncRequested = NO;
239 [self loadLocalState];
242 [callbackQueue setSuspended:NO];
244 @synchronized(self) {
250 [[NSNotificationCenter defaultCenter] removeObserver:self];
255 #pragma mark Observers
257 - (void)applicationWillTerminate:(NSNotification *)notification {
258 [self saveLocalState];
262 #pragma mark Properties
264 - (NSString *)pithosStateFilePath {
265 if (!pithosStateFilePath) {
266 pithosStateFilePath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
267 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
268 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive",
269 pithosAccount.uniqueName]];
270 NSString *pithosStateFileDirPath = [pithosStateFilePath stringByDeletingLastPathComponent];
271 NSFileManager *fileManager = [NSFileManager defaultManager];
273 BOOL fileExists = [fileManager fileExistsAtPath:pithosStateFileDirPath isDirectory:&isDirectory];
274 NSError *error = nil;
275 if (fileExists && !isDirectory)
276 [fileManager removeItemAtPath:pithosStateFileDirPath error:&error];
277 if (!error && !fileExists)
278 [fileManager createDirectoryAtPath:pithosStateFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
280 // pithosStateFilePath = nil;
281 // XXX create a dir using mktmps?
283 return [pithosStateFilePath copy];
286 - (NSString *)tempDownloadsDirPath {
287 if (!tempDownloadsDirPath) {
288 tempDownloadsDirPath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
289 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
290 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads",
291 pithosAccount.uniqueName]];
292 NSFileManager *fileManager = [NSFileManager defaultManager];
294 BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
295 NSError *error = nil;
296 if (fileExists && !isDirectory)
297 [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
298 if (!error && !fileExists)
299 [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
301 // tempDownloadsDirPath = nil;
302 // XXX create a dir using mktmps?
304 return tempDownloadsDirPath;
307 - (NSString *)tempTrashDirPath {
308 if (!tempTrashDirPath) {
309 tempTrashDirPath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
310 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
311 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash",
312 pithosAccount.uniqueName]];
313 NSFileManager *fileManager = [NSFileManager defaultManager];
315 BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
316 NSError *error = nil;
317 if (fileExists && !isDirectory)
318 [fileManager removeItemAtPath:tempTrashDirPath error:&error];
319 if (!error && !fileExists)
320 [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
322 // tempTrashDirPath = nil;
323 // XXX create a dir using mktmps?
325 return tempTrashDirPath;
328 - (void)setDirectoryPath:(NSString *)aDirectoryPath {
329 if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
331 [self resetLocalStateWithAll:YES];
332 directoryPath = [aDirectoryPath copy];
336 - (void)setAccountsDictionary:(NSDictionary *)anAccountsDictionary {
337 if (anAccountsDictionary && ![anAccountsDictionary isEqualToDictionary:accountsDictionary]) {
338 BOOL reset = (accountsDictionary != nil);
342 accountsDictionary = [anAccountsDictionary copy];
344 accountsCount = [accountsDictionary count];
345 self.accountsNames = [NSMutableArray arrayWithCapacity:accountsCount];
346 self.accountsPithosContainers = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
347 NSDictionary *containersDictionary = [accountsDictionary objectForKey:@""];
348 if (containersDictionary && [containersDictionary count]) {
349 NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
350 for (NSString *containerName in containersDictionary) {
351 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
352 pithosContainer.name = containerName;
353 [pithosContainers addObject:pithosContainer];
355 [accountsNames addObject:@""];
356 [accountsPithosContainers setObject:pithosContainers forKey:@""];
358 for (NSString *accountName in accountsDictionary) {
359 NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
360 if (![accountsNames containsObject:accountName] && [containersDictionary count]) {
361 NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
362 for (NSString *containerName in containersDictionary) {
363 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
364 pithosContainer.name = containerName;
365 [pithosContainers addObject:pithosContainer];
367 [accountsNames addObject:accountName];
368 [accountsPithosContainers setObject:pithosContainers forKey:accountName];
373 [self resetLocalStateWithAll:NO];
377 - (void)setPithos:(ASIPithos *)aPithos {
379 pithos = [ASIPithos pithos];
380 pithos.authUser = [aPithos.authUser copy];
381 pithos.authToken = [aPithos.authToken copy];
382 pithos.storageURLPrefix = [aPithos.storageURLPrefix copy];
383 pithos.authURL = [aPithos.authURL copy];
384 pithos.publicURLPrefix = [aPithos.publicURLPrefix copy];
385 pithos.userCatalogURL = [aPithos.userCatalogURL copy];
388 (![aPithos.authUser isEqualToString:pithos.authUser] ||
389 ![aPithos.authToken isEqualToString:pithos.authToken] ||
390 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
392 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
393 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
394 [self resetLocalStateWithAll:YES];
395 pithos.authUser = [aPithos.authUser copy];
396 pithos.authToken = [aPithos.authToken copy];
397 pithos.storageURLPrefix = [aPithos.storageURLPrefix copy];
398 pithos.authURL = [aPithos.authURL copy];
399 pithos.publicURLPrefix = [aPithos.publicURLPrefix copy];
400 pithos.userCatalogURL = [aPithos.userCatalogURL copy];
405 #pragma mark Helper Methods
407 - (BOOL)createSyncDirectory:(NSString *)dirPath {
408 NSFileManager *fileManager = [NSFileManager defaultManager];
410 NSError *error = nil;
411 if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory]) {
412 if (![fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
413 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
414 message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'",
419 } else if (!isDirectory) {
420 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
421 message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'",
429 - (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
430 if ([accountName isEqualToString:@""])
431 return [directoryPath stringByAppendingPathComponent:containerName];
433 return [[[directoryPath stringByAppendingPathComponent:@"shared with me"]
434 stringByAppendingPathComponent:accountName]
435 stringByAppendingPathComponent:containerName];
438 - (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
439 if ([accountName isEqualToString:@""])
440 return containerName;
442 return [[@"shared with me" stringByAppendingPathComponent:accountName] stringByAppendingPathComponent:containerName];
448 - (void)syncOperationStarted {
449 @synchronized(self) {
450 syncOperationCount++;
454 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
455 @synchronized(self) {
456 if (!operationSuccessfull)
457 syncIncomplete = YES;
458 if (syncOperationCount == 0) {
459 // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
460 DLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
463 syncOperationCount--;
464 if (syncOperationCount == 0) {
465 if (!syncIncomplete) {
466 self.lastCompletedSync = [NSDate date];
467 dispatch_async(dispatch_get_main_queue(), ^{
468 [activityFacility startAndEndActivityWithType:PithosActivityOther
469 message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]
470 pithosAccount:pithosAccount];
474 [self emptyTempTrash];
475 if (newSyncRequested && daemonActive)
482 @synchronized(self) {
483 return ((syncOperationCount > 0) && daemonActive);
488 @synchronized(self) {
489 if ([self isSyncing])
495 @synchronized(self) {
496 if ([self isSyncing]) {
497 // If at least one operation is running return
498 newSyncRequested = YES;
500 } else if (daemonActive && accountsCount) {
501 // The first operation is the server listing
502 [self syncOperationStarted];
503 newSyncRequested = NO;
511 if (![self createSyncDirectory:directoryPath]) {
512 [self syncOperationFinishedWithSuccess:NO];
515 for (NSString *accountName in accountsNames) {
516 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
517 if (![self createSyncDirectory:[self dirPathForAccount:accountName container:pithosContainer.name]]) {
518 [self syncOperationFinishedWithSuccess:NO];
524 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
525 for (NSString *accountName in accountsNames) {
526 [remoteObjects setObject:[NSMutableDictionary dictionary] forKey:accountName];
530 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
531 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
532 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
533 containerName:pithosContainer.name
542 ifModifiedSince:pithosContainer.lastModified];
543 if (![accountName isEqualToString:@""])
544 [containerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
545 containerRequest.delegate = self;
546 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
547 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
548 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther
549 message:@"Sync: Getting server listing"
550 pithosAccount:pithosAccount];
551 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
552 activity, @"activity",
553 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage",
554 @"Sync: Getting server listing (failed)", @"failedActivityMessage",
555 @"Sync: Getting server listing (finished)", @"finishedActivityMessage",
556 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
557 [NSNumber numberWithUnsignedInteger:10], @"retries",
558 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector",
559 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector",
561 [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
564 - (void)emptyTempTrash {
566 NSString *trashDirPath = self.tempTrashDirPath;
568 NSFileManager *fileManager = [NSFileManager defaultManager];
569 NSError *error = nil;
570 // NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
571 NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
573 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
574 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath]
578 if ([subPaths count]) {
579 // NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
580 // for (NSString *subPath in subPaths) {
581 // [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
583 // [self syncOperationStarted];
584 // [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
586 // [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
587 // message:@"Cannot move files to Trash"
590 // [self syncOperationFinishedWithSuccess:YES];
592 for (NSString *subPath in subPaths) {
593 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
595 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
596 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
597 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
605 - (BOOL)moveToTempTrashFile:(NSString *)filePath
606 accountName:(NSString *)accountName
607 pithosContainer:(ASIPithosContainer *)pithosContainer {
609 if (!self.tempTrashDirPath)
611 NSFileManager *fileManager = [NSFileManager defaultManager];
613 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
614 NSError *error = nil;
615 NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:directoryPath withString:self.tempTrashDirPath];
616 NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
617 if (fileExists && isDirectory) {
618 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
620 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
621 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath]
625 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
626 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
627 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
631 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
632 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
633 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
634 filePath, newFilePath]
638 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
640 currentState.filePath = newFilePath;
641 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
642 [currentLocalObjectStates removeObjectForKey:filePath];
644 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
645 blockHash:pithosContainer.blockHash
646 blockSize:pithosContainer.blockSize]
649 for (NSString *subPath in subPaths) {
650 NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
651 NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:directoryPath
652 withString:self.tempTrashDirPath];
653 currentState = [currentLocalObjectStates objectForKey:subFilePath];
655 currentState.filePath = newSubFilePath;
656 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
657 [currentLocalObjectStates removeObjectForKey:subFilePath];
659 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
660 blockHash:pithosContainer.blockHash
661 blockSize:pithosContainer.blockSize]
662 forKey:newSubFilePath];
665 } else if (fileExists && !isDirectory) {
666 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
667 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
668 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
672 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
673 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
674 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
675 filePath, newFilePath]
679 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
681 currentState.filePath = newFilePath;
682 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
683 [currentLocalObjectStates removeObjectForKey:filePath];
685 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
686 blockHash:pithosContainer.blockHash
687 blockSize:pithosContainer.blockSize]
695 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
696 if ([hash length] != 64)
699 PithosLocalObjectState *localState;
700 NSFileManager *fileManager = [NSFileManager defaultManager];
702 NSError *error = nil;
703 for (NSString *localFilePath in currentLocalObjectStates) {
704 localState = [currentLocalObjectStates objectForKey:localFilePath];
705 if (!localState.isDirectory && [hash isEqualToString:localState.hash] &&
706 [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
707 if ([localFilePath hasPrefix:directoryPath]) {
708 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
709 [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
710 message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
711 localFilePath, filePath]
716 } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
717 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
718 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
719 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
720 localFilePath, filePath]
723 localState.filePath = filePath;
724 [currentLocalObjectStates setObject:localState forKey:filePath];
725 [currentLocalObjectStates removeObjectForKey:localFilePath];
735 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
736 localFilePath:(NSString *)filePath
737 accountName:(NSString *)accountName
738 pithosContainer:(ASIPithosContainer *)pithosContainer {
740 NSFileManager *fileManager = [NSFileManager defaultManager];
743 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
744 NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
745 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
746 objectForKey:pithosContainer.name];
747 PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
748 // Remote updated info
749 NSError *remoteError;
750 BOOL remoteIsDirectory;
751 BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos
752 containerName:pithosContainer.name
753 objectName:object.name
755 isDirectory:&remoteIsDirectory
756 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
757 if (!object || !object.objectHash) {
758 // Delete local object
759 if (![accountName isEqualToString:@""])
760 // If "shared with me" skip
762 if (remoteObjectExists) {
763 // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
764 syncIncomplete = YES;
766 DLog(@"Sync::delete local object: %@", filePath);
767 if (!fileExists || [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer]) {
768 dispatch_async(dispatch_get_main_queue(), ^{
769 [activityFacility startAndEndActivityWithType:PithosActivityOther
770 message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)",
771 pithosContainer.name, object.name]
772 pithosAccount:pithosAccount];
774 [containerStoredLocalObjectStates removeObjectForKey:object.name];
775 [self saveLocalState];
777 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
778 // Create local directory object
779 if (!remoteObjectExists || !remoteIsDirectory) {
780 // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
781 syncIncomplete = YES;
784 DLog(@"Sync::create local directory object: %@", filePath);
785 BOOL directoryCreated = NO;
786 if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
787 DLog(@"Sync::local directory object doesn't exist: %@", filePath);
789 if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
790 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
791 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
794 directoryCreated = YES;
795 storedState.filePath = filePath;
796 storedState.isDirectory = YES;
797 [self saveLocalState];
800 DLog(@"Sync::local directory object exists: %@", filePath);
801 directoryCreated = YES;
803 if (directoryCreated)
804 dispatch_async(dispatch_get_main_queue(), ^{
805 [activityFacility startAndEndActivityWithType:PithosActivityOther
806 message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)",
807 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
809 pithosAccount:pithosAccount];
811 } else if (object.bytes == 0) {
812 // Create local object with zero length
813 if (!remoteObjectExists || remoteIsDirectory) {
814 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
815 syncIncomplete = YES;
818 DLog(@"Sync::create local zero length object: %@", filePath);
819 BOOL fileCreated = NO;
821 ((isDirectory || [PithosUtilities bytesOfFile:filePath]) &&
822 [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
823 DLog(@"Sync::local zero length object doesn't exist: %@", filePath);
824 // Create directory of the file, if it doesn't exist
826 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
827 dispatch_async(dispatch_get_main_queue(), ^{
828 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
829 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
834 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
835 [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
836 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
840 storedState.filePath = filePath;
841 storedState.hash = object.objectHash;
842 storedState.tmpFilePath = nil;
843 [self saveLocalState];
846 DLog(@"Sync::local zero length object exists: %@", filePath);
850 dispatch_async(dispatch_get_main_queue(), ^{
851 [activityFacility startAndEndActivityWithType:PithosActivityOther
852 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
853 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
855 pithosAccount:pithosAccount];
857 } else if (storedState.tmpFilePath == nil) {
858 // Create new local object
859 if (!remoteObjectExists || remoteIsDirectory) {
860 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
861 syncIncomplete = YES;
864 // Create directory of the file, if it doesn't exist
866 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
867 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
868 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
871 // Check first if a local copy exists
872 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
873 storedState.filePath = filePath;
874 storedState.hash = object.objectHash;
875 [self saveLocalState];
876 dispatch_async(dispatch_get_main_queue(), ^{
877 [activityFacility startAndEndActivityWithType:PithosActivityOther
878 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
879 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
881 pithosAccount:pithosAccount];
884 [self syncOperationStarted];
885 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
886 containerName:pithosContainer.name
889 blockSize:pithosContainer.blockSize];
890 if (![accountName isEqualToString:@""])
891 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
892 objectRequest.delegate = self;
893 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
894 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
895 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
896 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
897 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
898 message:[messagePrefix stringByAppendingString:@" (0%%)"]
899 totalBytes:object.bytes
901 pithosAccount:pithosAccount];
902 dispatch_async(dispatch_get_main_queue(), ^{
903 [activityFacility updateActivity:activity withMessage:activity.message];
905 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
906 accountName, @"accountName",
907 pithosContainer, @"pithosContainer",
908 object, @"pithosObject",
909 messagePrefix, @"messagePrefix",
910 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks",
911 [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
912 filePath, @"filePath",
913 activity, @"activity",
914 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
915 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
916 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
917 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
918 [NSNumber numberWithUnsignedInteger:10], @"retries",
919 NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector",
920 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
922 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
923 [activityFacility updateActivity:activity
924 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
925 messagePrefix, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
926 totalBytes:activity.totalBytes
927 currentBytes:(activity.currentBytes + size)];
929 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
932 // Resume local object download
933 if (!remoteObjectExists || remoteIsDirectory) {
934 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
935 syncIncomplete = YES;
938 // Create directory of the file, if it doesn't exist
940 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
941 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
942 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
945 // Check first if a local copy exists
946 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
947 storedState.filePath = filePath;
948 storedState.hash = object.objectHash;
949 // Delete incomplete temp download
950 storedState.tmpFilePath = nil;
951 [self saveLocalState];
952 dispatch_async(dispatch_get_main_queue(), ^{
953 [activityFacility startAndEndActivityWithType:PithosActivityOther
954 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
955 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
957 pithosAccount:pithosAccount];
960 [self syncOperationStarted];
961 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
962 containerName:pithosContainer.name
963 objectName:object.name];
964 if (![accountName isEqualToString:@""])
965 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
966 objectRequest.delegate = self;
967 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
968 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
969 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
970 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
971 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
972 message:[messagePrefix stringByAppendingString:@" (0%%)"]
973 totalBytes:object.bytes
975 pithosAccount:pithosAccount];
976 dispatch_async(dispatch_get_main_queue(), ^{
977 [activityFacility updateActivity:activity withMessage:activity.message];
979 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
980 accountName, @"accountName",
981 pithosContainer, @"pithosContainer",
982 object, @"pithosObject",
983 messagePrefix, @"messagePrefix",
984 filePath, @"filePath",
985 activity, @"activity",
986 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
987 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
988 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
989 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
990 [NSNumber numberWithUnsignedInteger:10], @"retries",
991 NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector",
992 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
994 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1000 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
1001 object:(ASIPithosObject *)object
1002 localFilePath:(NSString *)filePath
1003 accountName:(NSString *)accountName
1004 pithosContainer:(ASIPithosContainer *)pithosContainer {
1006 [self syncOperationStarted];
1007 NSFileManager *fileManager = [NSFileManager defaultManager];
1009 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1010 if (currentState.isDirectory) {
1011 // Create remote directory object
1012 if (![accountName isEqualToString:@""]) {
1013 if (!object.allowedTo) {
1014 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1015 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1016 while ([objectAncestorName length] && !object.allowedTo) {
1017 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1018 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1021 if (![object.allowedTo isEqualToString:@"write"]) {
1022 // If read-only "shared with me" skip
1023 [self syncOperationFinishedWithSuccess:YES];
1027 if (!fileExists || !isDirectory) {
1028 // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
1029 [self syncOperationFinishedWithSuccess:NO];
1032 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
1033 containerName:pithosContainer.name
1034 objectName:object.name
1036 contentType:@"application/directory"
1038 contentDisposition:nil
1041 isPublic:ASIPithosObjectRequestPublicIgnore
1043 data:[NSData data]];
1044 if (![accountName isEqualToString:@""])
1045 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1046 objectRequest.delegate = self;
1047 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1048 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1049 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Creating directory '%@/%@'",
1050 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1051 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1052 message:messagePrefix
1053 pithosAccount:pithosAccount];
1054 dispatch_async(dispatch_get_main_queue(), ^{
1055 [activityFacility updateActivity:activity withMessage:activity.message];
1057 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1058 accountName, @"accountName",
1059 pithosContainer, @"pithosContainer",
1060 object, @"pithosObject",
1061 messagePrefix, @"messagePrefix",
1062 activity, @"activity",
1063 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1064 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1065 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1066 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1067 [NSNumber numberWithUnsignedInteger:10], @"retries",
1068 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1069 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1071 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1072 } else if (![currentState exists]) {
1073 // Delete remote object
1074 if (![accountName isEqualToString:@""]) {
1075 // If "shared with me" skip
1076 [self syncOperationFinishedWithSuccess:YES];
1080 // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
1081 syncIncomplete = YES;
1083 if ([pithosContainer.name isEqualToString:@"trash"]) {
1085 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
1086 containerName:pithosContainer.name
1087 objectName:object.name];
1088 objectRequest.delegate = self;
1089 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1090 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1091 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Deleting '%@/%@'",
1092 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1093 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1094 message:messagePrefix
1095 pithosAccount:pithosAccount];
1096 dispatch_async(dispatch_get_main_queue(), ^{
1097 [activityFacility updateActivity:activity withMessage:activity.message];
1099 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1100 accountName, @"accountName",
1101 pithosContainer, @"pithosContainer",
1102 object, @"pithosObject",
1103 messagePrefix, @"messagePrefix",
1104 activity, @"activity",
1105 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1106 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1107 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1108 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1109 [NSNumber numberWithUnsignedInteger:10], @"retries",
1110 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
1111 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1113 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1115 // Move to container trash
1117 if ([PithosUtilities isContentTypeDirectory:object.contentType])
1118 safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
1120 safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
1122 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1123 containerName:pithosContainer.name
1124 objectName:object.name
1125 destinationContainerName:@"trash"
1126 destinationObjectName:safeName
1128 if (objectRequest) {
1129 objectRequest.delegate = self;
1130 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1131 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1132 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'",
1133 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1134 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1135 message:messagePrefix
1136 pithosAccount:pithosAccount];
1137 dispatch_async(dispatch_get_main_queue(), ^{
1138 [activityFacility updateActivity:activity withMessage:activity.message];
1140 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1141 accountName, @"accountName",
1142 pithosContainer, @"pithosContainer",
1143 object, @"pithosObject",
1144 messagePrefix, @"messagePrefix",
1145 activity, @"activity",
1146 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1147 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1148 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1149 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1150 [NSNumber numberWithUnsignedInteger:10], @"retries",
1151 NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector",
1152 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1154 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1156 [self syncOperationFinishedWithSuccess:NO];
1159 [self syncOperationFinishedWithSuccess:NO];
1163 // Upload file to remote object
1164 if (![accountName isEqualToString:@""]) {
1165 if (!object.allowedTo) {
1166 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1167 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1168 while ([objectAncestorName length] && !object.allowedTo) {
1169 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1170 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1173 if (![object.allowedTo isEqualToString:@"write"]) {
1174 // If read-only "shared with me" skip
1175 [self syncOperationFinishedWithSuccess:YES];
1179 if (!fileExists || isDirectory) {
1180 // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
1181 [self syncOperationFinishedWithSuccess:NO];
1184 NSError *error = nil;
1185 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1186 if (object.contentType == nil)
1187 object.contentType = @"application/octet-stream";
1190 DLog(@"contentType detection error: %@", error);
1192 NSArray *hashes = nil;
1193 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1194 containerName:pithosContainer.name
1195 objectName:object.name
1196 contentType:object.contentType
1197 blockSize:pithosContainer.blockSize
1198 blockHash:pithosContainer.blockHash
1202 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
1203 if (objectRequest) {
1204 objectRequest.delegate = self;
1205 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1206 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1207 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Uploading '%@/%@'",
1208 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1209 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1210 message:[messagePrefix stringByAppendingString:@" (0%%)"]
1211 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1213 pithosAccount:pithosAccount];
1214 dispatch_async(dispatch_get_main_queue(), ^{
1215 [activityFacility updateActivity:activity withMessage:activity.message];
1217 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1218 [NSDictionary dictionaryWithObjectsAndKeys:
1219 accountName, @"accountName",
1220 pithosContainer, @"pithosContainer",
1221 object, @"pithosObject",
1222 messagePrefix, @"messagePrefix",
1223 filePath, @"filePath",
1225 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1226 activity, @"activity",
1227 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1228 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1229 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1230 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1231 [NSNumber numberWithUnsignedInteger:10], @"retries",
1232 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1233 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1235 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1237 [self syncOperationFinishedWithSuccess:NO];
1244 #pragma mark ASIHTTPRequestDelegate
1246 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1247 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1248 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1249 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1251 operation.completionBlock = ^{
1253 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1254 dispatch_async(dispatch_get_main_queue(), ^{
1255 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1256 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1258 [self syncOperationFinishedWithSuccess:NO];
1262 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1263 [callbackQueue addOperation:operation];
1266 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1267 if (request.isCancelled) {
1268 // Request has been cancelled
1269 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1270 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1271 withObject:request];
1273 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1274 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1275 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1277 operation.completionBlock = ^{
1279 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1280 dispatch_async(dispatch_get_main_queue(), ^{
1281 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1282 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1284 [self syncOperationFinishedWithSuccess:NO];
1288 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1289 [callbackQueue addOperation:operation];
1293 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1295 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1296 DLog(@"Sync::list request finished: %@", containerRequest.url);
1297 if (operation.isCancelled) {
1298 [self listRequestFailed:containerRequest];
1299 } else if ((containerRequest.responseStatusCode == 200) ||
1300 (containerRequest.responseStatusCode == 304) ||
1301 (containerRequest.responseStatusCode == 403)) {
1302 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
1303 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1304 if (containerRequest.responseStatusCode == 200) {
1305 NSArray *someObjects = [containerRequest objects];
1306 if (objects == nil) {
1307 objects = [[NSMutableArray alloc] initWithArray:someObjects];
1309 [objects addObjectsFromArray:someObjects];
1311 if ([someObjects count] < 10000) {
1312 pithosContainer.blockHash = [containerRequest blockHash];
1313 pithosContainer.blockSize = [containerRequest blockSize];
1314 pithosContainer.lastModified = [containerRequest lastModified];
1315 NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1316 for (ASIPithosObject *object in objects) {
1317 [containerRemoteObjects setObject:object forKey:object.name];
1319 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1322 // Do an additional request to fetch more objects
1323 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1324 containerName:pithosContainer.name
1326 marker:[[someObjects lastObject] name]
1333 ifModifiedSince:pithosContainer.lastModified];
1334 if (![accountName isEqualToString:@""])
1335 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1336 newContainerRequest.delegate = self;
1337 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1338 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1339 newContainerRequest.userInfo = containerRequest.userInfo;
1340 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1341 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1344 } else if (containerRequest.responseStatusCode == 304) {
1345 NSMutableDictionary *containerRemoteObjects = [[previousRemoteObjects objectForKey:accountName]
1346 objectForKey:pithosContainer.name];
1347 if (containerRemoteObjects)
1348 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1349 } else if (containerRequest.responseStatusCode == 403) {
1350 [[remoteObjects objectForKey:accountName] setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
1354 if (containersIndex == [[accountsPithosContainers objectForKey:accountName] count]) {
1356 containersIndex = 0;
1358 if (accountsIndex < accountsCount) {
1359 accountName = [accountsNames objectAtIndex:accountsIndex];
1360 pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1361 // Do a request for the next container
1362 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1363 containerName:pithosContainer.name
1372 ifModifiedSince:pithosContainer.lastModified];
1373 if (![accountName isEqualToString:@""])
1374 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1375 newContainerRequest.delegate = self;
1376 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1377 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1378 newContainerRequest.userInfo = containerRequest.userInfo;
1379 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1380 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1383 self.previousRemoteObjects = remoteObjects;
1384 // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
1386 if (operation.isCancelled) {
1387 [self listRequestFailed:containerRequest];
1391 dispatch_async(dispatch_get_main_queue(), ^{
1392 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1393 withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1395 NSFileManager *fileManager = [NSFileManager defaultManager];
1397 // Compute current state of legal existing local objects
1398 // and add an empty stored state for legal new local objects since last sync
1399 self.currentLocalObjectStates = [NSMutableDictionary dictionary];
1400 for (NSString *accountName in accountsNames) {
1401 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1402 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1403 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1404 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1405 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1406 objectForKey:pithosContainer.name];
1407 NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath];
1408 for (__strong NSString *objectName in dirEnumerator) {
1409 objectName = [objectName precomposedStringWithCanonicalMapping];
1410 if (operation.isCancelled) {
1411 operation.completionBlock = nil;
1412 [self saveLocalState];
1413 [self syncOperationFinishedWithSuccess:NO];
1417 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1418 NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1420 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1421 if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) {
1422 [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error"
1423 message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.",
1424 containerDirectoryPath, pithosAccount.name]
1426 pithosAccount.syncActive = NO;
1428 } else if (fileExists) {
1429 if (skipHidden && [[objectName lastPathComponent] hasPrefix:@"."]) {
1430 // Skip hidden directory and its descendants, or hidden file
1432 [dirEnumerator skipDescendants];
1433 // Remove stored state if any
1434 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1436 } else if ([[objectName pathComponents] count] == 1) {
1437 if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) {
1438 // Skip excluded directory and its descendants, or root file with same name
1440 [dirEnumerator skipDescendants];
1441 // Remove stored state if any
1442 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1444 } else if (!isDirectory && containerExcludeRootFiles) {
1445 // Skip excluded root file
1446 // Remove stored state if any
1447 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1451 // Include local object
1452 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
1453 if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1454 // New or modified existing local object, compute current state
1455 if (!storedLocalObjectState)
1456 // For new local object, also create empty stored state
1457 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1458 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
1459 blockHash:pithosContainer.blockHash
1460 blockSize:pithosContainer.blockSize]
1463 // Local object hasn't changed, set stored state also to current
1464 [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1468 [self saveLocalState];
1472 if (operation.isCancelled) {
1473 operation.completionBlock = nil;
1474 [self syncOperationFinishedWithSuccess:NO];
1478 // Add an empty stored state for legal new remote objects since last sync
1479 for (NSString *accountName in accountsNames) {
1480 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1481 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1482 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1483 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1484 objectForKey:pithosContainer.name];
1485 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1486 for (NSString *objectName in containerRemoteObjects) {
1487 if (operation.isCancelled) {
1488 operation.completionBlock = nil;
1489 [self saveLocalState];
1490 [self syncOperationFinishedWithSuccess:NO];
1494 ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName];
1495 NSString *localObjectName;
1496 if ([object.name hasSuffix:@"/"])
1497 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1499 localObjectName = [NSString stringWithString:object.name];
1500 NSArray *pathComponents = [localObjectName pathComponents];
1502 BOOL skipObject = NO;
1503 for (NSString *pathComponent in pathComponents) {
1504 if ([pathComponent hasPrefix:@"."]) {
1505 // Skip hidden directory and its descendants, or hidden file
1506 // Remove stored state if any
1507 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1515 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1516 // Skip excluded directory object and its descendants, or root file object with same name
1517 // Remove stored state if any
1518 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1520 } else if (containerExcludeRootFiles &&
1521 ([pathComponents count] == 1) &&
1522 ![PithosUtilities isContentTypeDirectory:object.contentType]) {
1523 // Skip root file object
1524 // Remove stored state if any
1525 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1528 if (![containerStoredLocalObjectStates objectForKey:object.name])
1529 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name];
1531 [self saveLocalState];
1535 if (operation.isCancelled) {
1536 operation.completionBlock = nil;
1537 [self syncOperationFinishedWithSuccess:NO];
1541 // For each stored state compare with current and remote state
1542 // Stored states of local objects that have been deleted,
1543 // haven't been checked for legality (only existing local remote objects)
1544 // These should be identified and skipped
1545 for (NSString *accountName in accountsNames) {
1546 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1547 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1548 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1549 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1550 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1551 objectForKey:pithosContainer.name];
1552 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1553 for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1554 if (operation.isCancelled) {
1555 operation.completionBlock = nil;
1556 [self syncOperationFinishedWithSuccess:NO];
1560 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1561 if ([objectName hasSuffix:@"/"])
1562 filePath = [filePath stringByAppendingString:@":"];
1563 ASIPithosObject *object = [ASIPithosObject object];
1564 object.name = objectName;
1565 DLog(@"Sync::object name: %@", object.name);
1567 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
1568 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1569 if (!currentLocalObjectState) {
1570 // The stored state corresponds to a remote or deleted local object, that's why there is no current state
1571 // In the latter case it must be checked for legality, which can be determined by its stored state
1572 // If it existed locally, but was deleted, state.exists is true,
1573 // else if the stored state is an empty state that was created due to the server object, state.exists is false
1574 if (storedLocalObjectState.exists) {
1575 NSString *localObjectName;
1576 if ([object.name hasSuffix:@"/"])
1577 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1579 localObjectName = [NSString stringWithString:object.name];
1580 NSArray *pathComponents = [localObjectName pathComponents];
1582 BOOL skipObject = NO;
1583 for (NSString *pathComponent in pathComponents) {
1584 if ([pathComponent hasPrefix:@"."]) {
1585 // Skip hidden directory and its descendants, or hidden file
1586 // Remove stored state if any
1587 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1588 [self saveLocalState];
1596 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1597 // Skip excluded directory object and its descendants, or root file object with same name
1598 // Remove stored state
1599 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1600 [self saveLocalState];
1602 } else if (containerExcludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) {
1603 // Skip root file object
1604 // Remove stored state
1605 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1606 [self saveLocalState];
1610 // There is also the off case that a local object has been created in the meantime
1611 // This call works in any case, existent or non-existent local object
1612 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
1613 blockHash:pithosContainer.blockHash
1614 blockSize:pithosContainer.blockSize];
1617 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1618 ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
1620 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1621 remoteObjectState.isDirectory = YES;
1623 remoteObjectState.hash = remoteObject.objectHash;
1627 BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1628 BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1629 DLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1630 if (!localStateHasChanged) {
1631 // Local state hasn't changed
1632 if (serverStateHasChanged) {
1633 // Server state has changed
1634 // Update local state to match that of the server
1635 object.bytes = remoteObject.bytes;
1636 object.version = remoteObject.version;
1637 object.contentType = remoteObject.contentType;
1638 object.objectHash = remoteObject.objectHash;
1639 [self updateLocalStateWithObject:object localFilePath:filePath
1640 accountName:accountName pithosContainer:pithosContainer];
1641 } else if (!remoteObject && ![currentLocalObjectState exists]) {
1642 // Server state hasn't changed
1643 // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1644 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1645 [self saveLocalState];
1648 // Local state has changed
1649 if (!serverStateHasChanged) {
1650 // Server state hasn't changed
1651 if (currentLocalObjectState.isDirectory)
1652 object.contentType = @"application/directory";
1654 object.objectHash = currentLocalObjectState.hash;
1655 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1656 accountName:accountName pithosContainer:pithosContainer];
1658 // Server state has also changed
1659 if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1660 // Both did the same change (directory)
1661 storedLocalObjectState.filePath = filePath;
1662 storedLocalObjectState.isDirectory = YES;
1663 [self saveLocalState];
1664 } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1665 // Both did the same change (object edit or delete)
1666 if (![remoteObjectState exists]) {
1667 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1669 storedLocalObjectState.filePath = filePath;
1670 storedLocalObjectState.hash = remoteObjectState.hash;
1672 [self saveLocalState];
1674 // Conflict, we ask the user which change to keep
1675 NSString *informativeText;
1676 NSString *firstButtonText;
1677 NSString *secondButtonText;
1679 if (![remoteObjectState exists]) {
1680 // Remote object has been deleted
1681 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.",
1682 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name ];
1683 firstButtonText = @"Delete local file";
1684 secondButtonText = @"Upload file to server";
1685 } else if (![currentLocalObjectState exists]) {
1686 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.",
1687 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1688 firstButtonText = @"Download file from server";
1689 secondButtonText = @"Delete file on server";
1691 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.",
1692 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1693 firstButtonText = @"Keep server version";
1694 secondButtonText = @"Keep local version";
1696 __block NSInteger choice;
1697 dispatch_sync(dispatch_get_main_queue(), ^{
1698 NSAlert *alert = [[NSAlert alloc] init];
1699 [alert setMessageText:@"Conflict"];
1700 [alert setInformativeText:informativeText];
1701 [alert addButtonWithTitle:firstButtonText];
1702 [alert addButtonWithTitle:secondButtonText];
1703 [alert addButtonWithTitle:@"Do nothing"];
1704 choice = [alert runModal];
1706 if (choice == NSAlertFirstButtonReturn) {
1707 object.bytes = remoteObject.bytes;
1708 object.version = remoteObject.version;
1709 object.contentType = remoteObject.contentType;
1710 object.objectHash = remoteObject.objectHash;
1711 [self updateLocalStateWithObject:object localFilePath:filePath
1712 accountName:accountName pithosContainer:pithosContainer];
1713 } if (choice == NSAlertSecondButtonReturn) {
1714 if (currentLocalObjectState.isDirectory)
1715 object.contentType = @"application/directory";
1717 object.objectHash = currentLocalObjectState.hash;
1718 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1719 accountName:accountName pithosContainer:pithosContainer];
1727 [self syncOperationFinishedWithSuccess:YES];
1729 [self listRequestFailed:containerRequest];
1734 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1736 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1737 if (operation.isCancelled) {
1741 if (containerRequest.isCancelled) {
1742 dispatch_async(dispatch_get_main_queue(), ^{
1743 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1744 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1747 [self syncOperationFinishedWithSuccess:NO];
1750 // If the server listing fails, the sync should start over, so just retrying is enough
1751 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1753 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
1754 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1755 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1757 dispatch_async(dispatch_get_main_queue(), ^{
1758 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1759 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1762 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1763 [self syncOperationFinishedWithSuccess:NO];
1768 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1770 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1771 DLog(@"Sync::download object block finished: %@", objectRequest.url);
1772 if (operation.isCancelled) {
1773 [self requestFailed:objectRequest];
1774 } else if (objectRequest.responseStatusCode == 206) {
1775 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1776 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1777 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1778 NSFileManager *fileManager = [NSFileManager defaultManager];
1780 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1782 NSString *downloadsDirPath = self.tempDownloadsDirPath;
1783 if (!downloadsDirPath) {
1784 dispatch_async(dispatch_get_main_queue(), ^{
1785 [activityFacility endActivity:activity
1786 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1788 [self syncOperationFinishedWithSuccess:NO];
1792 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1793 objectForKey:pithosContainer.name]
1794 objectForKey:object.name];
1795 if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1796 NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1797 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1798 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1799 strcpy(tempFileNameCString, tempFileTemplateCString);
1800 int fileDescriptor = mkstemp(tempFileNameCString);
1801 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1802 free(tempFileNameCString);
1803 if (fileDescriptor == -1) {
1804 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
1805 message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]
1807 dispatch_async(dispatch_get_main_queue(), ^{
1808 [activityFacility endActivity:activity
1809 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1811 [self syncOperationFinishedWithSuccess:NO];
1814 close(fileDescriptor);
1815 storedState.tmpFilePath = tempFilePath;
1816 [self saveLocalState];
1819 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1820 NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1821 [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
1822 [tempFileHandle writeData:[objectRequest responseData]];
1823 [tempFileHandle closeFile];
1825 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1826 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1827 if (missingBlockIndex == NSNotFound) {
1828 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1829 NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1830 if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath
1831 accountName:accountName
1832 pithosContainer:pithosContainer]) {
1833 dispatch_async(dispatch_get_main_queue(), ^{
1834 [activityFacility endActivity:activity
1835 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1837 [self syncOperationFinishedWithSuccess:NO];
1839 } else if (![fileManager fileExistsAtPath:dirPath]) {
1840 // File doesn't exist but also the containing directory doesn't exist
1841 // In most cases this should have been resolved as an update of the corresponding local object,
1842 // but it never hurts to check
1844 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1846 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1847 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
1849 dispatch_async(dispatch_get_main_queue(), ^{
1850 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1851 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1853 [self syncOperationFinishedWithSuccess:NO];
1857 // Move file from tmp download
1859 [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1861 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
1862 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath]
1864 dispatch_async(dispatch_get_main_queue(), ^{
1865 [activityFacility endActivity:activity
1866 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1868 [self syncOperationFinishedWithSuccess:NO];
1871 dispatch_async(dispatch_get_main_queue(), ^{
1872 [activityFacility endActivity:activity
1873 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1874 totalBytes:activity.totalBytes
1875 currentBytes:activity.totalBytes];
1878 storedState.filePath = filePath;
1879 storedState.hash = object.objectHash;
1880 storedState.tmpFilePath = nil;
1881 [self saveLocalState];
1882 [self syncOperationFinishedWithSuccess:YES];
1885 if (newSyncRequested || syncLate || operation.isCancelled) {
1886 [self requestFailed:objectRequest];
1888 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1889 containerName:pithosContainer.name
1891 blockIndex:missingBlockIndex
1892 blockSize:pithosContainer.blockSize];
1893 if (![accountName isEqualToString:@""])
1894 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1895 newObjectRequest.delegate = self;
1896 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1897 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1898 newObjectRequest.userInfo = objectRequest.userInfo;
1899 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1900 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1901 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1902 [activityFacility updateActivity:activity
1903 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1904 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
1905 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1906 totalBytes:activity.totalBytes
1907 currentBytes:(activity.currentBytes + size)];
1909 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1912 } else if (objectRequest.responseStatusCode == 412) {
1913 // The object has changed on the server
1914 dispatch_async(dispatch_get_main_queue(), ^{
1915 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1916 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1918 [self syncOperationFinishedWithSuccess:NO];
1920 [self requestFailed:objectRequest];
1925 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1927 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1928 DLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1929 if (operation.isCancelled) {
1930 [self requestFailed:objectRequest];
1931 } else if (objectRequest.responseStatusCode == 200) {
1932 if (newSyncRequested || syncLate || operation.isCancelled) {
1933 [self requestFailed:objectRequest];
1935 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1936 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1937 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1938 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1939 objectForKey:pithosContainer.name]
1940 objectForKey:object.name];
1941 if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1942 [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
1943 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1944 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath
1945 blockSize:pithosContainer.blockSize
1946 blockHash:pithosContainer.blockHash
1947 withHashes:[objectRequest hashes]];
1948 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1949 dispatch_async(dispatch_get_main_queue(), ^{
1950 [activityFacility updateActivity:activity
1951 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1952 [objectRequest.userInfo objectForKey:@"messagePrefix"],
1953 (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))]
1954 totalBytes:activity.totalBytes
1955 currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
1958 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1959 containerName:pithosContainer.name
1961 blockIndex:missingBlockIndex
1962 blockSize:pithosContainer.blockSize];
1963 if (![accountName isEqualToString:@""])
1964 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1965 newObjectRequest.delegate = self;
1966 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1967 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1968 newObjectRequest.userInfo = objectRequest.userInfo;
1969 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1970 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1971 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1972 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1973 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1974 [activityFacility updateActivity:activity
1975 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1976 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
1977 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1978 totalBytes:activity.totalBytes
1979 currentBytes:(activity.currentBytes + size)];
1981 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1984 [self requestFailed:objectRequest];
1989 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1991 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1992 DLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1993 if (operation.isCancelled) {
1994 [self requestFailed:objectRequest];
1995 } else if (objectRequest.responseStatusCode == 201) {
1996 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
1997 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
1998 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1999 storedState.isDirectory = YES;
2000 [self saveLocalState];
2001 dispatch_async(dispatch_get_main_queue(), ^{
2002 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2003 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2005 [self syncOperationFinishedWithSuccess:YES];
2007 [self requestFailed:objectRequest];
2012 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
2014 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2015 DLog(@"Sync::move object to trash finished: %@", objectRequest.url);
2016 if (operation.isCancelled) {
2017 [self requestFailed:objectRequest];
2018 } else if (objectRequest.responseStatusCode == 201) {
2019 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2020 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2021 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2022 [self saveLocalState];
2023 dispatch_async(dispatch_get_main_queue(), ^{
2024 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2025 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2027 [self syncOperationFinishedWithSuccess:YES];
2029 [self requestFailed:objectRequest];
2034 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2036 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2037 DLog(@"Sync::delete object finished: %@", objectRequest.url);
2038 if (operation.isCancelled) {
2039 [self requestFailed:objectRequest];
2040 } else if (objectRequest.responseStatusCode == 204) {
2041 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2042 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2043 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2044 [self saveLocalState];
2045 dispatch_async(dispatch_get_main_queue(), ^{
2046 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2047 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2049 [self syncOperationFinishedWithSuccess:YES];
2051 [self requestFailed:objectRequest];
2056 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
2058 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2059 DLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
2060 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
2061 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
2062 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
2063 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
2064 objectForKey:pithosContainer.name]
2065 objectForKey:object.name];
2066 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
2067 NSUInteger totalBytes = activity.totalBytes;
2068 NSUInteger currentBytes = activity.currentBytes;
2069 if (operation.isCancelled) {
2070 [self requestFailed:objectRequest];
2071 } else if (objectRequest.responseStatusCode == 201) {
2072 DLog(@"Sync::object created: %@", objectRequest.url);
2073 storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
2074 storedState.hash = object.objectHash;
2075 [self saveLocalState];
2076 dispatch_async(dispatch_get_main_queue(), ^{
2077 [activityFacility endActivity:activity
2078 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
2079 totalBytes:totalBytes
2080 currentBytes:totalBytes];
2082 [self syncOperationFinishedWithSuccess:YES];
2083 } else if (objectRequest.responseStatusCode == 409) {
2084 if (newSyncRequested || syncLate || operation.isCancelled) {
2085 [self requestFailed:objectRequest];
2087 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
2088 if (iteration == 0) {
2089 DLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
2090 dispatch_async(dispatch_get_main_queue(), ^{
2091 [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
2093 [self syncOperationFinishedWithSuccess:NO];
2096 DLog(@"Sync::object is missing hashes: %@", objectRequest.url);
2097 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
2098 withMissingHashes:[objectRequest hashes]];
2099 if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
2100 currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
2101 dispatch_async(dispatch_get_main_queue(), ^{
2102 [activityFacility updateActivity:activity
2103 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2104 [objectRequest.userInfo objectForKey:@"messagePrefix"],
2105 (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
2106 totalBytes:totalBytes
2107 currentBytes:currentBytes];
2109 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2110 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2111 containerName:pithosContainer.name
2112 blockSize:pithosContainer.blockSize
2113 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
2114 missingBlockIndex:missingBlockIndex
2115 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2116 newContainerRequest.delegate = self;
2117 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2118 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2119 newContainerRequest.userInfo = objectRequest.userInfo;
2120 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
2121 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2122 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2123 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2124 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
2125 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2126 [activityFacility updateActivity:activity
2127 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2128 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2129 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2130 totalBytes:activity.totalBytes
2131 currentBytes:(activity.currentBytes + size)];
2133 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2136 [self requestFailed:objectRequest];
2141 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
2143 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
2144 DLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
2145 if (operation.isCancelled) {
2146 [self requestFailed:containerRequest];
2147 } else if (containerRequest.responseStatusCode == 202) {
2148 NSString *accountName = [containerRequest.userInfo objectForKey:@"accountName"];
2149 ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
2150 ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
2151 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
2152 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
2153 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
2154 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
2155 if (operation.isCancelled) {
2156 [self requestFailed:containerRequest];
2157 } else if (missingBlockIndex == NSNotFound) {
2158 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
2159 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
2160 containerName:pithosContainer.name
2161 objectName:object.name
2162 contentType:object.contentType
2163 blockSize:pithosContainer.blockSize
2164 blockHash:pithosContainer.blockHash
2165 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2168 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2169 newObjectRequest.delegate = self;
2170 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2171 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2172 newObjectRequest.userInfo = containerRequest.userInfo;
2173 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2174 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
2175 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
2176 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
2177 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2179 if (newSyncRequested || syncLate || operation.isCancelled) {
2180 [self requestFailed:containerRequest];
2182 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2183 containerName:pithosContainer.name
2184 blockSize:pithosContainer.blockSize
2185 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2186 missingBlockIndex:missingBlockIndex
2187 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2188 newContainerRequest.delegate = self;
2189 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2190 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2191 newContainerRequest.userInfo = containerRequest.userInfo;
2192 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2193 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2194 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2195 [activityFacility updateActivity:activity
2196 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2197 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2198 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2199 totalBytes:activity.totalBytes
2200 currentBytes:(activity.currentBytes + size)];
2202 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2206 [self requestFailed:containerRequest];
2211 - (void)requestFailed:(ASIPithosRequest *)request {
2213 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
2214 DLog(@"Sync::request failed: %@", request.url);
2215 if (operation.isCancelled)
2217 if (request.isCancelled || newSyncRequested || syncLate) {
2218 dispatch_async(dispatch_get_main_queue(), ^{
2219 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2220 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
2222 [self syncOperationFinishedWithSuccess:NO];
2225 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
2227 ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
2228 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
2229 [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
2231 dispatch_async(dispatch_get_main_queue(), ^{
2232 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2233 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
2235 [self syncOperationFinishedWithSuccess:NO];