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 retain];
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 to 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 to 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];
252 [callbackQueue release];
253 [networkQueue release];
254 [tempTrashDirPath release];
255 [tempDownloadsDirPath release];
256 [pithosStateFilePath release];
257 [currentLocalObjectStates release];
258 [storedLocalObjectStates release];
259 [previousRemoteObjects release];
260 [remoteObjects release];
262 [lastCompletedSync release];
263 [accountsPithosContainers release];
264 [accountsNames release];
266 [accountsDictionary release];
267 [pithosAccount release];
268 [directoryPath release];
273 #pragma mark Observers
275 - (void)applicationWillTerminate:(NSNotification *)notification {
276 [self saveLocalState];
280 #pragma mark Properties
282 - (NSString *)pithosStateFilePath {
283 if (!pithosStateFilePath) {
284 pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
285 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
286 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive",
287 pithosAccount.uniqueName]] retain];
288 NSString *pithosStateFileDirPath = [pithosStateFilePath stringByDeletingLastPathComponent];
289 NSFileManager *fileManager = [NSFileManager defaultManager];
291 BOOL fileExists = [fileManager fileExistsAtPath:pithosStateFileDirPath isDirectory:&isDirectory];
292 NSError *error = nil;
293 if (fileExists && !isDirectory)
294 [fileManager removeItemAtPath:pithosStateFileDirPath error:&error];
295 if (!error && !fileExists)
296 [fileManager createDirectoryAtPath:pithosStateFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
298 // pithosStateFilePath = nil;
299 // XXX create a dir using mktmps?
301 return [[pithosStateFilePath copy] autorelease];
304 - (NSString *)tempDownloadsDirPath {
305 if (!tempDownloadsDirPath) {
306 tempDownloadsDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
307 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
308 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads",
309 pithosAccount.uniqueName]] retain];
310 NSFileManager *fileManager = [NSFileManager defaultManager];
312 BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
313 NSError *error = nil;
314 if (fileExists && !isDirectory)
315 [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
316 if (!error && !fileExists)
317 [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
319 // tempDownloadsDirPath = nil;
320 // XXX create a dir using mktmps?
322 return tempDownloadsDirPath;
325 - (NSString *)tempTrashDirPath {
326 if (!tempTrashDirPath) {
327 tempTrashDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
328 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
329 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash",
330 pithosAccount.uniqueName]] retain];
331 NSFileManager *fileManager = [NSFileManager defaultManager];
333 BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
334 NSError *error = nil;
335 if (fileExists && !isDirectory)
336 [fileManager removeItemAtPath:tempTrashDirPath error:&error];
337 if (!error && !fileExists)
338 [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
340 // tempTrashDirPath = nil;
341 // XXX create a dir using mktmps?
343 return tempTrashDirPath;
346 - (void)setDirectoryPath:(NSString *)aDirectoryPath {
347 if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
349 [self resetLocalStateWithAll:YES];
350 [directoryPath release];
351 directoryPath = [aDirectoryPath copy];
355 - (void)setAccountsDictionary:(NSDictionary *)anAccountsDictionary {
356 if (anAccountsDictionary && ![anAccountsDictionary isEqualToDictionary:accountsDictionary]) {
357 BOOL reset = (accountsDictionary != nil);
361 [accountsDictionary release];
362 accountsDictionary = [anAccountsDictionary copy];
364 accountsCount = [accountsDictionary count];
365 self.accountsNames = [NSMutableArray arrayWithCapacity:accountsCount];
366 self.accountsPithosContainers = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
367 NSDictionary *containersDictionary = [accountsDictionary objectForKey:@""];
368 if (containersDictionary && [containersDictionary count]) {
369 NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
370 for (NSString *containerName in containersDictionary) {
371 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
372 pithosContainer.name = containerName;
373 [pithosContainers addObject:pithosContainer];
375 [accountsNames addObject:@""];
376 [accountsPithosContainers setObject:pithosContainers forKey:@""];
378 for (NSString *accountName in accountsDictionary) {
379 NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
380 if (![accountsNames containsObject:accountName] && [containersDictionary count]) {
381 NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
382 for (NSString *containerName in containersDictionary) {
383 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
384 pithosContainer.name = containerName;
385 [pithosContainers addObject:pithosContainer];
387 [accountsNames addObject:accountName];
388 [accountsPithosContainers setObject:pithosContainers forKey:accountName];
393 [self resetLocalStateWithAll:NO];
397 - (void)setPithos:(ASIPithos *)aPithos {
399 pithos = [[ASIPithos pithos] retain];
400 pithos.authUser = [[aPithos.authUser copy] autorelease];
401 pithos.authToken = [[aPithos.authToken copy] autorelease];
402 pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
403 pithos.authURL = [[aPithos.authURL copy] autorelease];
404 pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
407 (![aPithos.authUser isEqualToString:pithos.authUser] ||
408 ![aPithos.authToken isEqualToString:pithos.authToken] ||
409 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
411 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
412 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
413 [self resetLocalStateWithAll:YES];
414 pithos.authUser = [[aPithos.authUser copy] autorelease];
415 pithos.authToken = [[aPithos.authToken copy] autorelease];
416 pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
417 pithos.authURL = [[aPithos.authURL copy] autorelease];
418 pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
423 #pragma mark Helper Methods
425 - (BOOL)createSyncDirectory:(NSString *)dirPath {
426 NSFileManager *fileManager = [NSFileManager defaultManager];
428 NSError *error = nil;
429 if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory]) {
430 if (![fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
431 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
432 message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'",
437 } else if (!isDirectory) {
438 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
439 message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'",
447 - (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
448 if ([accountName isEqualToString:@""])
449 return [directoryPath stringByAppendingPathComponent:containerName];
451 return [[[directoryPath stringByAppendingPathComponent:@"shared to me"]
452 stringByAppendingPathComponent:accountName]
453 stringByAppendingPathComponent:containerName];
456 - (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
457 if ([accountName isEqualToString:@""])
458 return containerName;
460 return [[@"shared to me" stringByAppendingPathComponent:accountName] stringByAppendingPathComponent:containerName];
466 - (void)syncOperationStarted {
467 @synchronized(self) {
468 syncOperationCount++;
472 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
473 @synchronized(self) {
474 if (!operationSuccessfull)
475 syncIncomplete = YES;
476 if (syncOperationCount == 0) {
477 // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
478 DLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
481 syncOperationCount--;
482 if (syncOperationCount == 0) {
483 if (!syncIncomplete) {
484 self.lastCompletedSync = [NSDate date];
485 dispatch_async(dispatch_get_main_queue(), ^{
486 [activityFacility startAndEndActivityWithType:PithosActivityOther
487 message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]
488 pithosAccount:pithosAccount];
492 [self emptyTempTrash];
493 if (newSyncRequested && daemonActive)
500 @synchronized(self) {
501 return ((syncOperationCount > 0) && daemonActive);
506 @synchronized(self) {
507 if ([self isSyncing])
513 @synchronized(self) {
514 if ([self isSyncing]) {
515 // If at least one operation is running return
516 newSyncRequested = YES;
518 } else if (daemonActive && accountsCount) {
519 // The first operation is the server listing
520 [self syncOperationStarted];
521 newSyncRequested = NO;
529 if (![self createSyncDirectory:directoryPath]) {
530 [self syncOperationFinishedWithSuccess:NO];
533 for (NSString *accountName in accountsNames) {
534 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
535 if (![self createSyncDirectory:[self dirPathForAccount:accountName container:pithosContainer.name]]) {
536 [self syncOperationFinishedWithSuccess:NO];
542 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
543 for (NSString *accountName in accountsNames) {
544 [remoteObjects setObject:[NSMutableDictionary dictionary] forKey:accountName];
548 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
549 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
550 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
551 containerName:pithosContainer.name
560 ifModifiedSince:pithosContainer.lastModified];
561 if (![accountName isEqualToString:@""])
562 [containerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
563 containerRequest.delegate = self;
564 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
565 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
566 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther
567 message:@"Sync: Getting server listing"
568 pithosAccount:pithosAccount];
569 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
570 activity, @"activity",
571 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage",
572 @"Sync: Getting server listing (failed)", @"failedActivityMessage",
573 @"Sync: Getting server listing (finished)", @"finishedActivityMessage",
574 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
575 [NSNumber numberWithUnsignedInteger:10], @"retries",
576 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector",
577 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector",
579 [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
582 - (void)emptyTempTrash {
584 NSString *trashDirPath = self.tempTrashDirPath;
586 NSFileManager *fileManager = [NSFileManager defaultManager];
587 NSError *error = nil;
588 // NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
589 NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
591 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
592 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath]
596 if ([subPaths count]) {
597 // NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
598 // for (NSString *subPath in subPaths) {
599 // [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
601 // [self syncOperationStarted];
602 // [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
604 // [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
605 // message:@"Cannot move files to Trash"
608 // [self syncOperationFinishedWithSuccess:YES];
610 for (NSString *subPath in subPaths) {
611 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
613 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
614 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
615 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
623 - (BOOL)moveToTempTrashFile:(NSString *)filePath
624 accountName:(NSString *)accountName
625 pithosContainer:(ASIPithosContainer *)pithosContainer {
627 if (!self.tempTrashDirPath)
629 NSFileManager *fileManager = [NSFileManager defaultManager];
631 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
632 NSError *error = nil;
633 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
634 NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath withString:self.tempTrashDirPath];
635 NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
636 if (fileExists && isDirectory) {
637 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
639 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
640 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath]
644 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
645 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
646 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
650 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
651 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
652 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
653 filePath, newFilePath]
657 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
659 currentState.filePath = newFilePath;
660 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
661 [currentLocalObjectStates removeObjectForKey:filePath];
663 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
664 blockHash:pithosContainer.blockHash
665 blockSize:pithosContainer.blockSize]
668 for (NSString *subPath in subPaths) {
669 NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
670 NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath
671 withString:self.tempTrashDirPath];
672 currentState = [currentLocalObjectStates objectForKey:subFilePath];
674 currentState.filePath = newSubFilePath;
675 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
676 [currentLocalObjectStates removeObjectForKey:subFilePath];
678 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
679 blockHash:pithosContainer.blockHash
680 blockSize:pithosContainer.blockSize]
681 forKey:newSubFilePath];
684 } else if (fileExists && !isDirectory) {
685 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
686 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
687 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
691 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
692 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
693 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
694 filePath, newFilePath]
698 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
700 currentState.filePath = newFilePath;
701 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
702 [currentLocalObjectStates removeObjectForKey:filePath];
704 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
705 blockHash:pithosContainer.blockHash
706 blockSize:pithosContainer.blockSize]
714 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
715 if ([hash length] != 64)
718 PithosLocalObjectState *localState;
719 NSFileManager *fileManager = [NSFileManager defaultManager];
721 NSError *error = nil;
722 for (NSString *localFilePath in currentLocalObjectStates) {
723 localState = [currentLocalObjectStates objectForKey:localFilePath];
724 if (!localState.isDirectory && [hash isEqualToString:localState.hash] &&
725 [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
726 if ([localFilePath hasPrefix:directoryPath]) {
727 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
728 [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
729 message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
730 localFilePath, filePath]
735 } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
736 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
737 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
738 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
739 localFilePath, filePath]
742 localState.filePath = filePath;
743 [currentLocalObjectStates setObject:localState forKey:filePath];
744 [currentLocalObjectStates removeObjectForKey:localFilePath];
754 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
755 localFilePath:(NSString *)filePath
756 accountName:(NSString *)accountName
757 pithosContainer:(ASIPithosContainer *)pithosContainer {
759 NSFileManager *fileManager = [NSFileManager defaultManager];
762 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
763 NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
764 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
765 objectForKey:pithosContainer.name];
766 PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
767 // Remote updated info
768 NSError *remoteError;
769 BOOL remoteIsDirectory;
770 BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos
771 containerName:pithosContainer.name
772 objectName:object.name
774 isDirectory:&remoteIsDirectory
775 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
776 if (!object || !object.objectHash) {
777 // Delete local object
778 if (![accountName isEqualToString:@""])
779 // If "shared to me" skip
781 if (remoteObjectExists) {
782 // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
783 syncIncomplete = YES;
785 DLog(@"Sync::delete local object: %@", filePath);
786 if (!fileExists || [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer]) {
787 dispatch_async(dispatch_get_main_queue(), ^{
788 [activityFacility startAndEndActivityWithType:PithosActivityOther
789 message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)",
790 pithosContainer.name, object.name]
791 pithosAccount:pithosAccount];
793 [containerStoredLocalObjectStates removeObjectForKey:object.name];
794 [self saveLocalState];
796 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
797 // Create local directory object
798 if (!remoteObjectExists || !remoteIsDirectory) {
799 // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
800 syncIncomplete = YES;
803 DLog(@"Sync::create local directory object: %@", filePath);
804 BOOL directoryCreated = NO;
805 if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
806 DLog(@"Sync::local directory object doesn't exist: %@", filePath);
808 if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
809 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
810 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
813 directoryCreated = YES;
814 storedState.filePath = filePath;
815 storedState.isDirectory = YES;
816 [self saveLocalState];
819 DLog(@"Sync::local directory object exists: %@", filePath);
820 directoryCreated = YES;
822 if (directoryCreated)
823 dispatch_async(dispatch_get_main_queue(), ^{
824 [activityFacility startAndEndActivityWithType:PithosActivityOther
825 message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)",
826 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
828 pithosAccount:pithosAccount];
830 } else if (object.bytes == 0) {
831 // Create local object with zero length
832 if (!remoteObjectExists || remoteIsDirectory) {
833 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
834 syncIncomplete = YES;
837 DLog(@"Sync::create local zero length object: %@", filePath);
838 BOOL fileCreated = NO;
840 ((isDirectory || [PithosUtilities bytesOfFile:filePath]) &&
841 [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
842 DLog(@"Sync::local zero length object doesn't exist: %@", filePath);
843 // Create directory of the file, if it doesn't exist
845 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
846 dispatch_async(dispatch_get_main_queue(), ^{
847 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
848 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
853 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
854 [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
855 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
859 storedState.filePath = filePath;
860 storedState.hash = object.objectHash;
861 storedState.tmpFilePath = nil;
862 [self saveLocalState];
865 DLog(@"Sync::local zero length object exists: %@", filePath);
869 dispatch_async(dispatch_get_main_queue(), ^{
870 [activityFacility startAndEndActivityWithType:PithosActivityOther
871 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
872 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
874 pithosAccount:pithosAccount];
876 } else if (storedState.tmpFilePath == nil) {
877 // Create new local object
878 if (!remoteObjectExists || remoteIsDirectory) {
879 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
880 syncIncomplete = YES;
883 // Create directory of the file, if it doesn't exist
885 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
886 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
887 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
890 // Check first if a local copy exists
891 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
892 storedState.filePath = filePath;
893 storedState.hash = object.objectHash;
894 [self saveLocalState];
895 dispatch_async(dispatch_get_main_queue(), ^{
896 [activityFacility startAndEndActivityWithType:PithosActivityOther
897 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
898 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
900 pithosAccount:pithosAccount];
903 [self syncOperationStarted];
904 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
905 containerName:pithosContainer.name
908 blockSize:pithosContainer.blockSize];
909 if (![accountName isEqualToString:@""])
910 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
911 objectRequest.delegate = self;
912 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
913 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
914 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
915 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
916 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
917 message:[messagePrefix stringByAppendingString:@" (0%%)"]
918 totalBytes:object.bytes
920 pithosAccount:pithosAccount];
921 dispatch_async(dispatch_get_main_queue(), ^{
922 [activityFacility updateActivity:activity withMessage:activity.message];
924 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
925 accountName, @"accountName",
926 pithosContainer, @"pithosContainer",
927 object, @"pithosObject",
928 messagePrefix, @"messagePrefix",
929 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks",
930 [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
931 filePath, @"filePath",
932 activity, @"activity",
933 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
934 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
935 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
936 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
937 [NSNumber numberWithUnsignedInteger:10], @"retries",
938 NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector",
939 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
941 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
942 [activityFacility updateActivity:activity
943 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
944 messagePrefix, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
945 totalBytes:activity.totalBytes
946 currentBytes:(activity.currentBytes + size)];
948 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
951 // Resume local object download
952 if (!remoteObjectExists || remoteIsDirectory) {
953 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
954 syncIncomplete = YES;
957 // Create directory of the file, if it doesn't exist
959 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
960 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
961 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
964 // Check first if a local copy exists
965 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
966 storedState.filePath = filePath;
967 storedState.hash = object.objectHash;
968 // Delete incomplete temp download
969 storedState.tmpFilePath = nil;
970 [self saveLocalState];
971 dispatch_async(dispatch_get_main_queue(), ^{
972 [activityFacility startAndEndActivityWithType:PithosActivityOther
973 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
974 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
976 pithosAccount:pithosAccount];
979 [self syncOperationStarted];
980 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
981 containerName:pithosContainer.name
982 objectName:object.name];
983 if (![accountName isEqualToString:@""])
984 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
985 objectRequest.delegate = self;
986 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
987 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
988 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
989 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
990 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
991 message:[messagePrefix stringByAppendingString:@" (0%%)"]
992 totalBytes:object.bytes
994 pithosAccount:pithosAccount];
995 dispatch_async(dispatch_get_main_queue(), ^{
996 [activityFacility updateActivity:activity withMessage:activity.message];
998 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
999 accountName, @"accountName",
1000 pithosContainer, @"pithosContainer",
1001 object, @"pithosObject",
1002 messagePrefix, @"messagePrefix",
1003 filePath, @"filePath",
1004 activity, @"activity",
1005 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1006 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1007 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1008 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1009 [NSNumber numberWithUnsignedInteger:10], @"retries",
1010 NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector",
1011 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1013 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1019 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
1020 object:(ASIPithosObject *)object
1021 localFilePath:(NSString *)filePath
1022 accountName:(NSString *)accountName
1023 pithosContainer:(ASIPithosContainer *)pithosContainer {
1025 [self syncOperationStarted];
1026 NSFileManager *fileManager = [NSFileManager defaultManager];
1028 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1029 if (currentState.isDirectory) {
1030 // Create remote directory object
1031 if (![accountName isEqualToString:@""]) {
1032 if (!object.allowedTo) {
1033 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1034 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1035 while ([objectAncestorName length] && !object.allowedTo) {
1036 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1037 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1040 if (![object.allowedTo isEqualToString:@"write"]) {
1041 // If read-only "shared to me" skip
1042 [self syncOperationFinishedWithSuccess:YES];
1046 if (!fileExists || !isDirectory) {
1047 // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
1048 [self syncOperationFinishedWithSuccess:NO];
1051 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
1052 containerName:pithosContainer.name
1053 objectName:object.name
1055 contentType:@"application/directory"
1057 contentDisposition:nil
1060 isPublic:ASIPithosObjectRequestPublicIgnore
1062 data:[NSData data]];
1063 if (![accountName isEqualToString:@""])
1064 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1065 objectRequest.delegate = self;
1066 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1067 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1068 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Creating directory '%@/%@'",
1069 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1070 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1071 message:messagePrefix
1072 pithosAccount:pithosAccount];
1073 dispatch_async(dispatch_get_main_queue(), ^{
1074 [activityFacility updateActivity:activity withMessage:activity.message];
1076 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1077 accountName, @"accountName",
1078 pithosContainer, @"pithosContainer",
1079 object, @"pithosObject",
1080 messagePrefix, @"messagePrefix",
1081 activity, @"activity",
1082 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1083 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1084 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1085 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1086 [NSNumber numberWithUnsignedInteger:10], @"retries",
1087 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1088 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1090 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1091 } else if (![currentState exists]) {
1092 // Delete remote object
1093 if (![accountName isEqualToString:@""]) {
1094 // If "shared to me" skip
1095 [self syncOperationFinishedWithSuccess:YES];
1099 // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
1100 syncIncomplete = YES;
1102 if ([pithosContainer.name isEqualToString:@"trash"]) {
1104 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
1105 containerName:pithosContainer.name
1106 objectName:object.name];
1107 objectRequest.delegate = self;
1108 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1109 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1110 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Deleting '%@/%@'",
1111 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1112 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1113 message:messagePrefix
1114 pithosAccount:pithosAccount];
1115 dispatch_async(dispatch_get_main_queue(), ^{
1116 [activityFacility updateActivity:activity withMessage:activity.message];
1118 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1119 accountName, @"accountName",
1120 pithosContainer, @"pithosContainer",
1121 object, @"pithosObject",
1122 messagePrefix, @"messagePrefix",
1123 activity, @"activity",
1124 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1125 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1126 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1127 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1128 [NSNumber numberWithUnsignedInteger:10], @"retries",
1129 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
1130 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1132 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1134 // Move to container trash
1136 if ([PithosUtilities isContentTypeDirectory:object.contentType])
1137 safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
1139 safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
1141 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1142 containerName:pithosContainer.name
1143 objectName:object.name
1144 destinationContainerName:@"trash"
1145 destinationObjectName:safeName
1147 if (objectRequest) {
1148 objectRequest.delegate = self;
1149 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1150 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1151 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'",
1152 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1153 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1154 message:messagePrefix
1155 pithosAccount:pithosAccount];
1156 dispatch_async(dispatch_get_main_queue(), ^{
1157 [activityFacility updateActivity:activity withMessage:activity.message];
1159 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1160 accountName, @"accountName",
1161 pithosContainer, @"pithosContainer",
1162 object, @"pithosObject",
1163 messagePrefix, @"messagePrefix",
1164 activity, @"activity",
1165 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1166 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1167 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1168 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1169 [NSNumber numberWithUnsignedInteger:10], @"retries",
1170 NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector",
1171 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1173 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1175 [self syncOperationFinishedWithSuccess:NO];
1178 [self syncOperationFinishedWithSuccess:NO];
1182 // Upload file to remote object
1183 if (![accountName isEqualToString:@""]) {
1184 if (!object.allowedTo) {
1185 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1186 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1187 while ([objectAncestorName length] && !object.allowedTo) {
1188 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1189 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1192 if (![object.allowedTo isEqualToString:@"write"]) {
1193 // If read-only "shared to me" skip
1194 [self syncOperationFinishedWithSuccess:YES];
1198 if (!fileExists || isDirectory) {
1199 // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
1200 [self syncOperationFinishedWithSuccess:NO];
1203 NSError *error = nil;
1204 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1205 if (object.contentType == nil)
1206 object.contentType = @"application/octet-stream";
1209 DLog(@"contentType detection error: %@", error);
1211 NSArray *hashes = nil;
1212 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1213 containerName:pithosContainer.name
1214 objectName:object.name
1215 contentType:object.contentType
1216 blockSize:pithosContainer.blockSize
1217 blockHash:pithosContainer.blockHash
1221 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
1222 if (objectRequest) {
1223 objectRequest.delegate = self;
1224 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1225 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1226 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Uploading '%@/%@'",
1227 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1228 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1229 message:[messagePrefix stringByAppendingString:@" (0%%)"]
1230 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1232 pithosAccount:pithosAccount];
1233 dispatch_async(dispatch_get_main_queue(), ^{
1234 [activityFacility updateActivity:activity withMessage:activity.message];
1236 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1237 [NSDictionary dictionaryWithObjectsAndKeys:
1238 accountName, @"accountName",
1239 pithosContainer, @"pithosContainer",
1240 object, @"pithosObject",
1241 messagePrefix, @"messagePrefix",
1242 filePath, @"filePath",
1244 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1245 activity, @"activity",
1246 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1247 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1248 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1249 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1250 [NSNumber numberWithUnsignedInteger:10], @"retries",
1251 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1252 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1254 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1256 [self syncOperationFinishedWithSuccess:NO];
1263 #pragma mark ASIHTTPRequestDelegate
1265 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1266 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1267 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1268 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1269 object:request] autorelease];
1270 operation.completionBlock = ^{
1272 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1273 dispatch_async(dispatch_get_main_queue(), ^{
1274 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1275 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1277 [self syncOperationFinishedWithSuccess:NO];
1281 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1282 [callbackQueue addOperation:operation];
1285 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1286 if (request.isCancelled) {
1287 // Request has been cancelled
1288 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1289 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1290 withObject:request];
1292 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1293 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1294 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1295 object:request] autorelease];
1296 operation.completionBlock = ^{
1298 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1299 dispatch_async(dispatch_get_main_queue(), ^{
1300 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1301 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1303 [self syncOperationFinishedWithSuccess:NO];
1307 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1308 [callbackQueue addOperation:operation];
1312 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1314 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1315 DLog(@"Sync::list request finished: %@", containerRequest.url);
1316 if (operation.isCancelled) {
1317 [self listRequestFailed:containerRequest];
1318 } else if ((containerRequest.responseStatusCode == 200) ||
1319 (containerRequest.responseStatusCode == 304) ||
1320 (containerRequest.responseStatusCode == 403)) {
1321 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
1322 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1323 if (containerRequest.responseStatusCode == 200) {
1324 NSArray *someObjects = [containerRequest objects];
1325 if (objects == nil) {
1326 objects = [[NSMutableArray alloc] initWithArray:someObjects];
1328 [objects addObjectsFromArray:someObjects];
1330 if ([someObjects count] < 10000) {
1331 pithosContainer.blockHash = [containerRequest blockHash];
1332 pithosContainer.blockSize = [containerRequest blockSize];
1333 pithosContainer.lastModified = [containerRequest lastModified];
1334 NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1335 for (ASIPithosObject *object in objects) {
1336 [containerRemoteObjects setObject:object forKey:object.name];
1338 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1342 // Do an additional request to fetch more objects
1343 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1344 containerName:pithosContainer.name
1346 marker:[[someObjects lastObject] name]
1353 ifModifiedSince:pithosContainer.lastModified];
1354 if (![accountName isEqualToString:@""])
1355 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1356 newContainerRequest.delegate = self;
1357 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1358 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1359 newContainerRequest.userInfo = containerRequest.userInfo;
1360 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1361 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1364 } else if (containerRequest.responseStatusCode == 304) {
1365 NSMutableDictionary *containerRemoteObjects = [[previousRemoteObjects objectForKey:accountName]
1366 objectForKey:pithosContainer.name];
1367 if (containerRemoteObjects)
1368 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1369 } else if (containerRequest.responseStatusCode == 403) {
1370 [[remoteObjects objectForKey:accountName] setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
1374 if (containersIndex == [[accountsPithosContainers objectForKey:accountName] count]) {
1376 containersIndex = 0;
1378 if (accountsIndex < accountsCount) {
1379 accountName = [accountsNames objectAtIndex:accountsIndex];
1380 pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1381 // Do a request for the next container
1382 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1383 containerName:pithosContainer.name
1392 ifModifiedSince:pithosContainer.lastModified];
1393 if (![accountName isEqualToString:@""])
1394 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1395 newContainerRequest.delegate = self;
1396 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1397 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1398 newContainerRequest.userInfo = containerRequest.userInfo;
1399 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1400 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1403 self.previousRemoteObjects = remoteObjects;
1404 // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
1406 if (operation.isCancelled) {
1407 [self listRequestFailed:containerRequest];
1411 dispatch_async(dispatch_get_main_queue(), ^{
1412 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1413 withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1415 NSFileManager *fileManager = [NSFileManager defaultManager];
1417 // Compute current state of legal existing local objects
1418 // and add an empty stored state for legal new local objects since last sync
1419 self.currentLocalObjectStates = [NSMutableDictionary dictionary];
1420 for (NSString *accountName in accountsNames) {
1421 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1422 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1423 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1424 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1425 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1426 objectForKey:pithosContainer.name];
1427 NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath];
1428 for (NSString *objectName in dirEnumerator) {
1429 objectName = [objectName precomposedStringWithCanonicalMapping];
1430 if (operation.isCancelled) {
1431 operation.completionBlock = nil;
1432 [self saveLocalState];
1433 [self syncOperationFinishedWithSuccess:NO];
1437 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1438 NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1440 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1441 if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) {
1442 [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error"
1443 message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.",
1444 containerDirectoryPath, pithosAccount.name]
1446 pithosAccount.syncActive = NO;
1448 } else if (fileExists) {
1449 if (skipHidden && [[objectName lastPathComponent] hasPrefix:@"."]) {
1450 // Skip hidden directory and its descendants, or hidden file
1452 [dirEnumerator skipDescendants];
1453 // Remove stored state if any
1454 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1456 } else if ([[objectName pathComponents] count] == 1) {
1457 if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) {
1458 // Skip excluded directory and its descendants, or root file with same name
1460 [dirEnumerator skipDescendants];
1461 // Remove stored state if any
1462 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1464 } else if (!isDirectory && containerExcludeRootFiles) {
1465 // Skip excluded root file
1466 // Remove stored state if any
1467 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1471 // Include local object
1472 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
1473 if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1474 // New or modified existing local object, compute current state
1475 if (!storedLocalObjectState)
1476 // For new local object, also create empty stored state
1477 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1478 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
1479 blockHash:pithosContainer.blockHash
1480 blockSize:pithosContainer.blockSize]
1483 // Local object hasn't changed, set stored state also to current
1484 [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1488 [self saveLocalState];
1492 if (operation.isCancelled) {
1493 operation.completionBlock = nil;
1494 [self syncOperationFinishedWithSuccess:NO];
1498 // Add an empty stored state for legal new remote objects since last sync
1499 for (NSString *accountName in accountsNames) {
1500 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1501 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1502 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1503 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1504 objectForKey:pithosContainer.name];
1505 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1506 for (NSString *objectName in containerRemoteObjects) {
1507 if (operation.isCancelled) {
1508 operation.completionBlock = nil;
1509 [self saveLocalState];
1510 [self syncOperationFinishedWithSuccess:NO];
1514 ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName];
1515 NSString *localObjectName;
1516 if ([object.name hasSuffix:@"/"])
1517 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1519 localObjectName = [NSString stringWithString:object.name];
1520 NSArray *pathComponents = [localObjectName pathComponents];
1522 BOOL skipObject = NO;
1523 for (NSString *pathComponent in pathComponents) {
1524 if ([pathComponent hasPrefix:@"."]) {
1525 // Skip hidden directory and its descendants, or hidden file
1526 // Remove stored state if any
1527 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1535 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1536 // Skip excluded directory object and its descendants, or root file object with same name
1537 // Remove stored state if any
1538 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1540 } else if (containerExcludeRootFiles &&
1541 ([pathComponents count] == 1) &&
1542 ![PithosUtilities isContentTypeDirectory:object.contentType]) {
1543 // Skip root file object
1544 // Remove stored state if any
1545 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1548 if (![containerStoredLocalObjectStates objectForKey:object.name])
1549 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name];
1551 [self saveLocalState];
1555 if (operation.isCancelled) {
1556 operation.completionBlock = nil;
1557 [self syncOperationFinishedWithSuccess:NO];
1561 // For each stored state compare with current and remote state
1562 // Stored states of local objects that have been deleted,
1563 // haven't been checked for legality (only existing local remote objects)
1564 // These should be identified and skipped
1565 for (NSString *accountName in accountsNames) {
1566 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1567 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1568 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1569 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1570 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1571 objectForKey:pithosContainer.name];
1572 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1573 for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1574 if (operation.isCancelled) {
1575 operation.completionBlock = nil;
1576 [self syncOperationFinishedWithSuccess:NO];
1580 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1581 if ([objectName hasSuffix:@"/"])
1582 filePath = [filePath stringByAppendingString:@":"];
1583 ASIPithosObject *object = [ASIPithosObject object];
1584 object.name = objectName;
1585 DLog(@"Sync::object name: %@", object.name);
1587 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
1588 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1589 if (!currentLocalObjectState) {
1590 // The stored state corresponds to a remote or deleted local object, that's why there is no current state
1591 // In the latter case it must be checked for legality, which can be determined by its stored state
1592 // If it existed locally, but was deleted, state.exists is true,
1593 // else if the stored state is an empty state that was created due to the server object, state.exists is false
1594 if (storedLocalObjectState.exists) {
1595 NSString *localObjectName;
1596 if ([object.name hasSuffix:@"/"])
1597 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1599 localObjectName = [NSString stringWithString:object.name];
1600 NSArray *pathComponents = [localObjectName pathComponents];
1602 BOOL skipObject = NO;
1603 for (NSString *pathComponent in pathComponents) {
1604 if ([pathComponent hasPrefix:@"."]) {
1605 // Skip hidden directory and its descendants, or hidden file
1606 // Remove stored state if any
1607 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1608 [self saveLocalState];
1616 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1617 // Skip excluded directory object and its descendants, or root file object with same name
1618 // Remove stored state
1619 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1620 [self saveLocalState];
1622 } else if (containerExcludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) {
1623 // Skip root file object
1624 // Remove stored state
1625 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1626 [self saveLocalState];
1630 // There is also the off case that a local object has been created in the meantime
1631 // This call works in any case, existent or non-existent local object
1632 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
1633 blockHash:pithosContainer.blockHash
1634 blockSize:pithosContainer.blockSize];
1637 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1638 ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
1640 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1641 remoteObjectState.isDirectory = YES;
1643 remoteObjectState.hash = remoteObject.objectHash;
1647 BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1648 BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1649 DLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1650 if (!localStateHasChanged) {
1651 // Local state hasn't changed
1652 if (serverStateHasChanged) {
1653 // Server state has changed
1654 // Update local state to match that of the server
1655 object.bytes = remoteObject.bytes;
1656 object.version = remoteObject.version;
1657 object.contentType = remoteObject.contentType;
1658 object.objectHash = remoteObject.objectHash;
1659 [self updateLocalStateWithObject:object localFilePath:filePath
1660 accountName:accountName pithosContainer:pithosContainer];
1661 } else if (!remoteObject && ![currentLocalObjectState exists]) {
1662 // Server state hasn't changed
1663 // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1664 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1665 [self saveLocalState];
1668 // Local state has changed
1669 if (!serverStateHasChanged) {
1670 // Server state hasn't changed
1671 if (currentLocalObjectState.isDirectory)
1672 object.contentType = @"application/directory";
1674 object.objectHash = currentLocalObjectState.hash;
1675 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1676 accountName:accountName pithosContainer:pithosContainer];
1678 // Server state has also changed
1679 if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1680 // Both did the same change (directory)
1681 storedLocalObjectState.filePath = filePath;
1682 storedLocalObjectState.isDirectory = YES;
1683 [self saveLocalState];
1684 } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1685 // Both did the same change (object edit or delete)
1686 if (![remoteObjectState exists]) {
1687 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1689 storedLocalObjectState.filePath = filePath;
1690 storedLocalObjectState.hash = remoteObjectState.hash;
1692 [self saveLocalState];
1694 // Conflict, we ask the user which change to keep
1695 NSString *informativeText;
1696 NSString *firstButtonText;
1697 NSString *secondButtonText;
1699 if (![remoteObjectState exists]) {
1700 // Remote object has been deleted
1701 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.",
1702 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name ];
1703 firstButtonText = @"Delete local file";
1704 secondButtonText = @"Upload file to server";
1705 } else if (![currentLocalObjectState exists]) {
1706 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.",
1707 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1708 firstButtonText = @"Download file from server";
1709 secondButtonText = @"Delete file on server";
1711 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.",
1712 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1713 firstButtonText = @"Keep server version";
1714 secondButtonText = @"Keep local version";
1716 __block NSInteger choice;
1717 dispatch_sync(dispatch_get_main_queue(), ^{
1718 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1719 [alert setMessageText:@"Conflict"];
1720 [alert setInformativeText:informativeText];
1721 [alert addButtonWithTitle:firstButtonText];
1722 [alert addButtonWithTitle:secondButtonText];
1723 [alert addButtonWithTitle:@"Do nothing"];
1724 choice = [alert runModal];
1726 if (choice == NSAlertFirstButtonReturn) {
1727 object.bytes = remoteObject.bytes;
1728 object.version = remoteObject.version;
1729 object.contentType = remoteObject.contentType;
1730 object.objectHash = remoteObject.objectHash;
1731 [self updateLocalStateWithObject:object localFilePath:filePath
1732 accountName:accountName pithosContainer:pithosContainer];
1733 } if (choice == NSAlertSecondButtonReturn) {
1734 if (currentLocalObjectState.isDirectory)
1735 object.contentType = @"application/directory";
1737 object.objectHash = currentLocalObjectState.hash;
1738 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1739 accountName:accountName pithosContainer:pithosContainer];
1747 [self syncOperationFinishedWithSuccess:YES];
1749 [self listRequestFailed:containerRequest];
1754 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1756 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1757 if (operation.isCancelled) {
1762 if (containerRequest.isCancelled) {
1763 dispatch_async(dispatch_get_main_queue(), ^{
1764 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1765 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1769 [self syncOperationFinishedWithSuccess:NO];
1772 // If the server listing fails, the sync should start over, so just retrying is enough
1773 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1775 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1776 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1777 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1779 dispatch_async(dispatch_get_main_queue(), ^{
1780 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1781 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1785 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1786 [self syncOperationFinishedWithSuccess:NO];
1791 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1793 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1794 DLog(@"Sync::download object block finished: %@", objectRequest.url);
1795 if (operation.isCancelled) {
1796 [self requestFailed:objectRequest];
1797 } else if (objectRequest.responseStatusCode == 206) {
1798 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1799 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1800 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1801 NSFileManager *fileManager = [NSFileManager defaultManager];
1803 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1805 NSString *downloadsDirPath = self.tempDownloadsDirPath;
1806 if (!downloadsDirPath) {
1807 dispatch_async(dispatch_get_main_queue(), ^{
1808 [activityFacility endActivity:activity
1809 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1811 [self syncOperationFinishedWithSuccess:NO];
1815 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1816 objectForKey:pithosContainer.name]
1817 objectForKey:object.name];
1818 if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1819 NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1820 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1821 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1822 strcpy(tempFileNameCString, tempFileTemplateCString);
1823 int fileDescriptor = mkstemp(tempFileNameCString);
1824 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1825 free(tempFileNameCString);
1826 if (fileDescriptor == -1) {
1827 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
1828 message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]
1830 dispatch_async(dispatch_get_main_queue(), ^{
1831 [activityFacility endActivity:activity
1832 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1834 [self syncOperationFinishedWithSuccess:NO];
1837 close(fileDescriptor);
1838 storedState.tmpFilePath = tempFilePath;
1839 [self saveLocalState];
1842 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1843 NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1844 [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
1845 [tempFileHandle writeData:[objectRequest responseData]];
1846 [tempFileHandle closeFile];
1848 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1849 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1850 if (missingBlockIndex == NSNotFound) {
1851 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1852 NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1853 if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath
1854 accountName:accountName
1855 pithosContainer:pithosContainer]) {
1856 dispatch_async(dispatch_get_main_queue(), ^{
1857 [activityFacility endActivity:activity
1858 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1860 [self syncOperationFinishedWithSuccess:NO];
1862 } else if (![fileManager fileExistsAtPath:dirPath]) {
1863 // File doesn't exist but also the containing directory doesn't exist
1864 // In most cases this should have been resolved as an update of the corresponding local object,
1865 // but it never hurts to check
1867 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1869 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1870 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
1872 dispatch_async(dispatch_get_main_queue(), ^{
1873 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1874 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1876 [self syncOperationFinishedWithSuccess:NO];
1880 // Move file from tmp download
1882 [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1884 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
1885 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath]
1887 dispatch_async(dispatch_get_main_queue(), ^{
1888 [activityFacility endActivity:activity
1889 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1891 [self syncOperationFinishedWithSuccess:NO];
1894 dispatch_async(dispatch_get_main_queue(), ^{
1895 [activityFacility endActivity:activity
1896 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1897 totalBytes:activity.totalBytes
1898 currentBytes:activity.totalBytes];
1901 storedState.filePath = filePath;
1902 storedState.hash = object.objectHash;
1903 storedState.tmpFilePath = nil;
1904 [self saveLocalState];
1905 [self syncOperationFinishedWithSuccess:YES];
1908 if (newSyncRequested || syncLate || operation.isCancelled) {
1909 [self requestFailed:objectRequest];
1911 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1912 containerName:pithosContainer.name
1914 blockIndex:missingBlockIndex
1915 blockSize:pithosContainer.blockSize];
1916 if (![accountName isEqualToString:@""])
1917 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1918 newObjectRequest.delegate = self;
1919 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1920 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1921 newObjectRequest.userInfo = objectRequest.userInfo;
1922 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1923 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1924 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1925 [activityFacility updateActivity:activity
1926 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1927 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
1928 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1929 totalBytes:activity.totalBytes
1930 currentBytes:(activity.currentBytes + size)];
1932 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1935 } else if (objectRequest.responseStatusCode == 412) {
1936 // The object has changed on the server
1937 dispatch_async(dispatch_get_main_queue(), ^{
1938 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1939 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1941 [self syncOperationFinishedWithSuccess:NO];
1943 [self requestFailed:objectRequest];
1948 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1950 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1951 DLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1952 if (operation.isCancelled) {
1953 [self requestFailed:objectRequest];
1954 } else if (objectRequest.responseStatusCode == 200) {
1955 if (newSyncRequested || syncLate || operation.isCancelled) {
1956 [self requestFailed:objectRequest];
1958 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1959 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1960 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1961 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1962 objectForKey:pithosContainer.name]
1963 objectForKey:object.name];
1964 if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1965 [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
1966 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1967 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath
1968 blockSize:pithosContainer.blockSize
1969 blockHash:pithosContainer.blockHash
1970 withHashes:[objectRequest hashes]];
1971 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1972 dispatch_async(dispatch_get_main_queue(), ^{
1973 [activityFacility updateActivity:activity
1974 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1975 [objectRequest.userInfo objectForKey:@"messagePrefix"],
1976 (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))]
1977 totalBytes:activity.totalBytes
1978 currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
1981 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1982 containerName:pithosContainer.name
1984 blockIndex:missingBlockIndex
1985 blockSize:pithosContainer.blockSize];
1986 if (![accountName isEqualToString:@""])
1987 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1988 newObjectRequest.delegate = self;
1989 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1990 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1991 newObjectRequest.userInfo = objectRequest.userInfo;
1992 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1993 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1994 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1995 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1996 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1997 [activityFacility updateActivity:activity
1998 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1999 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
2000 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2001 totalBytes:activity.totalBytes
2002 currentBytes:(activity.currentBytes + size)];
2004 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2007 [self requestFailed:objectRequest];
2012 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2014 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2015 DLog(@"Sync::upload directory object finished: %@", objectRequest.url);
2016 if (operation.isCancelled) {
2017 [self requestFailed:objectRequest];
2018 } else if (objectRequest.responseStatusCode == 201) {
2019 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2020 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2021 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2022 storedState.isDirectory = YES;
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)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
2037 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2038 DLog(@"Sync::move object to trash finished: %@", objectRequest.url);
2039 if (operation.isCancelled) {
2040 [self requestFailed:objectRequest];
2041 } else if (objectRequest.responseStatusCode == 201) {
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)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2059 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2060 DLog(@"Sync::delete object finished: %@", objectRequest.url);
2061 if (operation.isCancelled) {
2062 [self requestFailed:objectRequest];
2063 } else if (objectRequest.responseStatusCode == 204) {
2064 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2065 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2066 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2067 [self saveLocalState];
2068 dispatch_async(dispatch_get_main_queue(), ^{
2069 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2070 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2072 [self syncOperationFinishedWithSuccess:YES];
2074 [self requestFailed:objectRequest];
2079 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
2081 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2082 DLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
2083 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
2084 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
2085 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
2086 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
2087 objectForKey:pithosContainer.name]
2088 objectForKey:object.name];
2089 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
2090 NSUInteger totalBytes = activity.totalBytes;
2091 NSUInteger currentBytes = activity.currentBytes;
2092 if (operation.isCancelled) {
2093 [self requestFailed:objectRequest];
2094 } else if (objectRequest.responseStatusCode == 201) {
2095 DLog(@"Sync::object created: %@", objectRequest.url);
2096 storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
2097 storedState.hash = object.objectHash;
2098 [self saveLocalState];
2099 dispatch_async(dispatch_get_main_queue(), ^{
2100 [activityFacility endActivity:activity
2101 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
2102 totalBytes:totalBytes
2103 currentBytes:totalBytes];
2105 [self syncOperationFinishedWithSuccess:YES];
2106 } else if (objectRequest.responseStatusCode == 409) {
2107 if (newSyncRequested || syncLate || operation.isCancelled) {
2108 [self requestFailed:objectRequest];
2110 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
2111 if (iteration == 0) {
2112 DLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
2113 dispatch_async(dispatch_get_main_queue(), ^{
2114 [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
2116 [self syncOperationFinishedWithSuccess:NO];
2119 DLog(@"Sync::object is missing hashes: %@", objectRequest.url);
2120 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
2121 withMissingHashes:[objectRequest hashes]];
2122 if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
2123 currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
2124 dispatch_async(dispatch_get_main_queue(), ^{
2125 [activityFacility updateActivity:activity
2126 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2127 [objectRequest.userInfo objectForKey:@"messagePrefix"],
2128 (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
2129 totalBytes:totalBytes
2130 currentBytes:currentBytes];
2132 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2133 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2134 containerName:pithosContainer.name
2135 blockSize:pithosContainer.blockSize
2136 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
2137 missingBlockIndex:missingBlockIndex
2138 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2139 newContainerRequest.delegate = self;
2140 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2141 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2142 newContainerRequest.userInfo = objectRequest.userInfo;
2143 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
2144 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2145 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2146 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2147 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
2148 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2149 [activityFacility updateActivity:activity
2150 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2151 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2152 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2153 totalBytes:activity.totalBytes
2154 currentBytes:(activity.currentBytes + size)];
2156 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2159 [self requestFailed:objectRequest];
2164 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
2166 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
2167 DLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
2168 if (operation.isCancelled) {
2169 [self requestFailed:containerRequest];
2170 } else if (containerRequest.responseStatusCode == 202) {
2171 NSString *accountName = [containerRequest.userInfo objectForKey:@"accountName"];
2172 ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
2173 ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
2174 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
2175 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
2176 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
2177 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
2178 if (operation.isCancelled) {
2179 [self requestFailed:containerRequest];
2180 } else if (missingBlockIndex == NSNotFound) {
2181 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
2182 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
2183 containerName:pithosContainer.name
2184 objectName:object.name
2185 contentType:object.contentType
2186 blockSize:pithosContainer.blockSize
2187 blockHash:pithosContainer.blockHash
2188 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2191 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2192 newObjectRequest.delegate = self;
2193 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2194 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2195 newObjectRequest.userInfo = containerRequest.userInfo;
2196 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2197 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
2198 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
2199 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
2200 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2202 if (newSyncRequested || syncLate || operation.isCancelled) {
2203 [self requestFailed:containerRequest];
2205 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2206 containerName:pithosContainer.name
2207 blockSize:pithosContainer.blockSize
2208 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2209 missingBlockIndex:missingBlockIndex
2210 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2211 newContainerRequest.delegate = self;
2212 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2213 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2214 newContainerRequest.userInfo = containerRequest.userInfo;
2215 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2216 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2217 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2218 [activityFacility updateActivity:activity
2219 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2220 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2221 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2222 totalBytes:activity.totalBytes
2223 currentBytes:(activity.currentBytes + size)];
2225 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2229 [self requestFailed:containerRequest];
2234 - (void)requestFailed:(ASIPithosRequest *)request {
2236 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
2237 DLog(@"Sync::request failed: %@", request.url);
2238 if (operation.isCancelled)
2240 if (request.isCancelled || newSyncRequested || syncLate) {
2241 dispatch_async(dispatch_get_main_queue(), ^{
2242 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2243 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
2245 [self syncOperationFinishedWithSuccess:NO];
2248 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
2250 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
2251 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
2252 [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
2254 dispatch_async(dispatch_get_main_queue(), ^{
2255 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2256 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
2258 [self syncOperationFinishedWithSuccess:NO];