5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
15 // 2. Redistributions in binary form must reproduce the above
16 // copyright notice, this list of conditions and the following
17 // disclaimer in the documentation and/or other materials
18 // provided with the distribution.
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
38 #import "PithosSyncDaemon.h"
39 #import "PithosAccount.h"
40 #import "PithosLocalObjectState.h"
41 #import "PithosActivityFacility.h"
42 #import "PithosUtilities.h"
43 #import "ASINetworkQueue.h"
44 #import "ASIPithosRequest.h"
46 #import "ASIPithosContainer.h"
47 #import "ASIPithosContainerRequest.h"
48 #import "ASIPithosObjectRequest.h"
49 #import "ASIPithosObject.h"
51 @interface PithosSyncDaemon (Private)
52 - (void)loadLocalState;
53 - (void)resetLocalStateWithAll:(BOOL)all;
54 - (void)saveLocalState;
56 - (BOOL)createSyncDirectory:(NSString *)dirPath;
57 - (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName;
58 - (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName;
60 - (BOOL)moveToTempTrashFile:(NSString *)filePath
61 accountName:(NSString *)accountName
62 pithosContainer:(ASIPithosContainer *)pithosContainer;
63 - (void)emptyTempTrash;
64 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
66 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
67 localFilePath:(NSString *)filePath
68 accountName:(NSString *)accountName
69 pithosContainer:(ASIPithosContainer *)pithosContainer;
70 - (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
71 object:(ASIPithosObject *)object
72 localFilePath:(NSString *)filePath
73 accountName:(NSString *)accountName
74 pithosContainer:(ASIPithosContainer *)pithosContainer;
75 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest;
76 - (void)requestFailed:(ASIPithosRequest *)request;
78 - (void)syncOperationStarted;
79 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull;
83 @implementation PithosSyncDaemon
84 @synthesize directoryPath, accountsDictionary, skipHidden, pithos;
85 @synthesize accountsNames, accountsPithosContainers;
86 @synthesize lastCompletedSync, remoteObjects, previousRemoteObjects, storedLocalObjectStates, currentLocalObjectStates;
87 @synthesize pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
90 #pragma Object Lifecycle
92 - (id)initWithDirectoryPath:(NSString *)aDirectoryPath
93 pithosAccount:(PithosAccount *)aPithosAccount
94 accountsDictionary:(NSDictionary *)anAccountsDictionary
95 skipHidden:(BOOL)aSkipHidden
96 resetLocalState:(BOOL)resetLocalState {
97 if ((self = [super init])) {
98 directoryPath = [aDirectoryPath copy];
99 pithosAccount = aPithosAccount;
100 self.accountsDictionary = anAccountsDictionary;
101 skipHidden = aSkipHidden;
102 self.pithos = pithosAccount.pithos;
104 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
107 [self resetLocalStateWithAll:YES];
109 [self resetLocalStateWithAll:NO];
111 networkQueue = [[ASINetworkQueue alloc] init];
112 networkQueue.showAccurateProgress = YES;
113 networkQueue.shouldCancelAllRequestsOnFailure = NO;
114 // networkQueue.maxConcurrentOperationCount = 1;
116 callbackQueue = [[NSOperationQueue alloc] init];
117 [callbackQueue setSuspended:YES];
118 callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
119 // callbackQueue.maxConcurrentOperationCount = 1;
121 [[NSNotificationCenter defaultCenter] addObserver:self
122 selector:@selector(applicationWillTerminate:)
123 name:NSApplicationWillTerminateNotification
124 object:[NSApplication sharedApplication]];
129 - (void)loadLocalState {
131 if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath])
132 self.storedLocalObjectStates = [NSKeyedUnarchiver unarchiveObjectWithFile:self.pithosStateFilePath];
134 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
135 if (!storedLocalObjectStates)
136 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
137 for (NSString *accountName in accountsNames) {
138 NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
139 if (!accountStoredLocalObjectStates) {
140 accountStoredLocalObjectStates = [NSMutableDictionary dictionary];
141 [storedLocalObjectStates setObject:accountStoredLocalObjectStates forKey:accountName];
143 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
144 if (![accountStoredLocalObjectStates objectForKey:pithosContainer.name])
145 [accountStoredLocalObjectStates setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
151 - (void)resetLocalStateWithAll:(BOOL)all {
153 self.lastCompletedSync = nil;
155 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
156 [self saveLocalState]; // Save an empty dictionary
157 [self loadLocalState]; // Load to populate with containers
158 [self saveLocalState]; // Save again
159 if (self.tempDownloadsDirPath)
160 [PithosUtilities removeContentsAtPath:self.tempDownloadsDirPath];
162 // Remove containers that don't interest us anymore and save
163 if (!storedLocalObjectStates)
164 [self loadLocalState];
165 for (NSString *accountName in storedLocalObjectStates) {
166 NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
167 NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
168 if (!containersDictionary) {
169 if (self.tempDownloadsDirPath) {
170 if ([accountName isEqualToString:@""]) {
171 for (NSString *containerName in accountStoredLocalObjectStates) {
172 [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]];
175 [PithosUtilities removeContentsAtPath:[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared with me"]
176 stringByAppendingPathComponent:accountName]];
179 [storedLocalObjectStates removeObjectForKey:accountName];
181 // Check the account's containers
182 for (NSString *containerName in accountStoredLocalObjectStates) {
183 if (![containersDictionary objectForKey:containerName]) {
184 if ([accountName isEqualToString:@""])
185 [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]];
187 [PithosUtilities removeContentsAtPath:[[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared with me"]
188 stringByAppendingPathComponent:accountName]
189 stringByAppendingPathComponent:containerName]];
190 [accountStoredLocalObjectStates removeObjectForKey:containerName];
195 [self saveLocalState];
200 - (void)saveLocalState {
202 [NSKeyedArchiver archiveRootObject:storedLocalObjectStates toFile:self.pithosStateFilePath];
206 - (void)resetDaemon {
207 @synchronized(self) {
212 [networkQueue reset];
213 [callbackQueue cancelAllOperations];
214 [callbackQueue setSuspended:YES];
215 [self emptyTempTrash];
217 syncOperationCount = 0;
219 @synchronized(self) {
224 - (void)startDaemon {
225 @synchronized(self) {
230 // In the improbable case of leftover operations
231 [networkQueue reset];
232 [callbackQueue cancelAllOperations];
234 syncOperationCount = 0;
235 newSyncRequested = NO;
239 [self loadLocalState];
242 [callbackQueue setSuspended:NO];
244 @synchronized(self) {
250 [[NSNotificationCenter defaultCenter] removeObserver:self];
255 #pragma mark Observers
257 - (void)applicationWillTerminate:(NSNotification *)notification {
258 [self saveLocalState];
262 #pragma mark Properties
264 - (NSString *)pithosStateFilePath {
265 if (!pithosStateFilePath) {
266 pithosStateFilePath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
267 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
268 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive",
269 pithosAccount.uniqueName]];
270 NSString *pithosStateFileDirPath = [pithosStateFilePath stringByDeletingLastPathComponent];
271 NSFileManager *fileManager = [NSFileManager defaultManager];
273 BOOL fileExists = [fileManager fileExistsAtPath:pithosStateFileDirPath isDirectory:&isDirectory];
274 NSError *error = nil;
275 if (fileExists && !isDirectory)
276 [fileManager removeItemAtPath:pithosStateFileDirPath error:&error];
277 if (!error && !fileExists)
278 [fileManager createDirectoryAtPath:pithosStateFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
280 // pithosStateFilePath = nil;
281 // XXX create a dir using mktmps?
283 return [pithosStateFilePath copy];
286 - (NSString *)tempDownloadsDirPath {
287 if (!tempDownloadsDirPath) {
288 tempDownloadsDirPath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
289 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
290 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads",
291 pithosAccount.uniqueName]];
292 NSFileManager *fileManager = [NSFileManager defaultManager];
294 BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
295 NSError *error = nil;
296 if (fileExists && !isDirectory)
297 [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
298 if (!error && !fileExists)
299 [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
301 // tempDownloadsDirPath = nil;
302 // XXX create a dir using mktmps?
304 return tempDownloadsDirPath;
307 - (NSString *)tempTrashDirPath {
308 if (!tempTrashDirPath) {
309 tempTrashDirPath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
310 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
311 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash",
312 pithosAccount.uniqueName]];
313 NSFileManager *fileManager = [NSFileManager defaultManager];
315 BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
316 NSError *error = nil;
317 if (fileExists && !isDirectory)
318 [fileManager removeItemAtPath:tempTrashDirPath error:&error];
319 if (!error && !fileExists)
320 [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
322 // tempTrashDirPath = nil;
323 // XXX create a dir using mktmps?
325 return tempTrashDirPath;
328 - (void)setDirectoryPath:(NSString *)aDirectoryPath {
329 if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
331 [self resetLocalStateWithAll:YES];
332 directoryPath = [aDirectoryPath copy];
336 - (void)setAccountsDictionary:(NSDictionary *)anAccountsDictionary {
337 if (anAccountsDictionary && ![anAccountsDictionary isEqualToDictionary:accountsDictionary]) {
338 BOOL reset = (accountsDictionary != nil);
342 accountsDictionary = [anAccountsDictionary copy];
344 accountsCount = [accountsDictionary count];
345 self.accountsNames = [NSMutableArray arrayWithCapacity:accountsCount];
346 self.accountsPithosContainers = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
347 NSDictionary *containersDictionary = [accountsDictionary objectForKey:@""];
348 if (containersDictionary && [containersDictionary count]) {
349 NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
350 for (NSString *containerName in containersDictionary) {
351 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
352 pithosContainer.name = containerName;
353 [pithosContainers addObject:pithosContainer];
355 [accountsNames addObject:@""];
356 [accountsPithosContainers setObject:pithosContainers forKey:@""];
358 for (NSString *accountName in accountsDictionary) {
359 NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
360 if (![accountsNames containsObject:accountName] && [containersDictionary count]) {
361 NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
362 for (NSString *containerName in containersDictionary) {
363 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
364 pithosContainer.name = containerName;
365 [pithosContainers addObject:pithosContainer];
367 [accountsNames addObject:accountName];
368 [accountsPithosContainers setObject:pithosContainers forKey:accountName];
373 [self resetLocalStateWithAll:NO];
377 - (void)setPithos:(ASIPithos *)aPithos {
379 pithos = [ASIPithos pithos];
380 pithos.authUser = [aPithos.authUser copy];
381 pithos.authToken = [aPithos.authToken copy];
382 pithos.storageURLPrefix = [aPithos.storageURLPrefix copy];
383 pithos.authURL = [aPithos.authURL copy];
384 pithos.publicURLPrefix = [aPithos.publicURLPrefix copy];
387 (![aPithos.authUser isEqualToString:pithos.authUser] ||
388 ![aPithos.authToken isEqualToString:pithos.authToken] ||
389 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
391 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
392 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
393 [self resetLocalStateWithAll:YES];
394 pithos.authUser = [aPithos.authUser copy];
395 pithos.authToken = [aPithos.authToken copy];
396 pithos.storageURLPrefix = [aPithos.storageURLPrefix copy];
397 pithos.authURL = [aPithos.authURL copy];
398 pithos.publicURLPrefix = [aPithos.publicURLPrefix copy];
403 #pragma mark Helper Methods
405 - (BOOL)createSyncDirectory:(NSString *)dirPath {
406 NSFileManager *fileManager = [NSFileManager defaultManager];
408 NSError *error = nil;
409 if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory]) {
410 if (![fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
411 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
412 message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'",
417 } else if (!isDirectory) {
418 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
419 message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'",
427 - (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
428 if ([accountName isEqualToString:@""])
429 return [directoryPath stringByAppendingPathComponent:containerName];
431 return [[[directoryPath stringByAppendingPathComponent:@"shared with me"]
432 stringByAppendingPathComponent:accountName]
433 stringByAppendingPathComponent:containerName];
436 - (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
437 if ([accountName isEqualToString:@""])
438 return containerName;
440 return [[@"shared with me" stringByAppendingPathComponent:accountName] stringByAppendingPathComponent:containerName];
446 - (void)syncOperationStarted {
447 @synchronized(self) {
448 syncOperationCount++;
452 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
453 @synchronized(self) {
454 if (!operationSuccessfull)
455 syncIncomplete = YES;
456 if (syncOperationCount == 0) {
457 // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
458 DLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
461 syncOperationCount--;
462 if (syncOperationCount == 0) {
463 if (!syncIncomplete) {
464 self.lastCompletedSync = [NSDate date];
465 dispatch_async(dispatch_get_main_queue(), ^{
466 [activityFacility startAndEndActivityWithType:PithosActivityOther
467 message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]
468 pithosAccount:pithosAccount];
472 [self emptyTempTrash];
473 if (newSyncRequested && daemonActive)
480 @synchronized(self) {
481 return ((syncOperationCount > 0) && daemonActive);
486 @synchronized(self) {
487 if ([self isSyncing])
493 @synchronized(self) {
494 if ([self isSyncing]) {
495 // If at least one operation is running return
496 newSyncRequested = YES;
498 } else if (daemonActive && accountsCount) {
499 // The first operation is the server listing
500 [self syncOperationStarted];
501 newSyncRequested = NO;
509 if (![self createSyncDirectory:directoryPath]) {
510 [self syncOperationFinishedWithSuccess:NO];
513 for (NSString *accountName in accountsNames) {
514 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
515 if (![self createSyncDirectory:[self dirPathForAccount:accountName container:pithosContainer.name]]) {
516 [self syncOperationFinishedWithSuccess:NO];
522 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
523 for (NSString *accountName in accountsNames) {
524 [remoteObjects setObject:[NSMutableDictionary dictionary] forKey:accountName];
528 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
529 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
530 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
531 containerName:pithosContainer.name
540 ifModifiedSince:pithosContainer.lastModified];
541 if (![accountName isEqualToString:@""])
542 [containerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
543 containerRequest.delegate = self;
544 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
545 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
546 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther
547 message:@"Sync: Getting server listing"
548 pithosAccount:pithosAccount];
549 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
550 activity, @"activity",
551 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage",
552 @"Sync: Getting server listing (failed)", @"failedActivityMessage",
553 @"Sync: Getting server listing (finished)", @"finishedActivityMessage",
554 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
555 [NSNumber numberWithUnsignedInteger:10], @"retries",
556 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector",
557 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector",
559 [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
562 - (void)emptyTempTrash {
564 NSString *trashDirPath = self.tempTrashDirPath;
566 NSFileManager *fileManager = [NSFileManager defaultManager];
567 NSError *error = nil;
568 // NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
569 NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
571 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
572 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath]
576 if ([subPaths count]) {
577 // NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
578 // for (NSString *subPath in subPaths) {
579 // [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
581 // [self syncOperationStarted];
582 // [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
584 // [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
585 // message:@"Cannot move files to Trash"
588 // [self syncOperationFinishedWithSuccess:YES];
590 for (NSString *subPath in subPaths) {
591 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
593 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
594 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
595 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
603 - (BOOL)moveToTempTrashFile:(NSString *)filePath
604 accountName:(NSString *)accountName
605 pithosContainer:(ASIPithosContainer *)pithosContainer {
607 if (!self.tempTrashDirPath)
609 NSFileManager *fileManager = [NSFileManager defaultManager];
611 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
612 NSError *error = nil;
613 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
614 NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath withString:self.tempTrashDirPath];
615 NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
616 if (fileExists && isDirectory) {
617 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
619 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
620 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath]
624 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
625 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
626 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
630 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
631 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
632 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
633 filePath, newFilePath]
637 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
639 currentState.filePath = newFilePath;
640 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
641 [currentLocalObjectStates removeObjectForKey:filePath];
643 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
644 blockHash:pithosContainer.blockHash
645 blockSize:pithosContainer.blockSize]
648 for (NSString *subPath in subPaths) {
649 NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
650 NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath
651 withString:self.tempTrashDirPath];
652 currentState = [currentLocalObjectStates objectForKey:subFilePath];
654 currentState.filePath = newSubFilePath;
655 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
656 [currentLocalObjectStates removeObjectForKey:subFilePath];
658 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
659 blockHash:pithosContainer.blockHash
660 blockSize:pithosContainer.blockSize]
661 forKey:newSubFilePath];
664 } else if (fileExists && !isDirectory) {
665 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
666 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
667 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
671 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
672 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
673 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
674 filePath, newFilePath]
678 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
680 currentState.filePath = newFilePath;
681 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
682 [currentLocalObjectStates removeObjectForKey:filePath];
684 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
685 blockHash:pithosContainer.blockHash
686 blockSize:pithosContainer.blockSize]
694 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
695 if ([hash length] != 64)
698 PithosLocalObjectState *localState;
699 NSFileManager *fileManager = [NSFileManager defaultManager];
701 NSError *error = nil;
702 for (NSString *localFilePath in currentLocalObjectStates) {
703 localState = [currentLocalObjectStates objectForKey:localFilePath];
704 if (!localState.isDirectory && [hash isEqualToString:localState.hash] &&
705 [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
706 if ([localFilePath hasPrefix:directoryPath]) {
707 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
708 [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
709 message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
710 localFilePath, filePath]
715 } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
716 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
717 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
718 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
719 localFilePath, filePath]
722 localState.filePath = filePath;
723 [currentLocalObjectStates setObject:localState forKey:filePath];
724 [currentLocalObjectStates removeObjectForKey:localFilePath];
734 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
735 localFilePath:(NSString *)filePath
736 accountName:(NSString *)accountName
737 pithosContainer:(ASIPithosContainer *)pithosContainer {
739 NSFileManager *fileManager = [NSFileManager defaultManager];
742 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
743 NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
744 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
745 objectForKey:pithosContainer.name];
746 PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
747 // Remote updated info
748 NSError *remoteError;
749 BOOL remoteIsDirectory;
750 BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos
751 containerName:pithosContainer.name
752 objectName:object.name
754 isDirectory:&remoteIsDirectory
755 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
756 if (!object || !object.objectHash) {
757 // Delete local object
758 if (![accountName isEqualToString:@""])
759 // If "shared with me" skip
761 if (remoteObjectExists) {
762 // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
763 syncIncomplete = YES;
765 DLog(@"Sync::delete local object: %@", filePath);
766 if (!fileExists || [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer]) {
767 dispatch_async(dispatch_get_main_queue(), ^{
768 [activityFacility startAndEndActivityWithType:PithosActivityOther
769 message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)",
770 pithosContainer.name, object.name]
771 pithosAccount:pithosAccount];
773 [containerStoredLocalObjectStates removeObjectForKey:object.name];
774 [self saveLocalState];
776 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
777 // Create local directory object
778 if (!remoteObjectExists || !remoteIsDirectory) {
779 // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
780 syncIncomplete = YES;
783 DLog(@"Sync::create local directory object: %@", filePath);
784 BOOL directoryCreated = NO;
785 if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
786 DLog(@"Sync::local directory object doesn't exist: %@", filePath);
788 if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
789 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
790 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
793 directoryCreated = YES;
794 storedState.filePath = filePath;
795 storedState.isDirectory = YES;
796 [self saveLocalState];
799 DLog(@"Sync::local directory object exists: %@", filePath);
800 directoryCreated = YES;
802 if (directoryCreated)
803 dispatch_async(dispatch_get_main_queue(), ^{
804 [activityFacility startAndEndActivityWithType:PithosActivityOther
805 message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)",
806 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
808 pithosAccount:pithosAccount];
810 } else if (object.bytes == 0) {
811 // Create local object with zero length
812 if (!remoteObjectExists || remoteIsDirectory) {
813 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
814 syncIncomplete = YES;
817 DLog(@"Sync::create local zero length object: %@", filePath);
818 BOOL fileCreated = NO;
820 ((isDirectory || [PithosUtilities bytesOfFile:filePath]) &&
821 [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
822 DLog(@"Sync::local zero length object doesn't exist: %@", filePath);
823 // Create directory of the file, if it doesn't exist
825 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
826 dispatch_async(dispatch_get_main_queue(), ^{
827 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
828 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
833 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
834 [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
835 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
839 storedState.filePath = filePath;
840 storedState.hash = object.objectHash;
841 storedState.tmpFilePath = nil;
842 [self saveLocalState];
845 DLog(@"Sync::local zero length object exists: %@", filePath);
849 dispatch_async(dispatch_get_main_queue(), ^{
850 [activityFacility startAndEndActivityWithType:PithosActivityOther
851 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
852 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
854 pithosAccount:pithosAccount];
856 } else if (storedState.tmpFilePath == nil) {
857 // Create new local object
858 if (!remoteObjectExists || remoteIsDirectory) {
859 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
860 syncIncomplete = YES;
863 // Create directory of the file, if it doesn't exist
865 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
866 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
867 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
870 // Check first if a local copy exists
871 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
872 storedState.filePath = filePath;
873 storedState.hash = object.objectHash;
874 [self saveLocalState];
875 dispatch_async(dispatch_get_main_queue(), ^{
876 [activityFacility startAndEndActivityWithType:PithosActivityOther
877 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
878 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
880 pithosAccount:pithosAccount];
883 [self syncOperationStarted];
884 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
885 containerName:pithosContainer.name
888 blockSize:pithosContainer.blockSize];
889 if (![accountName isEqualToString:@""])
890 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
891 objectRequest.delegate = self;
892 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
893 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
894 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
895 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
896 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
897 message:[messagePrefix stringByAppendingString:@" (0%%)"]
898 totalBytes:object.bytes
900 pithosAccount:pithosAccount];
901 dispatch_async(dispatch_get_main_queue(), ^{
902 [activityFacility updateActivity:activity withMessage:activity.message];
904 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
905 accountName, @"accountName",
906 pithosContainer, @"pithosContainer",
907 object, @"pithosObject",
908 messagePrefix, @"messagePrefix",
909 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks",
910 [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
911 filePath, @"filePath",
912 activity, @"activity",
913 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
914 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
915 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
916 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
917 [NSNumber numberWithUnsignedInteger:10], @"retries",
918 NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector",
919 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
921 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
922 [activityFacility updateActivity:activity
923 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
924 messagePrefix, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
925 totalBytes:activity.totalBytes
926 currentBytes:(activity.currentBytes + size)];
928 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
931 // Resume local object download
932 if (!remoteObjectExists || remoteIsDirectory) {
933 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
934 syncIncomplete = YES;
937 // Create directory of the file, if it doesn't exist
939 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
940 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
941 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
944 // Check first if a local copy exists
945 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
946 storedState.filePath = filePath;
947 storedState.hash = object.objectHash;
948 // Delete incomplete temp download
949 storedState.tmpFilePath = nil;
950 [self saveLocalState];
951 dispatch_async(dispatch_get_main_queue(), ^{
952 [activityFacility startAndEndActivityWithType:PithosActivityOther
953 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
954 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
956 pithosAccount:pithosAccount];
959 [self syncOperationStarted];
960 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
961 containerName:pithosContainer.name
962 objectName:object.name];
963 if (![accountName isEqualToString:@""])
964 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
965 objectRequest.delegate = self;
966 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
967 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
968 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
969 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
970 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
971 message:[messagePrefix stringByAppendingString:@" (0%%)"]
972 totalBytes:object.bytes
974 pithosAccount:pithosAccount];
975 dispatch_async(dispatch_get_main_queue(), ^{
976 [activityFacility updateActivity:activity withMessage:activity.message];
978 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
979 accountName, @"accountName",
980 pithosContainer, @"pithosContainer",
981 object, @"pithosObject",
982 messagePrefix, @"messagePrefix",
983 filePath, @"filePath",
984 activity, @"activity",
985 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
986 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
987 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
988 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
989 [NSNumber numberWithUnsignedInteger:10], @"retries",
990 NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector",
991 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
993 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
999 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
1000 object:(ASIPithosObject *)object
1001 localFilePath:(NSString *)filePath
1002 accountName:(NSString *)accountName
1003 pithosContainer:(ASIPithosContainer *)pithosContainer {
1005 [self syncOperationStarted];
1006 NSFileManager *fileManager = [NSFileManager defaultManager];
1008 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1009 if (currentState.isDirectory) {
1010 // Create remote directory object
1011 if (![accountName isEqualToString:@""]) {
1012 if (!object.allowedTo) {
1013 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1014 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1015 while ([objectAncestorName length] && !object.allowedTo) {
1016 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1017 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1020 if (![object.allowedTo isEqualToString:@"write"]) {
1021 // If read-only "shared with me" skip
1022 [self syncOperationFinishedWithSuccess:YES];
1026 if (!fileExists || !isDirectory) {
1027 // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
1028 [self syncOperationFinishedWithSuccess:NO];
1031 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
1032 containerName:pithosContainer.name
1033 objectName:object.name
1035 contentType:@"application/directory"
1037 contentDisposition:nil
1040 isPublic:ASIPithosObjectRequestPublicIgnore
1042 data:[NSData data]];
1043 if (![accountName isEqualToString:@""])
1044 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1045 objectRequest.delegate = self;
1046 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1047 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1048 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Creating directory '%@/%@'",
1049 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1050 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1051 message:messagePrefix
1052 pithosAccount:pithosAccount];
1053 dispatch_async(dispatch_get_main_queue(), ^{
1054 [activityFacility updateActivity:activity withMessage:activity.message];
1056 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1057 accountName, @"accountName",
1058 pithosContainer, @"pithosContainer",
1059 object, @"pithosObject",
1060 messagePrefix, @"messagePrefix",
1061 activity, @"activity",
1062 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1063 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1064 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1065 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1066 [NSNumber numberWithUnsignedInteger:10], @"retries",
1067 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1068 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1070 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1071 } else if (![currentState exists]) {
1072 // Delete remote object
1073 if (![accountName isEqualToString:@""]) {
1074 // If "shared with me" skip
1075 [self syncOperationFinishedWithSuccess:YES];
1079 // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
1080 syncIncomplete = YES;
1082 if ([pithosContainer.name isEqualToString:@"trash"]) {
1084 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
1085 containerName:pithosContainer.name
1086 objectName:object.name];
1087 objectRequest.delegate = self;
1088 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1089 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1090 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Deleting '%@/%@'",
1091 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1092 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1093 message:messagePrefix
1094 pithosAccount:pithosAccount];
1095 dispatch_async(dispatch_get_main_queue(), ^{
1096 [activityFacility updateActivity:activity withMessage:activity.message];
1098 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1099 accountName, @"accountName",
1100 pithosContainer, @"pithosContainer",
1101 object, @"pithosObject",
1102 messagePrefix, @"messagePrefix",
1103 activity, @"activity",
1104 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1105 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1106 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1107 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1108 [NSNumber numberWithUnsignedInteger:10], @"retries",
1109 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
1110 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1112 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1114 // Move to container trash
1116 if ([PithosUtilities isContentTypeDirectory:object.contentType])
1117 safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
1119 safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
1121 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1122 containerName:pithosContainer.name
1123 objectName:object.name
1124 destinationContainerName:@"trash"
1125 destinationObjectName:safeName
1127 if (objectRequest) {
1128 objectRequest.delegate = self;
1129 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1130 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1131 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'",
1132 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1133 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1134 message:messagePrefix
1135 pithosAccount:pithosAccount];
1136 dispatch_async(dispatch_get_main_queue(), ^{
1137 [activityFacility updateActivity:activity withMessage:activity.message];
1139 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1140 accountName, @"accountName",
1141 pithosContainer, @"pithosContainer",
1142 object, @"pithosObject",
1143 messagePrefix, @"messagePrefix",
1144 activity, @"activity",
1145 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1146 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1147 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1148 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1149 [NSNumber numberWithUnsignedInteger:10], @"retries",
1150 NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector",
1151 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1153 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1155 [self syncOperationFinishedWithSuccess:NO];
1158 [self syncOperationFinishedWithSuccess:NO];
1162 // Upload file to remote object
1163 if (![accountName isEqualToString:@""]) {
1164 if (!object.allowedTo) {
1165 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1166 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1167 while ([objectAncestorName length] && !object.allowedTo) {
1168 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1169 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1172 if (![object.allowedTo isEqualToString:@"write"]) {
1173 // If read-only "shared with me" skip
1174 [self syncOperationFinishedWithSuccess:YES];
1178 if (!fileExists || isDirectory) {
1179 // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
1180 [self syncOperationFinishedWithSuccess:NO];
1183 NSError *error = nil;
1184 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1185 if (object.contentType == nil)
1186 object.contentType = @"application/octet-stream";
1189 DLog(@"contentType detection error: %@", error);
1191 NSArray *hashes = nil;
1192 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1193 containerName:pithosContainer.name
1194 objectName:object.name
1195 contentType:object.contentType
1196 blockSize:pithosContainer.blockSize
1197 blockHash:pithosContainer.blockHash
1201 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
1202 if (objectRequest) {
1203 objectRequest.delegate = self;
1204 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1205 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1206 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Uploading '%@/%@'",
1207 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1208 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1209 message:[messagePrefix stringByAppendingString:@" (0%%)"]
1210 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1212 pithosAccount:pithosAccount];
1213 dispatch_async(dispatch_get_main_queue(), ^{
1214 [activityFacility updateActivity:activity withMessage:activity.message];
1216 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1217 [NSDictionary dictionaryWithObjectsAndKeys:
1218 accountName, @"accountName",
1219 pithosContainer, @"pithosContainer",
1220 object, @"pithosObject",
1221 messagePrefix, @"messagePrefix",
1222 filePath, @"filePath",
1224 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1225 activity, @"activity",
1226 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1227 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1228 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1229 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1230 [NSNumber numberWithUnsignedInteger:10], @"retries",
1231 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1232 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1234 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1236 [self syncOperationFinishedWithSuccess:NO];
1243 #pragma mark ASIHTTPRequestDelegate
1245 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1246 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1247 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1248 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1250 operation.completionBlock = ^{
1252 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1253 dispatch_async(dispatch_get_main_queue(), ^{
1254 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1255 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1257 [self syncOperationFinishedWithSuccess:NO];
1261 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1262 [callbackQueue addOperation:operation];
1265 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1266 if (request.isCancelled) {
1267 // Request has been cancelled
1268 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1269 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1270 withObject:request];
1272 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1273 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1274 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1276 operation.completionBlock = ^{
1278 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1279 dispatch_async(dispatch_get_main_queue(), ^{
1280 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1281 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1283 [self syncOperationFinishedWithSuccess:NO];
1287 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1288 [callbackQueue addOperation:operation];
1292 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1294 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1295 DLog(@"Sync::list request finished: %@", containerRequest.url);
1296 if (operation.isCancelled) {
1297 [self listRequestFailed:containerRequest];
1298 } else if ((containerRequest.responseStatusCode == 200) ||
1299 (containerRequest.responseStatusCode == 304) ||
1300 (containerRequest.responseStatusCode == 403)) {
1301 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
1302 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1303 if (containerRequest.responseStatusCode == 200) {
1304 NSArray *someObjects = [containerRequest objects];
1305 if (objects == nil) {
1306 objects = [[NSMutableArray alloc] initWithArray:someObjects];
1308 [objects addObjectsFromArray:someObjects];
1310 if ([someObjects count] < 10000) {
1311 pithosContainer.blockHash = [containerRequest blockHash];
1312 pithosContainer.blockSize = [containerRequest blockSize];
1313 pithosContainer.lastModified = [containerRequest lastModified];
1314 NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1315 for (ASIPithosObject *object in objects) {
1316 [containerRemoteObjects setObject:object forKey:object.name];
1318 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1321 // Do an additional request to fetch more objects
1322 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1323 containerName:pithosContainer.name
1325 marker:[[someObjects lastObject] name]
1332 ifModifiedSince:pithosContainer.lastModified];
1333 if (![accountName isEqualToString:@""])
1334 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1335 newContainerRequest.delegate = self;
1336 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1337 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1338 newContainerRequest.userInfo = containerRequest.userInfo;
1339 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1340 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1343 } else if (containerRequest.responseStatusCode == 304) {
1344 NSMutableDictionary *containerRemoteObjects = [[previousRemoteObjects objectForKey:accountName]
1345 objectForKey:pithosContainer.name];
1346 if (containerRemoteObjects)
1347 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1348 } else if (containerRequest.responseStatusCode == 403) {
1349 [[remoteObjects objectForKey:accountName] setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
1353 if (containersIndex == [[accountsPithosContainers objectForKey:accountName] count]) {
1355 containersIndex = 0;
1357 if (accountsIndex < accountsCount) {
1358 accountName = [accountsNames objectAtIndex:accountsIndex];
1359 pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1360 // Do a request for the next container
1361 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1362 containerName:pithosContainer.name
1371 ifModifiedSince:pithosContainer.lastModified];
1372 if (![accountName isEqualToString:@""])
1373 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1374 newContainerRequest.delegate = self;
1375 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1376 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1377 newContainerRequest.userInfo = containerRequest.userInfo;
1378 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1379 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1382 self.previousRemoteObjects = remoteObjects;
1383 // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
1385 if (operation.isCancelled) {
1386 [self listRequestFailed:containerRequest];
1390 dispatch_async(dispatch_get_main_queue(), ^{
1391 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1392 withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1394 NSFileManager *fileManager = [NSFileManager defaultManager];
1396 // Compute current state of legal existing local objects
1397 // and add an empty stored state for legal new local objects since last sync
1398 self.currentLocalObjectStates = [NSMutableDictionary dictionary];
1399 for (NSString *accountName in accountsNames) {
1400 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1401 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1402 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1403 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1404 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1405 objectForKey:pithosContainer.name];
1406 NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath];
1407 for (__strong NSString *objectName in dirEnumerator) {
1408 objectName = [objectName precomposedStringWithCanonicalMapping];
1409 if (operation.isCancelled) {
1410 operation.completionBlock = nil;
1411 [self saveLocalState];
1412 [self syncOperationFinishedWithSuccess:NO];
1416 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1417 NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1419 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1420 if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) {
1421 [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error"
1422 message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.",
1423 containerDirectoryPath, pithosAccount.name]
1425 pithosAccount.syncActive = NO;
1427 } else if (fileExists) {
1428 if (skipHidden && [[objectName lastPathComponent] hasPrefix:@"."]) {
1429 // Skip hidden directory and its descendants, or hidden file
1431 [dirEnumerator skipDescendants];
1432 // Remove stored state if any
1433 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1435 } else if ([[objectName pathComponents] count] == 1) {
1436 if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) {
1437 // Skip excluded directory and its descendants, or root file with same name
1439 [dirEnumerator skipDescendants];
1440 // Remove stored state if any
1441 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1443 } else if (!isDirectory && containerExcludeRootFiles) {
1444 // Skip excluded root file
1445 // Remove stored state if any
1446 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1450 // Include local object
1451 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
1452 if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1453 // New or modified existing local object, compute current state
1454 if (!storedLocalObjectState)
1455 // For new local object, also create empty stored state
1456 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1457 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
1458 blockHash:pithosContainer.blockHash
1459 blockSize:pithosContainer.blockSize]
1462 // Local object hasn't changed, set stored state also to current
1463 [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1467 [self saveLocalState];
1471 if (operation.isCancelled) {
1472 operation.completionBlock = nil;
1473 [self syncOperationFinishedWithSuccess:NO];
1477 // Add an empty stored state for legal new remote objects since last sync
1478 for (NSString *accountName in accountsNames) {
1479 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1480 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1481 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1482 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1483 objectForKey:pithosContainer.name];
1484 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1485 for (NSString *objectName in containerRemoteObjects) {
1486 if (operation.isCancelled) {
1487 operation.completionBlock = nil;
1488 [self saveLocalState];
1489 [self syncOperationFinishedWithSuccess:NO];
1493 ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName];
1494 NSString *localObjectName;
1495 if ([object.name hasSuffix:@"/"])
1496 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1498 localObjectName = [NSString stringWithString:object.name];
1499 NSArray *pathComponents = [localObjectName pathComponents];
1501 BOOL skipObject = NO;
1502 for (NSString *pathComponent in pathComponents) {
1503 if ([pathComponent hasPrefix:@"."]) {
1504 // Skip hidden directory and its descendants, or hidden file
1505 // Remove stored state if any
1506 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1514 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1515 // Skip excluded directory object and its descendants, or root file object with same name
1516 // Remove stored state if any
1517 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1519 } else if (containerExcludeRootFiles &&
1520 ([pathComponents count] == 1) &&
1521 ![PithosUtilities isContentTypeDirectory:object.contentType]) {
1522 // Skip root file object
1523 // Remove stored state if any
1524 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1527 if (![containerStoredLocalObjectStates objectForKey:object.name])
1528 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name];
1530 [self saveLocalState];
1534 if (operation.isCancelled) {
1535 operation.completionBlock = nil;
1536 [self syncOperationFinishedWithSuccess:NO];
1540 // For each stored state compare with current and remote state
1541 // Stored states of local objects that have been deleted,
1542 // haven't been checked for legality (only existing local remote objects)
1543 // These should be identified and skipped
1544 for (NSString *accountName in accountsNames) {
1545 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1546 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1547 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1548 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1549 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1550 objectForKey:pithosContainer.name];
1551 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1552 for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1553 if (operation.isCancelled) {
1554 operation.completionBlock = nil;
1555 [self syncOperationFinishedWithSuccess:NO];
1559 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1560 if ([objectName hasSuffix:@"/"])
1561 filePath = [filePath stringByAppendingString:@":"];
1562 ASIPithosObject *object = [ASIPithosObject object];
1563 object.name = objectName;
1564 DLog(@"Sync::object name: %@", object.name);
1566 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
1567 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1568 if (!currentLocalObjectState) {
1569 // The stored state corresponds to a remote or deleted local object, that's why there is no current state
1570 // In the latter case it must be checked for legality, which can be determined by its stored state
1571 // If it existed locally, but was deleted, state.exists is true,
1572 // else if the stored state is an empty state that was created due to the server object, state.exists is false
1573 if (storedLocalObjectState.exists) {
1574 NSString *localObjectName;
1575 if ([object.name hasSuffix:@"/"])
1576 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1578 localObjectName = [NSString stringWithString:object.name];
1579 NSArray *pathComponents = [localObjectName pathComponents];
1581 BOOL skipObject = NO;
1582 for (NSString *pathComponent in pathComponents) {
1583 if ([pathComponent hasPrefix:@"."]) {
1584 // Skip hidden directory and its descendants, or hidden file
1585 // Remove stored state if any
1586 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1587 [self saveLocalState];
1595 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1596 // Skip excluded directory object and its descendants, or root file object with same name
1597 // Remove stored state
1598 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1599 [self saveLocalState];
1601 } else if (containerExcludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) {
1602 // Skip root file object
1603 // Remove stored state
1604 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1605 [self saveLocalState];
1609 // There is also the off case that a local object has been created in the meantime
1610 // This call works in any case, existent or non-existent local object
1611 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
1612 blockHash:pithosContainer.blockHash
1613 blockSize:pithosContainer.blockSize];
1616 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1617 ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
1619 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1620 remoteObjectState.isDirectory = YES;
1622 remoteObjectState.hash = remoteObject.objectHash;
1626 BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1627 BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1628 DLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1629 if (!localStateHasChanged) {
1630 // Local state hasn't changed
1631 if (serverStateHasChanged) {
1632 // Server state has changed
1633 // Update local state to match that of the server
1634 object.bytes = remoteObject.bytes;
1635 object.version = remoteObject.version;
1636 object.contentType = remoteObject.contentType;
1637 object.objectHash = remoteObject.objectHash;
1638 [self updateLocalStateWithObject:object localFilePath:filePath
1639 accountName:accountName pithosContainer:pithosContainer];
1640 } else if (!remoteObject && ![currentLocalObjectState exists]) {
1641 // Server state hasn't changed
1642 // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1643 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1644 [self saveLocalState];
1647 // Local state has changed
1648 if (!serverStateHasChanged) {
1649 // Server state hasn't changed
1650 if (currentLocalObjectState.isDirectory)
1651 object.contentType = @"application/directory";
1653 object.objectHash = currentLocalObjectState.hash;
1654 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1655 accountName:accountName pithosContainer:pithosContainer];
1657 // Server state has also changed
1658 if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1659 // Both did the same change (directory)
1660 storedLocalObjectState.filePath = filePath;
1661 storedLocalObjectState.isDirectory = YES;
1662 [self saveLocalState];
1663 } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1664 // Both did the same change (object edit or delete)
1665 if (![remoteObjectState exists]) {
1666 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1668 storedLocalObjectState.filePath = filePath;
1669 storedLocalObjectState.hash = remoteObjectState.hash;
1671 [self saveLocalState];
1673 // Conflict, we ask the user which change to keep
1674 NSString *informativeText;
1675 NSString *firstButtonText;
1676 NSString *secondButtonText;
1678 if (![remoteObjectState exists]) {
1679 // Remote object has been deleted
1680 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.",
1681 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name ];
1682 firstButtonText = @"Delete local file";
1683 secondButtonText = @"Upload file to server";
1684 } else if (![currentLocalObjectState exists]) {
1685 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.",
1686 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1687 firstButtonText = @"Download file from server";
1688 secondButtonText = @"Delete file on server";
1690 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.",
1691 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1692 firstButtonText = @"Keep server version";
1693 secondButtonText = @"Keep local version";
1695 __block NSInteger choice;
1696 dispatch_sync(dispatch_get_main_queue(), ^{
1697 NSAlert *alert = [[NSAlert alloc] init];
1698 [alert setMessageText:@"Conflict"];
1699 [alert setInformativeText:informativeText];
1700 [alert addButtonWithTitle:firstButtonText];
1701 [alert addButtonWithTitle:secondButtonText];
1702 [alert addButtonWithTitle:@"Do nothing"];
1703 choice = [alert runModal];
1705 if (choice == NSAlertFirstButtonReturn) {
1706 object.bytes = remoteObject.bytes;
1707 object.version = remoteObject.version;
1708 object.contentType = remoteObject.contentType;
1709 object.objectHash = remoteObject.objectHash;
1710 [self updateLocalStateWithObject:object localFilePath:filePath
1711 accountName:accountName pithosContainer:pithosContainer];
1712 } if (choice == NSAlertSecondButtonReturn) {
1713 if (currentLocalObjectState.isDirectory)
1714 object.contentType = @"application/directory";
1716 object.objectHash = currentLocalObjectState.hash;
1717 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1718 accountName:accountName pithosContainer:pithosContainer];
1726 [self syncOperationFinishedWithSuccess:YES];
1728 [self listRequestFailed:containerRequest];
1733 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1735 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1736 if (operation.isCancelled) {
1740 if (containerRequest.isCancelled) {
1741 dispatch_async(dispatch_get_main_queue(), ^{
1742 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1743 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1746 [self syncOperationFinishedWithSuccess:NO];
1749 // If the server listing fails, the sync should start over, so just retrying is enough
1750 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1752 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
1753 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1754 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1756 dispatch_async(dispatch_get_main_queue(), ^{
1757 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1758 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1761 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1762 [self syncOperationFinishedWithSuccess:NO];
1767 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1769 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1770 DLog(@"Sync::download object block finished: %@", objectRequest.url);
1771 if (operation.isCancelled) {
1772 [self requestFailed:objectRequest];
1773 } else if (objectRequest.responseStatusCode == 206) {
1774 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1775 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1776 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1777 NSFileManager *fileManager = [NSFileManager defaultManager];
1779 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1781 NSString *downloadsDirPath = self.tempDownloadsDirPath;
1782 if (!downloadsDirPath) {
1783 dispatch_async(dispatch_get_main_queue(), ^{
1784 [activityFacility endActivity:activity
1785 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1787 [self syncOperationFinishedWithSuccess:NO];
1791 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1792 objectForKey:pithosContainer.name]
1793 objectForKey:object.name];
1794 if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1795 NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1796 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1797 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1798 strcpy(tempFileNameCString, tempFileTemplateCString);
1799 int fileDescriptor = mkstemp(tempFileNameCString);
1800 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1801 free(tempFileNameCString);
1802 if (fileDescriptor == -1) {
1803 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
1804 message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]
1806 dispatch_async(dispatch_get_main_queue(), ^{
1807 [activityFacility endActivity:activity
1808 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1810 [self syncOperationFinishedWithSuccess:NO];
1813 close(fileDescriptor);
1814 storedState.tmpFilePath = tempFilePath;
1815 [self saveLocalState];
1818 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1819 NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1820 [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
1821 [tempFileHandle writeData:[objectRequest responseData]];
1822 [tempFileHandle closeFile];
1824 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1825 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1826 if (missingBlockIndex == NSNotFound) {
1827 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1828 NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1829 if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath
1830 accountName:accountName
1831 pithosContainer:pithosContainer]) {
1832 dispatch_async(dispatch_get_main_queue(), ^{
1833 [activityFacility endActivity:activity
1834 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1836 [self syncOperationFinishedWithSuccess:NO];
1838 } else if (![fileManager fileExistsAtPath:dirPath]) {
1839 // File doesn't exist but also the containing directory doesn't exist
1840 // In most cases this should have been resolved as an update of the corresponding local object,
1841 // but it never hurts to check
1843 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1845 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1846 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
1848 dispatch_async(dispatch_get_main_queue(), ^{
1849 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1850 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1852 [self syncOperationFinishedWithSuccess:NO];
1856 // Move file from tmp download
1858 [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1860 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
1861 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath]
1863 dispatch_async(dispatch_get_main_queue(), ^{
1864 [activityFacility endActivity:activity
1865 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1867 [self syncOperationFinishedWithSuccess:NO];
1870 dispatch_async(dispatch_get_main_queue(), ^{
1871 [activityFacility endActivity:activity
1872 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1873 totalBytes:activity.totalBytes
1874 currentBytes:activity.totalBytes];
1877 storedState.filePath = filePath;
1878 storedState.hash = object.objectHash;
1879 storedState.tmpFilePath = nil;
1880 [self saveLocalState];
1881 [self syncOperationFinishedWithSuccess:YES];
1884 if (newSyncRequested || syncLate || operation.isCancelled) {
1885 [self requestFailed:objectRequest];
1887 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1888 containerName:pithosContainer.name
1890 blockIndex:missingBlockIndex
1891 blockSize:pithosContainer.blockSize];
1892 if (![accountName isEqualToString:@""])
1893 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1894 newObjectRequest.delegate = self;
1895 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1896 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1897 newObjectRequest.userInfo = objectRequest.userInfo;
1898 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1899 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1900 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1901 [activityFacility updateActivity:activity
1902 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1903 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
1904 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1905 totalBytes:activity.totalBytes
1906 currentBytes:(activity.currentBytes + size)];
1908 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1911 } else if (objectRequest.responseStatusCode == 412) {
1912 // The object has changed on the server
1913 dispatch_async(dispatch_get_main_queue(), ^{
1914 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1915 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1917 [self syncOperationFinishedWithSuccess:NO];
1919 [self requestFailed:objectRequest];
1924 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1926 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1927 DLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1928 if (operation.isCancelled) {
1929 [self requestFailed:objectRequest];
1930 } else if (objectRequest.responseStatusCode == 200) {
1931 if (newSyncRequested || syncLate || operation.isCancelled) {
1932 [self requestFailed:objectRequest];
1934 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1935 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1936 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1937 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1938 objectForKey:pithosContainer.name]
1939 objectForKey:object.name];
1940 if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1941 [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
1942 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1943 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath
1944 blockSize:pithosContainer.blockSize
1945 blockHash:pithosContainer.blockHash
1946 withHashes:[objectRequest hashes]];
1947 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1948 dispatch_async(dispatch_get_main_queue(), ^{
1949 [activityFacility updateActivity:activity
1950 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1951 [objectRequest.userInfo objectForKey:@"messagePrefix"],
1952 (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))]
1953 totalBytes:activity.totalBytes
1954 currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
1957 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1958 containerName:pithosContainer.name
1960 blockIndex:missingBlockIndex
1961 blockSize:pithosContainer.blockSize];
1962 if (![accountName isEqualToString:@""])
1963 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1964 newObjectRequest.delegate = self;
1965 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1966 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1967 newObjectRequest.userInfo = objectRequest.userInfo;
1968 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1969 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1970 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1971 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1972 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1973 [activityFacility updateActivity:activity
1974 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1975 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
1976 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1977 totalBytes:activity.totalBytes
1978 currentBytes:(activity.currentBytes + size)];
1980 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1983 [self requestFailed:objectRequest];
1988 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1990 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1991 DLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1992 if (operation.isCancelled) {
1993 [self requestFailed:objectRequest];
1994 } else if (objectRequest.responseStatusCode == 201) {
1995 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
1996 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
1997 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1998 storedState.isDirectory = YES;
1999 [self saveLocalState];
2000 dispatch_async(dispatch_get_main_queue(), ^{
2001 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2002 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2004 [self syncOperationFinishedWithSuccess:YES];
2006 [self requestFailed:objectRequest];
2011 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
2013 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2014 DLog(@"Sync::move object to trash finished: %@", objectRequest.url);
2015 if (operation.isCancelled) {
2016 [self requestFailed:objectRequest];
2017 } else if (objectRequest.responseStatusCode == 201) {
2018 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2019 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2020 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2021 [self saveLocalState];
2022 dispatch_async(dispatch_get_main_queue(), ^{
2023 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2024 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2026 [self syncOperationFinishedWithSuccess:YES];
2028 [self requestFailed:objectRequest];
2033 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2035 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2036 DLog(@"Sync::delete object finished: %@", objectRequest.url);
2037 if (operation.isCancelled) {
2038 [self requestFailed:objectRequest];
2039 } else if (objectRequest.responseStatusCode == 204) {
2040 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2041 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2042 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2043 [self saveLocalState];
2044 dispatch_async(dispatch_get_main_queue(), ^{
2045 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2046 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2048 [self syncOperationFinishedWithSuccess:YES];
2050 [self requestFailed:objectRequest];
2055 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
2057 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2058 DLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
2059 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
2060 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
2061 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
2062 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
2063 objectForKey:pithosContainer.name]
2064 objectForKey:object.name];
2065 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
2066 NSUInteger totalBytes = activity.totalBytes;
2067 NSUInteger currentBytes = activity.currentBytes;
2068 if (operation.isCancelled) {
2069 [self requestFailed:objectRequest];
2070 } else if (objectRequest.responseStatusCode == 201) {
2071 DLog(@"Sync::object created: %@", objectRequest.url);
2072 storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
2073 storedState.hash = object.objectHash;
2074 [self saveLocalState];
2075 dispatch_async(dispatch_get_main_queue(), ^{
2076 [activityFacility endActivity:activity
2077 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
2078 totalBytes:totalBytes
2079 currentBytes:totalBytes];
2081 [self syncOperationFinishedWithSuccess:YES];
2082 } else if (objectRequest.responseStatusCode == 409) {
2083 if (newSyncRequested || syncLate || operation.isCancelled) {
2084 [self requestFailed:objectRequest];
2086 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
2087 if (iteration == 0) {
2088 DLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
2089 dispatch_async(dispatch_get_main_queue(), ^{
2090 [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
2092 [self syncOperationFinishedWithSuccess:NO];
2095 DLog(@"Sync::object is missing hashes: %@", objectRequest.url);
2096 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
2097 withMissingHashes:[objectRequest hashes]];
2098 if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
2099 currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
2100 dispatch_async(dispatch_get_main_queue(), ^{
2101 [activityFacility updateActivity:activity
2102 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2103 [objectRequest.userInfo objectForKey:@"messagePrefix"],
2104 (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
2105 totalBytes:totalBytes
2106 currentBytes:currentBytes];
2108 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2109 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2110 containerName:pithosContainer.name
2111 blockSize:pithosContainer.blockSize
2112 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
2113 missingBlockIndex:missingBlockIndex
2114 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2115 newContainerRequest.delegate = self;
2116 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2117 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2118 newContainerRequest.userInfo = objectRequest.userInfo;
2119 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
2120 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2121 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2122 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2123 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
2124 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2125 [activityFacility updateActivity:activity
2126 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2127 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2128 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2129 totalBytes:activity.totalBytes
2130 currentBytes:(activity.currentBytes + size)];
2132 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2135 [self requestFailed:objectRequest];
2140 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
2142 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
2143 DLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
2144 if (operation.isCancelled) {
2145 [self requestFailed:containerRequest];
2146 } else if (containerRequest.responseStatusCode == 202) {
2147 NSString *accountName = [containerRequest.userInfo objectForKey:@"accountName"];
2148 ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
2149 ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
2150 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
2151 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
2152 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
2153 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
2154 if (operation.isCancelled) {
2155 [self requestFailed:containerRequest];
2156 } else if (missingBlockIndex == NSNotFound) {
2157 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
2158 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
2159 containerName:pithosContainer.name
2160 objectName:object.name
2161 contentType:object.contentType
2162 blockSize:pithosContainer.blockSize
2163 blockHash:pithosContainer.blockHash
2164 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2167 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2168 newObjectRequest.delegate = self;
2169 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2170 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2171 newObjectRequest.userInfo = containerRequest.userInfo;
2172 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2173 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
2174 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
2175 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
2176 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2178 if (newSyncRequested || syncLate || operation.isCancelled) {
2179 [self requestFailed:containerRequest];
2181 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2182 containerName:pithosContainer.name
2183 blockSize:pithosContainer.blockSize
2184 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2185 missingBlockIndex:missingBlockIndex
2186 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2187 newContainerRequest.delegate = self;
2188 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2189 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2190 newContainerRequest.userInfo = containerRequest.userInfo;
2191 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2192 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2193 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2194 [activityFacility updateActivity:activity
2195 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2196 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2197 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2198 totalBytes:activity.totalBytes
2199 currentBytes:(activity.currentBytes + size)];
2201 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2205 [self requestFailed:containerRequest];
2210 - (void)requestFailed:(ASIPithosRequest *)request {
2212 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
2213 DLog(@"Sync::request failed: %@", request.url);
2214 if (operation.isCancelled)
2216 if (request.isCancelled || newSyncRequested || syncLate) {
2217 dispatch_async(dispatch_get_main_queue(), ^{
2218 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2219 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
2221 [self syncOperationFinishedWithSuccess:NO];
2224 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
2226 ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
2227 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
2228 [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
2230 dispatch_async(dispatch_get_main_queue(), ^{
2231 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2232 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
2234 [self syncOperationFinishedWithSuccess:NO];