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