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 userCatalog, accountsNames, accountsPithosContainers;
86 @synthesize lastCompletedSync, remoteObjects, previousRemoteObjects, storedLocalObjectStates, currentLocalObjectStates;
87 @synthesize userCatalogFilePath, 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 // Load user catalog.
112 if ([[NSFileManager defaultManager] fileExistsAtPath:self.userCatalogFilePath])
113 self.userCatalog = [NSKeyedUnarchiver unarchiveObjectWithFile:self.userCatalogFilePath];
115 self.userCatalog = [NSMutableDictionary dictionary];
117 self.userCatalog = [NSMutableDictionary dictionary];
119 networkQueue = [[ASINetworkQueue alloc] init];
120 networkQueue.showAccurateProgress = YES;
121 networkQueue.shouldCancelAllRequestsOnFailure = NO;
122 // networkQueue.maxConcurrentOperationCount = 1;
124 callbackQueue = [[NSOperationQueue alloc] init];
125 [callbackQueue setSuspended:YES];
126 callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
127 // callbackQueue.maxConcurrentOperationCount = 1;
129 [[NSNotificationCenter defaultCenter] addObserver:self
130 selector:@selector(applicationWillTerminate:)
131 name:NSApplicationWillTerminateNotification
132 object:[NSApplication sharedApplication]];
137 - (void)loadLocalState {
139 if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath])
140 self.storedLocalObjectStates = [NSKeyedUnarchiver unarchiveObjectWithFile:self.pithosStateFilePath];
142 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
143 if (!storedLocalObjectStates)
144 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
145 for (NSString *accountName in accountsNames) {
146 NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
147 if (!accountStoredLocalObjectStates) {
148 accountStoredLocalObjectStates = [NSMutableDictionary dictionary];
149 [storedLocalObjectStates setObject:accountStoredLocalObjectStates forKey:accountName];
151 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
152 if (![accountStoredLocalObjectStates objectForKey:pithosContainer.name])
153 [accountStoredLocalObjectStates setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
159 - (void)resetLocalStateWithAll:(BOOL)all {
161 self.lastCompletedSync = nil;
163 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
164 [self saveLocalState]; // Save an empty dictionary
165 [self loadLocalState]; // Load to populate with containers
166 [self saveLocalState]; // Save again
167 if (self.tempDownloadsDirPath)
168 [PithosUtilities removeContentsAtPath:self.tempDownloadsDirPath];
170 // Remove containers that don't interest us anymore and save
171 if (!storedLocalObjectStates)
172 [self loadLocalState];
173 for (NSString *accountName in [storedLocalObjectStates allKeys]) {
174 NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
175 NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
176 if (!containersDictionary) {
177 if (self.tempDownloadsDirPath) {
178 if ([accountName isEqualToString:@""]) {
179 for (NSString *containerName in accountStoredLocalObjectStates) {
180 [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]
184 [PithosUtilities removeContentsAtPath:[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared with me"]
185 stringByAppendingPathComponent:accountName]
189 [storedLocalObjectStates removeObjectForKey:accountName];
191 // Check the account's containers
192 for (NSString *containerName in [accountStoredLocalObjectStates allKeys]) {
193 if (![containersDictionary objectForKey:containerName]) {
194 if ([accountName isEqualToString:@""])
195 [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]
198 [PithosUtilities removeContentsAtPath:[[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared with me"]
199 stringByAppendingPathComponent:accountName]
200 stringByAppendingPathComponent:containerName]
202 [accountStoredLocalObjectStates removeObjectForKey:containerName];
207 [self saveLocalState];
212 - (void)saveLocalState {
214 [NSKeyedArchiver archiveRootObject:storedLocalObjectStates toFile:self.pithosStateFilePath];
218 - (void)resetDaemon {
219 @synchronized(self) {
224 [networkQueue reset];
225 [callbackQueue cancelAllOperations];
226 [callbackQueue setSuspended:YES];
227 [self emptyTempTrash];
229 syncOperationCount = 0;
231 @synchronized(self) {
236 - (void)startDaemon {
237 @synchronized(self) {
242 // In the improbable case of leftover operations
243 [networkQueue reset];
244 [callbackQueue cancelAllOperations];
246 syncOperationCount = 0;
247 newSyncRequested = NO;
251 [self loadLocalState];
254 [callbackQueue setSuspended:NO];
256 @synchronized(self) {
262 [[NSNotificationCenter defaultCenter] removeObserver:self];
267 #pragma mark Observers
269 - (void)applicationWillTerminate:(NSNotification *)notification {
270 [self saveLocalState];
274 #pragma mark Properties
276 - (NSString *)userCatalogFilePath {
277 if (!userCatalogFilePath) {
278 userCatalogFilePath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
279 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
280 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-SyncUserCatalog.archive",
281 pithosAccount.uniqueName]];
282 NSString *userCatalogFileDirPath = [userCatalogFilePath stringByDeletingLastPathComponent];
283 NSFileManager *fileManager = [NSFileManager defaultManager];
285 BOOL fileExists = [fileManager fileExistsAtPath:userCatalogFileDirPath isDirectory:&isDirectory];
286 NSError *error = nil;
287 if (fileExists && !isDirectory)
288 [fileManager removeItemAtPath:userCatalogFileDirPath error:&error];
289 if (!error && !fileExists)
290 [fileManager createDirectoryAtPath:userCatalogFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
292 // userCatalogFilePath = nil;
293 // XXX create a dir using mktmps?
295 return [userCatalogFilePath copy];
298 - (NSString *)pithosStateFilePath {
299 if (!pithosStateFilePath) {
300 pithosStateFilePath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
301 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
302 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive",
303 pithosAccount.uniqueName]];
304 NSString *pithosStateFileDirPath = [pithosStateFilePath stringByDeletingLastPathComponent];
305 NSFileManager *fileManager = [NSFileManager defaultManager];
307 BOOL fileExists = [fileManager fileExistsAtPath:pithosStateFileDirPath isDirectory:&isDirectory];
308 NSError *error = nil;
309 if (fileExists && !isDirectory)
310 [fileManager removeItemAtPath:pithosStateFileDirPath error:&error];
311 if (!error && !fileExists)
312 [fileManager createDirectoryAtPath:pithosStateFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
314 // pithosStateFilePath = nil;
315 // XXX create a dir using mktmps?
317 return [pithosStateFilePath copy];
320 - (NSString *)tempDownloadsDirPath {
321 if (!tempDownloadsDirPath) {
322 tempDownloadsDirPath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
323 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
324 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads",
325 pithosAccount.uniqueName]];
326 NSFileManager *fileManager = [NSFileManager defaultManager];
328 BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
329 NSError *error = nil;
330 if (fileExists && !isDirectory)
331 [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
332 if (!error && !fileExists)
333 [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
335 // tempDownloadsDirPath = nil;
336 // XXX create a dir using mktmps?
338 return tempDownloadsDirPath;
341 - (NSString *)tempTrashDirPath {
342 if (!tempTrashDirPath) {
343 tempTrashDirPath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
344 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
345 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash",
346 pithosAccount.uniqueName]];
347 NSFileManager *fileManager = [NSFileManager defaultManager];
349 BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
350 NSError *error = nil;
351 if (fileExists && !isDirectory)
352 [fileManager removeItemAtPath:tempTrashDirPath error:&error];
353 if (!error && !fileExists)
354 [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
356 // tempTrashDirPath = nil;
357 // XXX create a dir using mktmps?
359 return tempTrashDirPath;
362 - (void)setDirectoryPath:(NSString *)aDirectoryPath {
363 if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
365 [self resetLocalStateWithAll:YES];
366 directoryPath = [aDirectoryPath copy];
370 - (void)setAccountsDictionary:(NSDictionary *)anAccountsDictionary {
371 if (anAccountsDictionary && ![anAccountsDictionary isEqualToDictionary:accountsDictionary]) {
372 BOOL reset = (accountsDictionary != nil);
376 accountsDictionary = [anAccountsDictionary copy];
378 accountsCount = [accountsDictionary count];
379 self.accountsNames = [NSMutableArray arrayWithCapacity:accountsCount];
380 self.accountsPithosContainers = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
381 NSDictionary *containersDictionary = [accountsDictionary objectForKey:@""];
382 if (containersDictionary && [containersDictionary count]) {
383 NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
384 for (NSString *containerName in containersDictionary) {
385 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
386 pithosContainer.name = containerName;
387 [pithosContainers addObject:pithosContainer];
389 [accountsNames addObject:@""];
390 [accountsPithosContainers setObject:pithosContainers forKey:@""];
392 for (NSString *accountName in accountsDictionary) {
393 NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
394 if (![accountsNames containsObject:accountName] && [containersDictionary count]) {
395 NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
396 for (NSString *containerName in containersDictionary) {
397 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
398 pithosContainer.name = containerName;
399 [pithosContainers addObject:pithosContainer];
401 [accountsNames addObject:accountName];
402 [accountsPithosContainers setObject:pithosContainers forKey:accountName];
407 [self resetLocalStateWithAll:NO];
411 - (void)setPithos:(ASIPithos *)aPithos {
413 pithos = [ASIPithos pithos];
414 pithos.authUser = [aPithos.authUser copy];
415 pithos.authToken = [aPithos.authToken copy];
416 pithos.storageURLPrefix = [aPithos.storageURLPrefix copy];
417 pithos.authURL = [aPithos.authURL copy];
418 pithos.publicURLPrefix = [aPithos.publicURLPrefix copy];
419 pithos.userCatalogURL = [aPithos.userCatalogURL copy];
422 (![aPithos.authUser isEqualToString:pithos.authUser] ||
423 ![aPithos.authToken isEqualToString:pithos.authToken] ||
424 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
426 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
427 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
428 [self resetLocalStateWithAll:YES];
429 pithos.authUser = [aPithos.authUser copy];
430 pithos.authToken = [aPithos.authToken copy];
431 pithos.storageURLPrefix = [aPithos.storageURLPrefix copy];
432 pithos.authURL = [aPithos.authURL copy];
433 pithos.publicURLPrefix = [aPithos.publicURLPrefix copy];
434 pithos.userCatalogURL = [aPithos.userCatalogURL copy];
439 #pragma mark Helper Methods
441 - (BOOL)createSyncDirectory:(NSString *)dirPath {
442 NSFileManager *fileManager = [NSFileManager defaultManager];
444 NSError *error = nil;
445 if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory]) {
446 if (![fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
447 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
448 message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'",
453 } else if (!isDirectory) {
454 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
455 message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'",
463 - (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
464 if ([accountName isEqualToString:@""]) {
465 return containerName;
467 NSMutableDictionary *displaynameDictionary = [userCatalog objectForKey:accountName];
468 if ([[displaynameDictionary objectForKey:@"suffix"] unsignedIntegerValue] == 0) {
469 return [[@"shared with me" stringByAppendingPathComponent:[displaynameDictionary objectForKey:@"displayname"]]
470 stringByAppendingPathComponent:containerName];
472 return [[@"shared with me" stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ (%@)",
473 [displaynameDictionary objectForKey:@"displayname"],
474 [displaynameDictionary objectForKey:@"suffix"]]]
475 stringByAppendingPathComponent:containerName];
480 - (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
481 return [directoryPath stringByAppendingPathComponent:[self relativeDirPathForAccount:accountName container:containerName]];
487 - (void)syncOperationStarted {
488 @synchronized(self) {
489 syncOperationCount++;
493 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
494 @synchronized(self) {
495 if (!operationSuccessfull)
496 syncIncomplete = YES;
497 if (syncOperationCount == 0) {
498 // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
499 DLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
502 syncOperationCount--;
503 if (syncOperationCount == 0) {
504 if (!syncIncomplete) {
505 self.lastCompletedSync = [NSDate date];
506 dispatch_async(dispatch_get_main_queue(), ^{
507 [activityFacility startAndEndActivityWithType:PithosActivityOther
508 message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]
509 pithosAccount:pithosAccount];
513 [self emptyTempTrash];
514 if (newSyncRequested && daemonActive)
521 @synchronized(self) {
522 return ((syncOperationCount > 0) && daemonActive);
527 @synchronized(self) {
528 if ([self isSyncing])
534 @synchronized(self) {
535 if ([self isSyncing]) {
536 // If at least one operation is running return
537 newSyncRequested = YES;
539 } else if (daemonActive && accountsCount) {
540 // The first operation is the server listing
541 [self syncOperationStarted];
542 newSyncRequested = NO;
550 if (![self createSyncDirectory:directoryPath]) {
551 [self syncOperationFinishedWithSuccess:NO];
554 // Update user catalog for accountsNames.
555 ASIPithosRequest *userCatalogRequest = [pithosAccount updateUserCatalogForForDisplaynames:nil UUIDs:accountsNames];
556 if (userCatalogRequest.error || ((userCatalogRequest.responseStatusCode != 200) && (userCatalogRequest.responseStatusCode != 404))) {
557 // Update failed try sync again later.
558 [self syncOperationFinishedWithSuccess:NO];
561 for (NSString *accountName in accountsNames) {
562 if (![accountName isEqualToString:@""]) {
563 // If 404, displayname will be same as accountName.
564 NSString *displayname = [pithosAccount displaynameForUUID:accountName safe:YES];
565 NSMutableDictionary *displaynameDictionary = [userCatalog objectForKey:accountName];
566 if (!displaynameDictionary) {
567 // New entry in the user catalog. Determine suffix discriminator (0 is for no suffix).
568 NSNumber *suffix = [NSNumber numberWithInteger:0];
569 for (NSMutableDictionary *otherDisplaynameDictionary in [userCatalog objectEnumerator]) {
570 if ([[otherDisplaynameDictionary objectForKey:@"displayname"] isEqualToString:displayname] &&
571 [[otherDisplaynameDictionary objectForKey:@"suffix"] isGreaterThanOrEqualTo:suffix]) {
572 suffix = [NSNumber numberWithUnsignedInteger:([[otherDisplaynameDictionary objectForKey:@"suffix"] unsignedIntegerValue] + 1)];
575 [userCatalog setObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
576 displayname, @"displayname",
580 // Save user catalog.
581 [NSKeyedArchiver archiveRootObject:userCatalog toFile:self.userCatalogFilePath];
582 } else if (![[displaynameDictionary objectForKey:@"displayname"] isEqualToString:displayname]) {
583 // First determine new suffix.
584 NSNumber *suffix = [NSNumber numberWithInteger:0];
585 for (NSMutableDictionary *otherDisplaynameDictionary in [userCatalog objectEnumerator]) {
586 if ([[otherDisplaynameDictionary objectForKey:@"displayname"] isEqualToString:displayname] &&
587 [[otherDisplaynameDictionary objectForKey:@"suffix"] isGreaterThanOrEqualTo:suffix]) {
588 suffix = [NSNumber numberWithUnsignedInteger:([[otherDisplaynameDictionary objectForKey:@"suffix"] unsignedIntegerValue] + 1)];
591 // Compute old and new sync account dirPaths.
592 NSString *accountDirPath = (([[displaynameDictionary objectForKey:@"suffix"] unsignedIntegerValue] == 0) ?
593 [[directoryPath stringByAppendingPathComponent:@"shared with me"]
594 stringByAppendingPathComponent:[displaynameDictionary objectForKey:@"displayname"]] :
595 [[directoryPath stringByAppendingPathComponent:@"shared with me"]
596 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ (%@)",
597 [displaynameDictionary objectForKey:@"displayname"],
598 [displaynameDictionary objectForKey:@"suffix"]]]);
599 NSString *newAccountDirPath = (([suffix unsignedIntegerValue] == 0) ?
600 [[directoryPath stringByAppendingPathComponent:@"shared with me"]
601 stringByAppendingPathComponent:displayname] :
602 [[directoryPath stringByAppendingPathComponent:@"shared with me"]
603 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ (%@)",
604 displayname, suffix]]);
605 // Check if the old account sync directory exists.
606 NSFileManager *fileManager = [NSFileManager defaultManager];
608 NSError *error = nil;
609 if ([fileManager fileExistsAtPath:accountDirPath isDirectory:&isDirectory] && isDirectory &&
610 (![fileManager moveItemAtPath:accountDirPath toPath:newAccountDirPath error:&error] || error)) {
611 // If so try to move it. We know that the containing directory "shared with me" exists.
612 [PithosUtilities fileActionFailedAlertWithTitle:@"Move Directory Error"
613 message:[NSString stringWithFormat:@"Cannot move directory at '%@' to '%@'",
614 accountDirPath, newAccountDirPath]
616 [self syncOperationFinishedWithSuccess:NO];
619 // Else the new account sync directory will be created afterwards.
620 // Fix filePaths of the stored local object states of the account.
621 for (NSMutableDictionary *containerStoredLocalObjectStates in [[storedLocalObjectStates objectForKey:accountName] objectEnumerator]) {
622 for (PithosLocalObjectState *storedLocalObjectState in [containerStoredLocalObjectStates objectEnumerator]) {
623 if ([storedLocalObjectState.filePath hasPrefix:accountDirPath]) {
624 storedLocalObjectState.filePath = [newAccountDirPath stringByAppendingString:
625 [storedLocalObjectState.filePath substringFromIndex:accountDirPath.length]];
629 [self saveLocalState];
630 // Finally update user catalog.
631 [userCatalog setObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
632 displayname, @"displayname",
636 // Save user catalog.
637 [NSKeyedArchiver archiveRootObject:userCatalog toFile:self.userCatalogFilePath];
639 // Else no change is required.
643 for (NSString *accountName in accountsNames) {
644 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
645 if (![self createSyncDirectory:[self dirPathForAccount:accountName container:pithosContainer.name]]) {
646 [self syncOperationFinishedWithSuccess:NO];
652 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
653 for (NSString *accountName in accountsNames) {
654 [remoteObjects setObject:[NSMutableDictionary dictionary] forKey:accountName];
658 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
659 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
660 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
661 containerName:pithosContainer.name
670 ifModifiedSince:pithosContainer.lastModified];
671 if (![accountName isEqualToString:@""])
672 [containerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
673 containerRequest.delegate = self;
674 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
675 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
676 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther
677 message:@"Sync: Getting server listing"
678 pithosAccount:pithosAccount];
679 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
680 activity, @"activity",
681 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage",
682 @"Sync: Getting server listing (failed)", @"failedActivityMessage",
683 @"Sync: Getting server listing (finished)", @"finishedActivityMessage",
684 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
685 [NSNumber numberWithUnsignedInteger:10], @"retries",
686 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector",
687 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector",
689 [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
692 - (void)emptyTempTrash {
694 NSString *trashDirPath = self.tempTrashDirPath;
696 NSFileManager *fileManager = [NSFileManager defaultManager];
697 NSError *error = nil;
698 // NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
699 NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
701 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
702 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath]
706 if ([subPaths count]) {
707 // NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
708 // for (NSString *subPath in subPaths) {
709 // [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
711 // [self syncOperationStarted];
712 // [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
714 // [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
715 // message:@"Cannot move files to Trash"
718 // [self syncOperationFinishedWithSuccess:YES];
720 for (NSString *subPath in subPaths) {
721 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
723 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
724 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
725 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
733 - (BOOL)moveToTempTrashFile:(NSString *)filePath
734 accountName:(NSString *)accountName
735 pithosContainer:(ASIPithosContainer *)pithosContainer {
737 if (!self.tempTrashDirPath)
739 NSFileManager *fileManager = [NSFileManager defaultManager];
741 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
742 NSError *error = nil;
743 NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:directoryPath withString:self.tempTrashDirPath];
744 NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
745 if (fileExists && isDirectory) {
746 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
748 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
749 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath]
753 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
754 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
755 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
759 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
760 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
761 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
762 filePath, newFilePath]
766 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
768 currentState.filePath = newFilePath;
769 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
770 [currentLocalObjectStates removeObjectForKey:filePath];
772 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
773 blockHash:pithosContainer.blockHash
774 blockSize:pithosContainer.blockSize]
777 for (NSString *subPath in subPaths) {
778 NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
779 NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:directoryPath
780 withString:self.tempTrashDirPath];
781 currentState = [currentLocalObjectStates objectForKey:subFilePath];
783 currentState.filePath = newSubFilePath;
784 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
785 [currentLocalObjectStates removeObjectForKey:subFilePath];
787 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
788 blockHash:pithosContainer.blockHash
789 blockSize:pithosContainer.blockSize]
790 forKey:newSubFilePath];
793 } else if (fileExists && !isDirectory) {
794 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
795 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
796 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
800 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
801 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
802 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
803 filePath, newFilePath]
807 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
809 currentState.filePath = newFilePath;
810 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
811 [currentLocalObjectStates removeObjectForKey:filePath];
813 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
814 blockHash:pithosContainer.blockHash
815 blockSize:pithosContainer.blockSize]
823 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
824 if ([hash length] != 64)
827 PithosLocalObjectState *localState;
828 NSFileManager *fileManager = [NSFileManager defaultManager];
830 NSError *error = nil;
831 for (NSString *localFilePath in [currentLocalObjectStates allKeys]) {
832 localState = [currentLocalObjectStates objectForKey:localFilePath];
833 if (!localState.isDirectory && [hash isEqualToString:localState.hash] &&
834 [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
835 if ([localFilePath hasPrefix:directoryPath]) {
836 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
837 [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
838 message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
839 localFilePath, filePath]
844 } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
845 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
846 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
847 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
848 localFilePath, filePath]
851 localState.filePath = filePath;
852 [currentLocalObjectStates setObject:localState forKey:filePath];
853 [currentLocalObjectStates removeObjectForKey:localFilePath];
863 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
864 localFilePath:(NSString *)filePath
865 accountName:(NSString *)accountName
866 pithosContainer:(ASIPithosContainer *)pithosContainer {
868 NSFileManager *fileManager = [NSFileManager defaultManager];
871 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
872 NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
873 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
874 objectForKey:pithosContainer.name];
875 PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
876 // Remote updated info
877 NSError *remoteError;
878 BOOL remoteIsDirectory;
879 BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos
880 containerName:pithosContainer.name
881 objectName:object.name
883 isDirectory:&remoteIsDirectory
884 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
885 if (!object || !object.objectHash) {
886 // Delete local object
887 if (![accountName isEqualToString:@""])
888 // If "shared with me" skip
890 if (remoteObjectExists) {
891 // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
892 syncIncomplete = YES;
894 DLog(@"Sync::delete local object: %@", filePath);
895 if (!fileExists || [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer]) {
896 dispatch_async(dispatch_get_main_queue(), ^{
897 [activityFacility startAndEndActivityWithType:PithosActivityOther
898 message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)",
899 pithosContainer.name, object.name]
900 pithosAccount:pithosAccount];
902 [containerStoredLocalObjectStates removeObjectForKey:object.name];
903 [self saveLocalState];
905 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
906 // Create local directory object
907 if (!remoteObjectExists || !remoteIsDirectory) {
908 // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
909 syncIncomplete = YES;
912 DLog(@"Sync::create local directory object: %@", filePath);
913 BOOL directoryCreated = NO;
914 if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
915 DLog(@"Sync::local directory object doesn't exist: %@", filePath);
917 if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
918 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
919 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
922 directoryCreated = YES;
923 storedState.filePath = filePath;
924 storedState.isDirectory = YES;
925 [self saveLocalState];
928 DLog(@"Sync::local directory object exists: %@", filePath);
929 directoryCreated = YES;
931 if (directoryCreated)
932 dispatch_async(dispatch_get_main_queue(), ^{
933 [activityFacility startAndEndActivityWithType:PithosActivityOther
934 message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)",
935 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
937 pithosAccount:pithosAccount];
939 } else if (object.bytes == 0) {
940 // Create local object with zero length
941 if (!remoteObjectExists || remoteIsDirectory) {
942 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
943 syncIncomplete = YES;
946 DLog(@"Sync::create local zero length object: %@", filePath);
947 BOOL fileCreated = NO;
949 ((isDirectory || [PithosUtilities bytesOfFile:filePath]) &&
950 [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
951 DLog(@"Sync::local zero length object doesn't exist: %@", filePath);
952 // Create directory of the file, if it doesn't exist
954 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
955 dispatch_async(dispatch_get_main_queue(), ^{
956 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
957 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
962 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
963 [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
964 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
968 storedState.filePath = filePath;
969 storedState.hash = object.objectHash;
970 storedState.tmpFilePath = nil;
971 [self saveLocalState];
974 DLog(@"Sync::local zero length object exists: %@", filePath);
978 dispatch_async(dispatch_get_main_queue(), ^{
979 [activityFacility startAndEndActivityWithType:PithosActivityOther
980 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
981 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
983 pithosAccount:pithosAccount];
985 } else if (storedState.tmpFilePath == nil) {
986 // Create new local object
987 if (!remoteObjectExists || remoteIsDirectory) {
988 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
989 syncIncomplete = YES;
992 // Create directory of the file, if it doesn't exist
994 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
995 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
996 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
999 // Check first if a local copy exists
1000 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
1001 storedState.filePath = filePath;
1002 storedState.hash = object.objectHash;
1003 [self saveLocalState];
1004 dispatch_async(dispatch_get_main_queue(), ^{
1005 [activityFacility startAndEndActivityWithType:PithosActivityOther
1006 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
1007 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
1009 pithosAccount:pithosAccount];
1012 [self syncOperationStarted];
1013 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1014 containerName:pithosContainer.name
1017 blockSize:pithosContainer.blockSize];
1018 if (![accountName isEqualToString:@""])
1019 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1020 objectRequest.delegate = self;
1021 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1022 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1023 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
1024 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1025 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1026 message:[messagePrefix stringByAppendingString:@" (0%%)"]
1027 totalBytes:object.bytes
1029 pithosAccount:pithosAccount];
1030 dispatch_async(dispatch_get_main_queue(), ^{
1031 [activityFacility updateActivity:activity withMessage:activity.message];
1033 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1034 accountName, @"accountName",
1035 pithosContainer, @"pithosContainer",
1036 object, @"pithosObject",
1037 messagePrefix, @"messagePrefix",
1038 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks",
1039 [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
1040 filePath, @"filePath",
1041 activity, @"activity",
1042 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1043 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1044 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1045 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1046 [NSNumber numberWithUnsignedInteger:10], @"retries",
1047 NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector",
1048 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1050 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1051 [activityFacility updateActivity:activity
1052 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1053 messagePrefix, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1054 totalBytes:activity.totalBytes
1055 currentBytes:(activity.currentBytes + size)];
1057 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1060 // Resume local object download
1061 if (!remoteObjectExists || remoteIsDirectory) {
1062 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
1063 syncIncomplete = YES;
1066 // Create directory of the file, if it doesn't exist
1068 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
1069 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1070 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
1073 // Check first if a local copy exists
1074 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
1075 storedState.filePath = filePath;
1076 storedState.hash = object.objectHash;
1077 // Delete incomplete temp download
1078 storedState.tmpFilePath = nil;
1079 [self saveLocalState];
1080 dispatch_async(dispatch_get_main_queue(), ^{
1081 [activityFacility startAndEndActivityWithType:PithosActivityOther
1082 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
1083 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
1085 pithosAccount:pithosAccount];
1088 [self syncOperationStarted];
1089 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
1090 containerName:pithosContainer.name
1091 objectName:object.name];
1092 if (![accountName isEqualToString:@""])
1093 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1094 objectRequest.delegate = self;
1095 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1096 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1097 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
1098 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1099 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1100 message:[messagePrefix stringByAppendingString:@" (0%%)"]
1101 totalBytes:object.bytes
1103 pithosAccount:pithosAccount];
1104 dispatch_async(dispatch_get_main_queue(), ^{
1105 [activityFacility updateActivity:activity withMessage:activity.message];
1107 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1108 accountName, @"accountName",
1109 pithosContainer, @"pithosContainer",
1110 object, @"pithosObject",
1111 messagePrefix, @"messagePrefix",
1112 filePath, @"filePath",
1113 activity, @"activity",
1114 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1115 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1116 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1117 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1118 [NSNumber numberWithUnsignedInteger:10], @"retries",
1119 NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector",
1120 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1122 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1128 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
1129 object:(ASIPithosObject *)object
1130 localFilePath:(NSString *)filePath
1131 accountName:(NSString *)accountName
1132 pithosContainer:(ASIPithosContainer *)pithosContainer {
1134 [self syncOperationStarted];
1135 NSFileManager *fileManager = [NSFileManager defaultManager];
1137 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1138 if (currentState.isDirectory) {
1139 // Create remote directory object
1140 if (![accountName isEqualToString:@""]) {
1141 if (!object.allowedTo) {
1142 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1143 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1144 while ([objectAncestorName length] && !object.allowedTo) {
1145 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1146 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1149 if (![object.allowedTo isEqualToString:@"write"]) {
1150 // If read-only "shared with me" skip
1151 [self syncOperationFinishedWithSuccess:YES];
1155 if (!fileExists || !isDirectory) {
1156 // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
1157 [self syncOperationFinishedWithSuccess:NO];
1160 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
1161 containerName:pithosContainer.name
1162 objectName:object.name
1164 contentType:@"application/directory"
1166 contentDisposition:nil
1169 isPublic:ASIPithosObjectRequestPublicIgnore
1171 data:[NSData data]];
1172 if (![accountName isEqualToString:@""])
1173 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1174 objectRequest.delegate = self;
1175 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1176 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1177 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Creating directory '%@/%@'",
1178 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1179 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1180 message:messagePrefix
1181 pithosAccount:pithosAccount];
1182 dispatch_async(dispatch_get_main_queue(), ^{
1183 [activityFacility updateActivity:activity withMessage:activity.message];
1185 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1186 accountName, @"accountName",
1187 pithosContainer, @"pithosContainer",
1188 object, @"pithosObject",
1189 messagePrefix, @"messagePrefix",
1190 activity, @"activity",
1191 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1192 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1193 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1194 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1195 [NSNumber numberWithUnsignedInteger:10], @"retries",
1196 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1197 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1199 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1200 } else if (![currentState exists]) {
1201 // Delete remote object
1202 if (![accountName isEqualToString:@""]) {
1203 // If "shared with me" skip
1204 [self syncOperationFinishedWithSuccess:YES];
1208 // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
1209 syncIncomplete = YES;
1211 if ([pithosContainer.name isEqualToString:@"trash"]) {
1213 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
1214 containerName:pithosContainer.name
1215 objectName:object.name];
1216 objectRequest.delegate = self;
1217 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1218 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1219 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Deleting '%@/%@'",
1220 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1221 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1222 message:messagePrefix
1223 pithosAccount:pithosAccount];
1224 dispatch_async(dispatch_get_main_queue(), ^{
1225 [activityFacility updateActivity:activity withMessage:activity.message];
1227 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1228 accountName, @"accountName",
1229 pithosContainer, @"pithosContainer",
1230 object, @"pithosObject",
1231 messagePrefix, @"messagePrefix",
1232 activity, @"activity",
1233 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1234 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1235 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1236 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1237 [NSNumber numberWithUnsignedInteger:10], @"retries",
1238 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
1239 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1241 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1243 // Move to container trash
1245 if ([PithosUtilities isContentTypeDirectory:object.contentType])
1246 safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
1248 safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
1250 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1251 containerName:pithosContainer.name
1252 objectName:object.name
1253 destinationContainerName:@"trash"
1254 destinationObjectName:safeName
1256 if (objectRequest) {
1257 objectRequest.delegate = self;
1258 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1259 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1260 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'",
1261 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1262 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1263 message:messagePrefix
1264 pithosAccount:pithosAccount];
1265 dispatch_async(dispatch_get_main_queue(), ^{
1266 [activityFacility updateActivity:activity withMessage:activity.message];
1268 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1269 accountName, @"accountName",
1270 pithosContainer, @"pithosContainer",
1271 object, @"pithosObject",
1272 messagePrefix, @"messagePrefix",
1273 activity, @"activity",
1274 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1275 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1276 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1277 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1278 [NSNumber numberWithUnsignedInteger:10], @"retries",
1279 NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector",
1280 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1282 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1284 [self syncOperationFinishedWithSuccess:NO];
1287 [self syncOperationFinishedWithSuccess:NO];
1291 // Upload file to remote object
1292 if (![accountName isEqualToString:@""]) {
1293 if (!object.allowedTo) {
1294 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1295 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1296 while ([objectAncestorName length] && !object.allowedTo) {
1297 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1298 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1301 if (![object.allowedTo isEqualToString:@"write"]) {
1302 // If read-only "shared with me" skip
1303 [self syncOperationFinishedWithSuccess:YES];
1307 if (!fileExists || isDirectory) {
1308 // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
1309 [self syncOperationFinishedWithSuccess:NO];
1312 NSError *error = nil;
1313 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1314 if (object.contentType == nil)
1315 object.contentType = @"application/octet-stream";
1318 DLog(@"contentType detection error: %@", error);
1320 NSArray *hashes = nil;
1321 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1322 containerName:pithosContainer.name
1323 objectName:object.name
1324 contentType:object.contentType
1325 blockSize:pithosContainer.blockSize
1326 blockHash:pithosContainer.blockHash
1330 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
1331 if (objectRequest) {
1332 objectRequest.delegate = self;
1333 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1334 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1335 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Uploading '%@/%@'",
1336 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1337 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1338 message:[messagePrefix stringByAppendingString:@" (0%%)"]
1339 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1341 pithosAccount:pithosAccount];
1342 dispatch_async(dispatch_get_main_queue(), ^{
1343 [activityFacility updateActivity:activity withMessage:activity.message];
1345 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1346 [NSDictionary dictionaryWithObjectsAndKeys:
1347 accountName, @"accountName",
1348 pithosContainer, @"pithosContainer",
1349 object, @"pithosObject",
1350 messagePrefix, @"messagePrefix",
1351 filePath, @"filePath",
1353 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1354 activity, @"activity",
1355 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1356 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1357 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1358 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1359 [NSNumber numberWithUnsignedInteger:10], @"retries",
1360 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1361 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1363 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1365 [self syncOperationFinishedWithSuccess:NO];
1372 #pragma mark ASIHTTPRequestDelegate
1374 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1375 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1376 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1377 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1379 operation.completionBlock = ^{
1381 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1382 dispatch_async(dispatch_get_main_queue(), ^{
1383 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1384 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1386 [self syncOperationFinishedWithSuccess:NO];
1390 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1391 [callbackQueue addOperation:operation];
1394 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1395 if (request.isCancelled) {
1396 // Request has been cancelled
1397 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1398 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1399 withObject:request];
1401 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1402 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1403 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1405 operation.completionBlock = ^{
1407 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1408 dispatch_async(dispatch_get_main_queue(), ^{
1409 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1410 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1412 [self syncOperationFinishedWithSuccess:NO];
1416 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1417 [callbackQueue addOperation:operation];
1421 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1423 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1424 DLog(@"Sync::list request finished: %@", containerRequest.url);
1425 if (operation.isCancelled) {
1426 [self listRequestFailed:containerRequest];
1427 } else if ((containerRequest.responseStatusCode == 200) ||
1428 (containerRequest.responseStatusCode == 304) ||
1429 (containerRequest.responseStatusCode == 403)) {
1430 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
1431 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1432 if (containerRequest.responseStatusCode == 200) {
1433 NSArray *someObjects = [containerRequest objects];
1434 if (objects == nil) {
1435 objects = [[NSMutableArray alloc] initWithArray:someObjects];
1437 [objects addObjectsFromArray:someObjects];
1439 if ([someObjects count] < 10000) {
1440 pithosContainer.blockHash = [containerRequest blockHash];
1441 pithosContainer.blockSize = [containerRequest blockSize];
1442 pithosContainer.lastModified = [containerRequest lastModified];
1443 NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1444 for (ASIPithosObject *object in objects) {
1445 [containerRemoteObjects setObject:object forKey:object.name];
1447 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1450 // Do an additional request to fetch more objects
1451 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1452 containerName:pithosContainer.name
1454 marker:[[someObjects lastObject] name]
1461 ifModifiedSince:pithosContainer.lastModified];
1462 if (![accountName isEqualToString:@""])
1463 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1464 newContainerRequest.delegate = self;
1465 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1466 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1467 newContainerRequest.userInfo = containerRequest.userInfo;
1468 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1469 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1472 } else if (containerRequest.responseStatusCode == 304) {
1473 NSMutableDictionary *containerRemoteObjects = [[previousRemoteObjects objectForKey:accountName]
1474 objectForKey:pithosContainer.name];
1475 if (containerRemoteObjects)
1476 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1477 } else if (containerRequest.responseStatusCode == 403) {
1478 [[remoteObjects objectForKey:accountName] setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
1482 if (containersIndex == [[accountsPithosContainers objectForKey:accountName] count]) {
1484 containersIndex = 0;
1486 if (accountsIndex < accountsCount) {
1487 accountName = [accountsNames objectAtIndex:accountsIndex];
1488 pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1489 // Do a request for the next container
1490 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1491 containerName:pithosContainer.name
1500 ifModifiedSince:pithosContainer.lastModified];
1501 if (![accountName isEqualToString:@""])
1502 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1503 newContainerRequest.delegate = self;
1504 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1505 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1506 newContainerRequest.userInfo = containerRequest.userInfo;
1507 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1508 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1511 self.previousRemoteObjects = remoteObjects;
1512 // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
1514 if (operation.isCancelled) {
1515 [self listRequestFailed:containerRequest];
1519 dispatch_async(dispatch_get_main_queue(), ^{
1520 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1521 withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1523 NSFileManager *fileManager = [NSFileManager defaultManager];
1525 // Compute current state of legal existing local objects
1526 // and add an empty stored state for legal new local objects since last sync
1527 self.currentLocalObjectStates = [NSMutableDictionary dictionary];
1528 for (NSString *accountName in accountsNames) {
1529 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1530 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1531 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1532 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1533 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1534 objectForKey:pithosContainer.name];
1535 NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath];
1536 for (__strong NSString *objectName in dirEnumerator) {
1537 objectName = [objectName precomposedStringWithCanonicalMapping];
1538 if (operation.isCancelled) {
1539 operation.completionBlock = nil;
1540 [self saveLocalState];
1541 [self syncOperationFinishedWithSuccess:NO];
1545 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1546 NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1548 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1549 if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) {
1550 [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error"
1551 message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.",
1552 containerDirectoryPath, pithosAccount.name]
1554 pithosAccount.syncActive = NO;
1556 } else if (fileExists) {
1557 if (skipHidden && [[objectName lastPathComponent] hasPrefix:@"."]) {
1558 // Skip hidden directory and its descendants, or hidden file
1560 [dirEnumerator skipDescendants];
1561 // Remove stored state if any
1562 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1564 } else if ([[objectName pathComponents] count] == 1) {
1565 if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) {
1566 // Skip excluded directory and its descendants, or root file with same name
1568 [dirEnumerator skipDescendants];
1569 // Remove stored state if any
1570 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1572 } else if (!isDirectory && containerExcludeRootFiles) {
1573 // Skip excluded root file
1574 // Remove stored state if any
1575 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1579 // Include local object
1580 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
1581 if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1582 // New or modified existing local object, compute current state
1583 if (!storedLocalObjectState)
1584 // For new local object, also create empty stored state
1585 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1586 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
1587 blockHash:pithosContainer.blockHash
1588 blockSize:pithosContainer.blockSize]
1591 // Local object hasn't changed, set stored state also to current
1592 [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1596 [self saveLocalState];
1600 if (operation.isCancelled) {
1601 operation.completionBlock = nil;
1602 [self syncOperationFinishedWithSuccess:NO];
1606 // Add an empty stored state for legal new remote objects since last sync
1607 for (NSString *accountName in accountsNames) {
1608 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1609 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1610 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1611 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1612 objectForKey:pithosContainer.name];
1613 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1614 for (NSString *objectName in containerRemoteObjects) {
1615 if (operation.isCancelled) {
1616 operation.completionBlock = nil;
1617 [self saveLocalState];
1618 [self syncOperationFinishedWithSuccess:NO];
1622 ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName];
1623 NSString *localObjectName;
1624 if ([object.name hasSuffix:@"/"])
1625 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1627 localObjectName = [NSString stringWithString:object.name];
1628 NSArray *pathComponents = [localObjectName pathComponents];
1630 BOOL skipObject = NO;
1631 for (NSString *pathComponent in pathComponents) {
1632 if ([pathComponent hasPrefix:@"."]) {
1633 // Skip hidden directory and its descendants, or hidden file
1634 // Remove stored state if any
1635 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1643 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1644 // Skip excluded directory object and its descendants, or root file object with same name
1645 // Remove stored state if any
1646 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1648 } else if (containerExcludeRootFiles &&
1649 ([pathComponents count] == 1) &&
1650 ![PithosUtilities isContentTypeDirectory:object.contentType]) {
1651 // Skip root file object
1652 // Remove stored state if any
1653 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1656 if (![containerStoredLocalObjectStates objectForKey:object.name])
1657 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name];
1659 [self saveLocalState];
1663 if (operation.isCancelled) {
1664 operation.completionBlock = nil;
1665 [self syncOperationFinishedWithSuccess:NO];
1669 // For each stored state compare with current and remote state
1670 // Stored states of local objects that have been deleted,
1671 // haven't been checked for legality (only existing local remote objects)
1672 // These should be identified and skipped
1673 for (NSString *accountName in accountsNames) {
1674 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1675 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1676 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1677 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1678 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1679 objectForKey:pithosContainer.name];
1680 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1681 for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1682 if (operation.isCancelled) {
1683 operation.completionBlock = nil;
1684 [self syncOperationFinishedWithSuccess:NO];
1688 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1689 if ([objectName hasSuffix:@"/"])
1690 filePath = [filePath stringByAppendingString:@":"];
1691 ASIPithosObject *object = [ASIPithosObject object];
1692 object.name = objectName;
1693 DLog(@"Sync::object name: %@", object.name);
1695 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
1696 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1697 if (!currentLocalObjectState) {
1698 // The stored state corresponds to a remote or deleted local object, that's why there is no current state
1699 // In the latter case it must be checked for legality, which can be determined by its stored state
1700 // If it existed locally, but was deleted, state.exists is true,
1701 // else if the stored state is an empty state that was created due to the server object, state.exists is false
1702 if (storedLocalObjectState.exists) {
1703 NSString *localObjectName;
1704 if ([object.name hasSuffix:@"/"])
1705 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1707 localObjectName = [NSString stringWithString:object.name];
1708 NSArray *pathComponents = [localObjectName pathComponents];
1710 BOOL skipObject = NO;
1711 for (NSString *pathComponent in pathComponents) {
1712 if ([pathComponent hasPrefix:@"."]) {
1713 // Skip hidden directory and its descendants, or hidden file
1714 // Remove stored state if any
1715 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1716 [self saveLocalState];
1724 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1725 // Skip excluded directory object and its descendants, or root file object with same name
1726 // Remove stored state
1727 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1728 [self saveLocalState];
1730 } else if (containerExcludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) {
1731 // Skip root file object
1732 // Remove stored state
1733 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1734 [self saveLocalState];
1738 // There is also the off case that a local object has been created in the meantime
1739 // This call works in any case, existent or non-existent local object
1740 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
1741 blockHash:pithosContainer.blockHash
1742 blockSize:pithosContainer.blockSize];
1745 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1746 ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
1748 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1749 remoteObjectState.isDirectory = YES;
1751 remoteObjectState.hash = remoteObject.objectHash;
1755 BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1756 BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1757 DLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1758 if (!localStateHasChanged) {
1759 // Local state hasn't changed
1760 if (serverStateHasChanged) {
1761 // Server state has changed
1762 // Update local state to match that of the server
1763 object.bytes = remoteObject.bytes;
1764 object.version = remoteObject.version;
1765 object.contentType = remoteObject.contentType;
1766 object.objectHash = remoteObject.objectHash;
1767 [self updateLocalStateWithObject:object localFilePath:filePath
1768 accountName:accountName pithosContainer:pithosContainer];
1769 } else if (!remoteObject && ![currentLocalObjectState exists]) {
1770 // Server state hasn't changed
1771 // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1772 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1773 [self saveLocalState];
1776 // Local state has changed
1777 if (!serverStateHasChanged) {
1778 // Server state hasn't changed
1779 if (currentLocalObjectState.isDirectory)
1780 object.contentType = @"application/directory";
1782 object.objectHash = currentLocalObjectState.hash;
1783 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1784 accountName:accountName pithosContainer:pithosContainer];
1786 // Server state has also changed
1787 if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1788 // Both did the same change (directory)
1789 storedLocalObjectState.filePath = filePath;
1790 storedLocalObjectState.isDirectory = YES;
1791 [self saveLocalState];
1792 } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1793 // Both did the same change (object edit or delete)
1794 if (![remoteObjectState exists]) {
1795 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1797 storedLocalObjectState.filePath = filePath;
1798 storedLocalObjectState.hash = remoteObjectState.hash;
1800 [self saveLocalState];
1802 // Conflict, we ask the user which change to keep
1803 NSString *informativeText;
1804 NSString *firstButtonText;
1805 NSString *secondButtonText;
1807 if (![remoteObjectState exists]) {
1808 // Remote object has been deleted
1809 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.",
1810 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name ];
1811 firstButtonText = @"Delete local file";
1812 secondButtonText = @"Upload file to server";
1813 } else if (![currentLocalObjectState exists]) {
1814 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.",
1815 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1816 firstButtonText = @"Download file from server";
1817 secondButtonText = @"Delete file on server";
1819 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.",
1820 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1821 firstButtonText = @"Keep server version";
1822 secondButtonText = @"Keep local version";
1824 __block NSInteger choice;
1825 dispatch_sync(dispatch_get_main_queue(), ^{
1826 NSAlert *alert = [[NSAlert alloc] init];
1827 [alert setMessageText:@"Conflict"];
1828 [alert setInformativeText:informativeText];
1829 [alert addButtonWithTitle:firstButtonText];
1830 [alert addButtonWithTitle:secondButtonText];
1831 [alert addButtonWithTitle:@"Do nothing"];
1832 choice = [alert runModal];
1834 if (choice == NSAlertFirstButtonReturn) {
1835 object.bytes = remoteObject.bytes;
1836 object.version = remoteObject.version;
1837 object.contentType = remoteObject.contentType;
1838 object.objectHash = remoteObject.objectHash;
1839 [self updateLocalStateWithObject:object localFilePath:filePath
1840 accountName:accountName pithosContainer:pithosContainer];
1841 } if (choice == NSAlertSecondButtonReturn) {
1842 if (currentLocalObjectState.isDirectory)
1843 object.contentType = @"application/directory";
1845 object.objectHash = currentLocalObjectState.hash;
1846 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1847 accountName:accountName pithosContainer:pithosContainer];
1855 [self syncOperationFinishedWithSuccess:YES];
1857 [self listRequestFailed:containerRequest];
1862 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1864 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1865 if (operation.isCancelled) {
1869 if (containerRequest.isCancelled) {
1870 dispatch_async(dispatch_get_main_queue(), ^{
1871 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1872 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1875 [self syncOperationFinishedWithSuccess:NO];
1878 // If the server listing fails, the sync should start over, so just retrying is enough
1879 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1881 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
1882 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1883 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1885 dispatch_async(dispatch_get_main_queue(), ^{
1886 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1887 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1890 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1891 [self syncOperationFinishedWithSuccess:NO];
1896 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1898 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1899 DLog(@"Sync::download object block finished: %@", objectRequest.url);
1900 if (operation.isCancelled) {
1901 [self requestFailed:objectRequest];
1902 } else if (objectRequest.responseStatusCode == 206) {
1903 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1904 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1905 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1906 NSFileManager *fileManager = [NSFileManager defaultManager];
1908 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1910 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1911 objectForKey:pithosContainer.name]
1912 objectForKey:object.name];
1913 if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1914 NSString *downloadsDirPath = self.tempDownloadsDirPath;
1915 if (!downloadsDirPath) {
1916 dispatch_async(dispatch_get_main_queue(), ^{
1917 [activityFacility endActivity:activity
1918 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1920 [self syncOperationFinishedWithSuccess:NO];
1923 if ([accountName isEqualToString:@""]) {
1924 downloadsDirPath = [downloadsDirPath stringByAppendingPathComponent:pithosContainer.name];
1926 downloadsDirPath = [[[downloadsDirPath stringByAppendingPathComponent:@"shared with me"]
1927 stringByAppendingPathComponent:accountName]
1928 stringByAppendingPathComponent:pithosContainer.name];
1932 if (![fileManager fileExistsAtPath:downloadsDirPath isDirectory:&isDirectory]) {
1933 if (![fileManager createDirectoryAtPath:downloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
1934 dispatch_async(dispatch_get_main_queue(), ^{
1935 [activityFacility endActivity:activity
1936 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1938 [self syncOperationFinishedWithSuccess:NO];
1941 } else if (!isDirectory) {
1942 dispatch_async(dispatch_get_main_queue(), ^{
1943 [activityFacility endActivity:activity
1944 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1946 [self syncOperationFinishedWithSuccess:NO];
1950 NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1951 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1952 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1953 strcpy(tempFileNameCString, tempFileTemplateCString);
1954 int fileDescriptor = mkstemp(tempFileNameCString);
1955 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1956 free(tempFileNameCString);
1957 if (fileDescriptor == -1) {
1958 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
1959 message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]
1961 dispatch_async(dispatch_get_main_queue(), ^{
1962 [activityFacility endActivity:activity
1963 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1965 [self syncOperationFinishedWithSuccess:NO];
1968 close(fileDescriptor);
1969 storedState.tmpFilePath = tempFilePath;
1970 [self saveLocalState];
1973 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1974 NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1975 [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
1976 [tempFileHandle writeData:[objectRequest responseData]];
1977 [tempFileHandle closeFile];
1979 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1980 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1981 if (missingBlockIndex == NSNotFound) {
1982 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1983 NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1984 if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath
1985 accountName:accountName
1986 pithosContainer:pithosContainer]) {
1987 dispatch_async(dispatch_get_main_queue(), ^{
1988 [activityFacility endActivity:activity
1989 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1991 [self syncOperationFinishedWithSuccess:NO];
1993 } else if (![fileManager fileExistsAtPath:dirPath]) {
1994 // File doesn't exist but also the containing directory doesn't exist
1995 // In most cases this should have been resolved as an update of the corresponding local object,
1996 // but it never hurts to check
1998 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
2000 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
2001 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
2003 dispatch_async(dispatch_get_main_queue(), ^{
2004 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2005 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
2007 [self syncOperationFinishedWithSuccess:NO];
2011 // Move file from tmp download
2013 [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
2015 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
2016 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath]
2018 dispatch_async(dispatch_get_main_queue(), ^{
2019 [activityFacility endActivity:activity
2020 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
2022 [self syncOperationFinishedWithSuccess:NO];
2025 dispatch_async(dispatch_get_main_queue(), ^{
2026 [activityFacility endActivity:activity
2027 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
2028 totalBytes:activity.totalBytes
2029 currentBytes:activity.totalBytes];
2032 storedState.filePath = filePath;
2033 storedState.hash = object.objectHash;
2034 storedState.tmpFilePath = nil;
2035 [self saveLocalState];
2036 [self syncOperationFinishedWithSuccess:YES];
2039 if (newSyncRequested || syncLate || operation.isCancelled) {
2040 [self requestFailed:objectRequest];
2042 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
2043 containerName:pithosContainer.name
2045 blockIndex:missingBlockIndex
2046 blockSize:pithosContainer.blockSize];
2047 if (![accountName isEqualToString:@""])
2048 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
2049 newObjectRequest.delegate = self;
2050 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2051 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2052 newObjectRequest.userInfo = objectRequest.userInfo;
2053 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2054 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2055 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
2056 [activityFacility updateActivity:activity
2057 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2058 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
2059 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2060 totalBytes:activity.totalBytes
2061 currentBytes:(activity.currentBytes + size)];
2063 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2066 } else if (objectRequest.responseStatusCode == 412) {
2067 // The object has changed on the server
2068 dispatch_async(dispatch_get_main_queue(), ^{
2069 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2070 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
2072 [self syncOperationFinishedWithSuccess:NO];
2074 [self requestFailed:objectRequest];
2079 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
2081 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2082 DLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
2083 if (operation.isCancelled) {
2084 [self requestFailed:objectRequest];
2085 } else if (objectRequest.responseStatusCode == 200) {
2086 if (newSyncRequested || syncLate || operation.isCancelled) {
2087 [self requestFailed:objectRequest];
2089 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
2090 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
2091 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
2092 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
2093 objectForKey:pithosContainer.name]
2094 objectForKey:object.name];
2095 if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
2096 [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
2097 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
2098 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath
2099 blockSize:pithosContainer.blockSize
2100 blockHash:pithosContainer.blockHash
2101 withHashes:[objectRequest hashes]];
2102 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2103 dispatch_async(dispatch_get_main_queue(), ^{
2104 [activityFacility updateActivity:activity
2105 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2106 [objectRequest.userInfo objectForKey:@"messagePrefix"],
2107 (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))]
2108 totalBytes:activity.totalBytes
2109 currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
2112 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
2113 containerName:pithosContainer.name
2115 blockIndex:missingBlockIndex
2116 blockSize:pithosContainer.blockSize];
2117 if (![accountName isEqualToString:@""])
2118 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
2119 newObjectRequest.delegate = self;
2120 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2121 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2122 newObjectRequest.userInfo = objectRequest.userInfo;
2123 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2124 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2125 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2126 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
2127 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
2128 [activityFacility updateActivity:activity
2129 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2130 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
2131 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2132 totalBytes:activity.totalBytes
2133 currentBytes:(activity.currentBytes + size)];
2135 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2138 [self requestFailed:objectRequest];
2143 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2145 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2146 DLog(@"Sync::upload directory object finished: %@", objectRequest.url);
2147 if (operation.isCancelled) {
2148 [self requestFailed:objectRequest];
2149 } else if (objectRequest.responseStatusCode == 201) {
2150 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2151 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2152 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2153 storedState.isDirectory = YES;
2154 [self saveLocalState];
2155 dispatch_async(dispatch_get_main_queue(), ^{
2156 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2157 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2159 [self syncOperationFinishedWithSuccess:YES];
2161 [self requestFailed:objectRequest];
2166 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
2168 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2169 DLog(@"Sync::move object to trash finished: %@", objectRequest.url);
2170 if (operation.isCancelled) {
2171 [self requestFailed:objectRequest];
2172 } else if (objectRequest.responseStatusCode == 201) {
2173 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2174 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2175 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2176 [self saveLocalState];
2177 dispatch_async(dispatch_get_main_queue(), ^{
2178 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2179 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2181 [self syncOperationFinishedWithSuccess:YES];
2183 [self requestFailed:objectRequest];
2188 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2190 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2191 DLog(@"Sync::delete object finished: %@", objectRequest.url);
2192 if (operation.isCancelled) {
2193 [self requestFailed:objectRequest];
2194 } else if (objectRequest.responseStatusCode == 204) {
2195 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2196 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2197 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2198 [self saveLocalState];
2199 dispatch_async(dispatch_get_main_queue(), ^{
2200 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2201 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2203 [self syncOperationFinishedWithSuccess:YES];
2205 [self requestFailed:objectRequest];
2210 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
2212 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2213 DLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
2214 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
2215 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
2216 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
2217 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
2218 objectForKey:pithosContainer.name]
2219 objectForKey:object.name];
2220 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
2221 NSUInteger totalBytes = activity.totalBytes;
2222 NSUInteger currentBytes = activity.currentBytes;
2223 if (operation.isCancelled) {
2224 [self requestFailed:objectRequest];
2225 } else if (objectRequest.responseStatusCode == 201) {
2226 DLog(@"Sync::object created: %@", objectRequest.url);
2227 storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
2228 storedState.hash = object.objectHash;
2229 [self saveLocalState];
2230 dispatch_async(dispatch_get_main_queue(), ^{
2231 [activityFacility endActivity:activity
2232 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
2233 totalBytes:totalBytes
2234 currentBytes:totalBytes];
2236 [self syncOperationFinishedWithSuccess:YES];
2237 } else if (objectRequest.responseStatusCode == 409) {
2238 if (newSyncRequested || syncLate || operation.isCancelled) {
2239 [self requestFailed:objectRequest];
2241 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
2242 if (iteration == 0) {
2243 DLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
2244 dispatch_async(dispatch_get_main_queue(), ^{
2245 [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
2247 [self syncOperationFinishedWithSuccess:NO];
2250 DLog(@"Sync::object is missing hashes: %@", objectRequest.url);
2251 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
2252 withMissingHashes:[objectRequest hashes]];
2253 if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
2254 currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
2255 dispatch_async(dispatch_get_main_queue(), ^{
2256 [activityFacility updateActivity:activity
2257 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2258 [objectRequest.userInfo objectForKey:@"messagePrefix"],
2259 (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
2260 totalBytes:totalBytes
2261 currentBytes:currentBytes];
2263 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2264 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2265 containerName:pithosContainer.name
2266 blockSize:pithosContainer.blockSize
2267 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
2268 missingBlockIndex:missingBlockIndex
2269 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2270 newContainerRequest.delegate = self;
2271 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2272 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2273 newContainerRequest.userInfo = objectRequest.userInfo;
2274 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
2275 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2276 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2277 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2278 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
2279 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2280 [activityFacility updateActivity:activity
2281 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2282 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2283 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2284 totalBytes:activity.totalBytes
2285 currentBytes:(activity.currentBytes + size)];
2287 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2290 [self requestFailed:objectRequest];
2295 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
2297 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
2298 DLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
2299 if (operation.isCancelled) {
2300 [self requestFailed:containerRequest];
2301 } else if (containerRequest.responseStatusCode == 202) {
2302 NSString *accountName = [containerRequest.userInfo objectForKey:@"accountName"];
2303 ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
2304 ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
2305 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
2306 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
2307 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
2308 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
2309 if (operation.isCancelled) {
2310 [self requestFailed:containerRequest];
2311 } else if (missingBlockIndex == NSNotFound) {
2312 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
2313 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
2314 containerName:pithosContainer.name
2315 objectName:object.name
2316 contentType:object.contentType
2317 blockSize:pithosContainer.blockSize
2318 blockHash:pithosContainer.blockHash
2319 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2322 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2323 newObjectRequest.delegate = self;
2324 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2325 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2326 newObjectRequest.userInfo = containerRequest.userInfo;
2327 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2328 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
2329 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
2330 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
2331 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2333 if (newSyncRequested || syncLate || operation.isCancelled) {
2334 [self requestFailed:containerRequest];
2336 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2337 containerName:pithosContainer.name
2338 blockSize:pithosContainer.blockSize
2339 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2340 missingBlockIndex:missingBlockIndex
2341 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2342 newContainerRequest.delegate = self;
2343 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2344 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2345 newContainerRequest.userInfo = containerRequest.userInfo;
2346 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2347 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2348 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2349 [activityFacility updateActivity:activity
2350 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2351 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2352 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2353 totalBytes:activity.totalBytes
2354 currentBytes:(activity.currentBytes + size)];
2356 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2360 [self requestFailed:containerRequest];
2365 - (void)requestFailed:(ASIPithosRequest *)request {
2367 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
2368 DLog(@"Sync::request failed: %@", request.url);
2369 if (operation.isCancelled)
2371 if (request.isCancelled || newSyncRequested || syncLate) {
2372 dispatch_async(dispatch_get_main_queue(), ^{
2373 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2374 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
2376 [self syncOperationFinishedWithSuccess:NO];
2379 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
2381 ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
2382 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
2383 [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
2385 dispatch_async(dispatch_get_main_queue(), ^{
2386 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2387 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
2389 [self syncOperationFinishedWithSuccess:NO];