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 {
130 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
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 {
152 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
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 {
201 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
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 [[[NSString stringWithString:@"shared to me"]
461 stringByAppendingPathComponent:accountName]
462 stringByAppendingPathComponent:containerName];
468 - (void)syncOperationStarted {
469 @synchronized(self) {
470 syncOperationCount++;
474 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
475 @synchronized(self) {
476 if (!operationSuccessfull)
477 syncIncomplete = YES;
478 if (syncOperationCount == 0) {
479 // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
480 NSLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
483 syncOperationCount--;
484 if (syncOperationCount == 0) {
485 if (!syncIncomplete) {
486 self.lastCompletedSync = [NSDate date];
487 dispatch_async(dispatch_get_main_queue(), ^{
488 [activityFacility startAndEndActivityWithType:PithosActivityOther
489 message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]
490 pithosAccount:pithosAccount];
494 [self emptyTempTrash];
495 if (newSyncRequested && daemonActive)
502 @synchronized(self) {
503 return ((syncOperationCount > 0) && daemonActive);
508 @synchronized(self) {
509 if ([self isSyncing])
515 @synchronized(self) {
516 if ([self isSyncing]) {
517 // If at least one operation is running return
518 newSyncRequested = YES;
520 } else if (daemonActive && accountsCount) {
521 // The first operation is the server listing
522 [self syncOperationStarted];
523 newSyncRequested = NO;
531 if (![self createSyncDirectory:directoryPath]) {
532 [self syncOperationFinishedWithSuccess:NO];
535 for (NSString *accountName in accountsNames) {
536 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
537 if (![self createSyncDirectory:[self dirPathForAccount:accountName container:pithosContainer.name]]) {
538 [self syncOperationFinishedWithSuccess:NO];
544 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
545 for (NSString *accountName in accountsNames) {
546 [remoteObjects setObject:[NSMutableDictionary dictionary] forKey:accountName];
550 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
551 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
552 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
553 containerName:pithosContainer.name
562 ifModifiedSince:pithosContainer.lastModified];
563 if (![accountName isEqualToString:@""])
564 [containerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
565 containerRequest.delegate = self;
566 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
567 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
568 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther
569 message:@"Sync: Getting server listing"
570 pithosAccount:pithosAccount];
571 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
572 activity, @"activity",
573 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage",
574 @"Sync: Getting server listing (failed)", @"failedActivityMessage",
575 @"Sync: Getting server listing (finished)", @"finishedActivityMessage",
576 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
577 [NSNumber numberWithUnsignedInteger:10], @"retries",
578 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector",
579 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector",
581 [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
584 - (void)emptyTempTrash {
585 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
586 NSString *trashDirPath = self.tempTrashDirPath;
588 NSFileManager *fileManager = [NSFileManager defaultManager];
589 NSError *error = nil;
590 // NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
591 NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
593 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
594 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath]
599 if ([subPaths count]) {
600 // NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
601 // for (NSString *subPath in subPaths) {
602 // [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
604 // [self syncOperationStarted];
605 // [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
607 // [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
608 // message:@"Cannot move files to Trash"
611 // [self syncOperationFinishedWithSuccess:YES];
613 for (NSString *subPath in subPaths) {
614 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
616 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
617 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
618 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
626 - (BOOL)moveToTempTrashFile:(NSString *)filePath
627 accountName:(NSString *)accountName
628 pithosContainer:(ASIPithosContainer *)pithosContainer {
629 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
630 if (!self.tempTrashDirPath) {
634 NSFileManager *fileManager = [NSFileManager defaultManager];
636 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
637 NSError *error = nil;
638 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
639 NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath withString:self.tempTrashDirPath];
640 NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
641 if (fileExists && isDirectory) {
642 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
644 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
645 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath]
650 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
651 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
652 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
657 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
658 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
659 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
660 filePath, newFilePath]
665 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
667 currentState.filePath = newFilePath;
668 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
669 [currentLocalObjectStates removeObjectForKey:filePath];
671 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
672 blockHash:pithosContainer.blockHash
673 blockSize:pithosContainer.blockSize]
676 for (NSString *subPath in subPaths) {
677 NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
678 NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath
679 withString:self.tempTrashDirPath];
680 currentState = [currentLocalObjectStates objectForKey:subFilePath];
682 currentState.filePath = newSubFilePath;
683 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
684 [currentLocalObjectStates removeObjectForKey:subFilePath];
686 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
687 blockHash:pithosContainer.blockHash
688 blockSize:pithosContainer.blockSize]
689 forKey:newSubFilePath];
692 } else if (fileExists && !isDirectory) {
693 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
694 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
695 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
700 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
701 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
702 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
703 filePath, newFilePath]
708 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
710 currentState.filePath = newFilePath;
711 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
712 [currentLocalObjectStates removeObjectForKey:filePath];
714 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
715 blockHash:pithosContainer.blockHash
716 blockSize:pithosContainer.blockSize]
724 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
725 if ([hash length] != 64)
727 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
728 PithosLocalObjectState *localState;
729 NSFileManager *fileManager = [NSFileManager defaultManager];
731 NSError *error = nil;
732 for (NSString *localFilePath in currentLocalObjectStates) {
733 localState = [currentLocalObjectStates objectForKey:localFilePath];
734 if (!localState.isDirectory && [hash isEqualToString:localState.hash] &&
735 [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
736 if ([localFilePath hasPrefix:directoryPath]) {
737 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
738 [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
739 message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
740 localFilePath, filePath]
746 } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
747 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
748 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
749 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
750 localFilePath, filePath]
753 localState.filePath = filePath;
754 [currentLocalObjectStates setObject:localState forKey:filePath];
755 [currentLocalObjectStates removeObjectForKey:localFilePath];
766 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
767 localFilePath:(NSString *)filePath
768 accountName:(NSString *)accountName
769 pithosContainer:(ASIPithosContainer *)pithosContainer {
770 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
771 NSFileManager *fileManager = [NSFileManager defaultManager];
774 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
775 NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
776 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
777 objectForKey:pithosContainer.name];
778 PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
779 // Remote updated info
780 NSError *remoteError;
781 BOOL remoteIsDirectory;
782 BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos
783 containerName:pithosContainer.name
784 objectName:object.name
786 isDirectory:&remoteIsDirectory
787 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
788 if (!object || !object.objectHash) {
789 // Delete local object
790 if (![accountName isEqualToString:@""]) {
791 // If "shared to me" skip
795 if (remoteObjectExists) {
796 // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
797 syncIncomplete = YES;
799 NSLog(@"Sync::delete local object: %@", filePath);
800 if (!fileExists || [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer]) {
801 dispatch_async(dispatch_get_main_queue(), ^{
802 [activityFacility startAndEndActivityWithType:PithosActivityOther
803 message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)",
804 pithosContainer.name, object.name]
805 pithosAccount:pithosAccount];
807 [containerStoredLocalObjectStates removeObjectForKey:object.name];
808 [self saveLocalState];
810 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
811 // Create local directory object
812 if (!remoteObjectExists || !remoteIsDirectory) {
813 // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
814 syncIncomplete = YES;
818 NSLog(@"Sync::create local directory object: %@", filePath);
819 BOOL directoryCreated = NO;
820 if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
821 NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
823 if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
824 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
825 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
828 directoryCreated = YES;
829 storedState.filePath = filePath;
830 storedState.isDirectory = YES;
831 [self saveLocalState];
834 NSLog(@"Sync::local directory object exists: %@", filePath);
835 directoryCreated = YES;
837 if (directoryCreated)
838 dispatch_async(dispatch_get_main_queue(), ^{
839 [activityFacility startAndEndActivityWithType:PithosActivityOther
840 message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)",
841 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
843 pithosAccount:pithosAccount];
845 } else if (object.bytes == 0) {
846 // Create local object with zero length
847 if (!remoteObjectExists || remoteIsDirectory) {
848 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
849 syncIncomplete = YES;
853 NSLog(@"Sync::create local zero length object: %@", filePath);
854 BOOL fileCreated = NO;
856 ((isDirectory || [PithosUtilities bytesOfFile:filePath]) &&
857 [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
858 NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
859 // Create directory of the file, if it doesn't exist
861 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
862 dispatch_async(dispatch_get_main_queue(), ^{
863 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
864 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
869 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
870 [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
871 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
875 storedState.filePath = filePath;
876 storedState.hash = object.objectHash;
877 storedState.tmpFilePath = nil;
878 [self saveLocalState];
881 NSLog(@"Sync::local zero length object exists: %@", filePath);
885 dispatch_async(dispatch_get_main_queue(), ^{
886 [activityFacility startAndEndActivityWithType:PithosActivityOther
887 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
888 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
890 pithosAccount:pithosAccount];
892 } else if (storedState.tmpFilePath == nil) {
893 // Create new local object
894 if (!remoteObjectExists || remoteIsDirectory) {
895 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
896 syncIncomplete = YES;
900 // Create directory of the file, if it doesn't exist
902 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
903 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
904 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
907 // Check first if a local copy exists
908 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
909 storedState.filePath = filePath;
910 storedState.hash = object.objectHash;
911 [self saveLocalState];
912 dispatch_async(dispatch_get_main_queue(), ^{
913 [activityFacility startAndEndActivityWithType:PithosActivityOther
914 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
915 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
917 pithosAccount:pithosAccount];
920 [self syncOperationStarted];
921 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
922 containerName:pithosContainer.name
925 blockSize:pithosContainer.blockSize];
926 if (![accountName isEqualToString:@""])
927 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
928 objectRequest.delegate = self;
929 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
930 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
931 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
932 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
933 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
934 message:[messagePrefix stringByAppendingString:@" (0%%)"]
935 totalBytes:object.bytes
937 pithosAccount:pithosAccount];
938 dispatch_async(dispatch_get_main_queue(), ^{
939 [activityFacility updateActivity:activity withMessage:activity.message];
941 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
942 accountName, @"accountName",
943 pithosContainer, @"pithosContainer",
944 object, @"pithosObject",
945 messagePrefix, @"messagePrefix",
946 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks",
947 [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
948 filePath, @"filePath",
949 activity, @"activity",
950 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
951 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
952 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
953 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
954 [NSNumber numberWithUnsignedInteger:10], @"retries",
955 NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector",
956 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
958 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
959 [activityFacility updateActivity:activity
960 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
961 messagePrefix, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
962 totalBytes:activity.totalBytes
963 currentBytes:(activity.currentBytes + size)];
965 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
968 // Resume local object download
969 if (!remoteObjectExists || remoteIsDirectory) {
970 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
971 syncIncomplete = YES;
975 // Create directory of the file, if it doesn't exist
977 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
978 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
979 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
982 // Check first if a local copy exists
983 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
984 storedState.filePath = filePath;
985 storedState.hash = object.objectHash;
986 // Delete incomplete temp download
987 storedState.tmpFilePath = nil;
988 [self saveLocalState];
989 dispatch_async(dispatch_get_main_queue(), ^{
990 [activityFacility startAndEndActivityWithType:PithosActivityOther
991 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
992 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
994 pithosAccount:pithosAccount];
997 [self syncOperationStarted];
998 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
999 containerName:pithosContainer.name
1000 objectName:object.name];
1001 if (![accountName isEqualToString:@""])
1002 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1003 objectRequest.delegate = self;
1004 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1005 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1006 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
1007 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1008 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1009 message:[messagePrefix stringByAppendingString:@" (0%%)"]
1010 totalBytes:object.bytes
1012 pithosAccount:pithosAccount];
1013 dispatch_async(dispatch_get_main_queue(), ^{
1014 [activityFacility updateActivity:activity withMessage:activity.message];
1016 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1017 accountName, @"accountName",
1018 pithosContainer, @"pithosContainer",
1019 object, @"pithosObject",
1020 messagePrefix, @"messagePrefix",
1021 filePath, @"filePath",
1022 activity, @"activity",
1023 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1024 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1025 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1026 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1027 [NSNumber numberWithUnsignedInteger:10], @"retries",
1028 NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector",
1029 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1031 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1037 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
1038 object:(ASIPithosObject *)object
1039 localFilePath:(NSString *)filePath
1040 accountName:(NSString *)accountName
1041 pithosContainer:(ASIPithosContainer *)pithosContainer {
1042 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1043 [self syncOperationStarted];
1044 NSFileManager *fileManager = [NSFileManager defaultManager];
1046 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1047 if (currentState.isDirectory) {
1048 // Create remote directory object
1049 if (![accountName isEqualToString:@""]) {
1050 if (!object.allowedTo) {
1051 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1052 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1053 while ([objectAncestorName length] && !object.allowedTo) {
1054 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1055 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1058 if (![object.allowedTo isEqualToString:@"write"]) {
1059 // If read-only "shared to me" skip
1060 [self syncOperationFinishedWithSuccess:YES];
1065 if (!fileExists || !isDirectory) {
1066 // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
1067 [self syncOperationFinishedWithSuccess:NO];
1071 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
1072 containerName:pithosContainer.name
1073 objectName:object.name
1075 contentType:@"application/directory"
1077 contentDisposition:nil
1080 isPublic:ASIPithosObjectRequestPublicIgnore
1082 data:[NSData data]];
1083 if (![accountName isEqualToString:@""])
1084 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1085 objectRequest.delegate = self;
1086 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1087 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1088 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Creating directory '%@/%@'",
1089 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1090 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1091 message:messagePrefix
1092 pithosAccount:pithosAccount];
1093 dispatch_async(dispatch_get_main_queue(), ^{
1094 [activityFacility updateActivity:activity withMessage:activity.message];
1096 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1097 accountName, @"accountName",
1098 pithosContainer, @"pithosContainer",
1099 object, @"pithosObject",
1100 messagePrefix, @"messagePrefix",
1101 activity, @"activity",
1102 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1103 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1104 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1105 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1106 [NSNumber numberWithUnsignedInteger:10], @"retries",
1107 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1108 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1110 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1111 } else if (![currentState exists]) {
1112 // Delete remote object
1113 if (![accountName isEqualToString:@""]) {
1114 // If "shared to me" skip
1115 [self syncOperationFinishedWithSuccess:YES];
1120 // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
1121 syncIncomplete = YES;
1123 if ([pithosContainer.name isEqualToString:@"trash"]) {
1125 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
1126 containerName:pithosContainer.name
1127 objectName:object.name];
1128 objectRequest.delegate = self;
1129 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1130 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1131 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Deleting '%@/%@'",
1132 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1133 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1134 message:messagePrefix
1135 pithosAccount:pithosAccount];
1136 dispatch_async(dispatch_get_main_queue(), ^{
1137 [activityFacility updateActivity:activity withMessage:activity.message];
1139 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1140 accountName, @"accountName",
1141 pithosContainer, @"pithosContainer",
1142 object, @"pithosObject",
1143 messagePrefix, @"messagePrefix",
1144 activity, @"activity",
1145 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1146 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1147 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1148 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1149 [NSNumber numberWithUnsignedInteger:10], @"retries",
1150 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
1151 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1153 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1155 // Move to container trash
1157 if ([PithosUtilities isContentTypeDirectory:object.contentType])
1158 safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
1160 safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
1162 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1163 containerName:pithosContainer.name
1164 objectName:object.name
1165 destinationContainerName:@"trash"
1166 destinationObjectName:safeName
1168 if (objectRequest) {
1169 objectRequest.delegate = self;
1170 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1171 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1172 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'",
1173 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1174 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1175 message:messagePrefix
1176 pithosAccount:pithosAccount];
1177 dispatch_async(dispatch_get_main_queue(), ^{
1178 [activityFacility updateActivity:activity withMessage:activity.message];
1180 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1181 accountName, @"accountName",
1182 pithosContainer, @"pithosContainer",
1183 object, @"pithosObject",
1184 messagePrefix, @"messagePrefix",
1185 activity, @"activity",
1186 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1187 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1188 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1189 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1190 [NSNumber numberWithUnsignedInteger:10], @"retries",
1191 NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector",
1192 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1194 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1196 [self syncOperationFinishedWithSuccess:NO];
1199 [self syncOperationFinishedWithSuccess:NO];
1203 // Upload file to remote object
1204 if (![accountName isEqualToString:@""]) {
1205 if (!object.allowedTo) {
1206 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1207 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1208 while ([objectAncestorName length] && !object.allowedTo) {
1209 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1210 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1213 if (![object.allowedTo isEqualToString:@"write"]) {
1214 // If read-only "shared to me" skip
1215 [self syncOperationFinishedWithSuccess:YES];
1220 if (!fileExists || isDirectory) {
1221 // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
1222 [self syncOperationFinishedWithSuccess:NO];
1226 NSError *error = nil;
1227 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1228 if (object.contentType == nil)
1229 object.contentType = @"application/octet-stream";
1231 NSLog(@"contentType detection error: %@", error);
1232 NSArray *hashes = nil;
1233 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1234 containerName:pithosContainer.name
1235 objectName:object.name
1236 contentType:object.contentType
1237 blockSize:pithosContainer.blockSize
1238 blockHash:pithosContainer.blockHash
1242 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
1243 if (objectRequest) {
1244 objectRequest.delegate = self;
1245 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1246 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1247 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Uploading '%@/%@'",
1248 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1249 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1250 message:[messagePrefix stringByAppendingString:@" (0%%)"]
1251 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1253 pithosAccount:pithosAccount];
1254 dispatch_async(dispatch_get_main_queue(), ^{
1255 [activityFacility updateActivity:activity withMessage:activity.message];
1257 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1258 [NSDictionary dictionaryWithObjectsAndKeys:
1259 accountName, @"accountName",
1260 pithosContainer, @"pithosContainer",
1261 object, @"pithosObject",
1262 messagePrefix, @"messagePrefix",
1263 filePath, @"filePath",
1265 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1266 activity, @"activity",
1267 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1268 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1269 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1270 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1271 [NSNumber numberWithUnsignedInteger:10], @"retries",
1272 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1273 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1275 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1277 [self syncOperationFinishedWithSuccess:NO];
1284 #pragma mark ASIHTTPRequestDelegate
1286 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1287 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1288 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1289 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1290 object:request] autorelease];
1291 operation.completionBlock = ^{
1292 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1293 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1294 dispatch_async(dispatch_get_main_queue(), ^{
1295 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1296 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1298 [self syncOperationFinishedWithSuccess:NO];
1302 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1303 [callbackQueue addOperation:operation];
1306 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1307 if (request.isCancelled) {
1308 // Request has been cancelled
1309 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1310 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1311 withObject:request];
1313 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1314 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1315 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1316 object:request] autorelease];
1317 operation.completionBlock = ^{
1318 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1319 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1320 dispatch_async(dispatch_get_main_queue(), ^{
1321 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1322 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1324 [self syncOperationFinishedWithSuccess:NO];
1328 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1329 [callbackQueue addOperation:operation];
1333 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1334 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1335 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1336 NSLog(@"Sync::list request finished: %@", containerRequest.url);
1337 if (operation.isCancelled) {
1338 [self listRequestFailed:containerRequest];
1339 } else if ((containerRequest.responseStatusCode == 200) ||
1340 (containerRequest.responseStatusCode == 304) ||
1341 (containerRequest.responseStatusCode == 403)) {
1342 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
1343 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1344 if (containerRequest.responseStatusCode == 200) {
1345 NSArray *someObjects = [containerRequest objects];
1346 if (objects == nil) {
1347 objects = [[NSMutableArray alloc] initWithArray:someObjects];
1349 [objects addObjectsFromArray:someObjects];
1351 if ([someObjects count] < 10000) {
1352 pithosContainer.blockHash = [containerRequest blockHash];
1353 pithosContainer.blockSize = [containerRequest blockSize];
1354 pithosContainer.lastModified = [containerRequest lastModified];
1355 NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1356 for (ASIPithosObject *object in objects) {
1357 [containerRemoteObjects setObject:object forKey:object.name];
1359 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1363 // Do an additional request to fetch more objects
1364 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1365 containerName:pithosContainer.name
1367 marker:[[someObjects lastObject] name]
1374 ifModifiedSince:pithosContainer.lastModified];
1375 if (![accountName isEqualToString:@""])
1376 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1377 newContainerRequest.delegate = self;
1378 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1379 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1380 newContainerRequest.userInfo = containerRequest.userInfo;
1381 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1382 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1386 } else if (containerRequest.responseStatusCode == 304) {
1387 NSMutableDictionary *containerRemoteObjects = [[previousRemoteObjects objectForKey:accountName]
1388 objectForKey:pithosContainer.name];
1389 if (containerRemoteObjects)
1390 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1391 } else if (containerRequest.responseStatusCode == 403) {
1392 [[remoteObjects objectForKey:accountName] setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
1396 if (containersIndex == [[accountsPithosContainers objectForKey:accountName] count]) {
1398 containersIndex = 0;
1400 if (accountsIndex < accountsCount) {
1401 accountName = [accountsNames objectAtIndex:accountsIndex];
1402 pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1403 // Do a request for the next container
1404 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1405 containerName:pithosContainer.name
1414 ifModifiedSince:pithosContainer.lastModified];
1415 if (![accountName isEqualToString:@""])
1416 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1417 newContainerRequest.delegate = self;
1418 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1419 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1420 newContainerRequest.userInfo = containerRequest.userInfo;
1421 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1422 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1426 self.previousRemoteObjects = remoteObjects;
1427 // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
1429 if (operation.isCancelled) {
1430 [self listRequestFailed:containerRequest];
1435 dispatch_async(dispatch_get_main_queue(), ^{
1436 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1437 withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1439 NSFileManager *fileManager = [NSFileManager defaultManager];
1441 // Compute current state of legal existing local objects
1442 // and add an empty stored state for legal new local objects since last sync
1443 self.currentLocalObjectStates = [NSMutableDictionary dictionary];
1444 for (NSString *accountName in accountsNames) {
1445 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1446 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1447 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1448 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1449 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1450 objectForKey:pithosContainer.name];
1451 NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath];
1452 for (NSString *objectName in dirEnumerator) {
1453 objectName = [objectName precomposedStringWithCanonicalMapping];
1454 if (operation.isCancelled) {
1455 operation.completionBlock = nil;
1456 [self saveLocalState];
1457 [self syncOperationFinishedWithSuccess:NO];
1462 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1463 NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1465 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1466 if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) {
1467 [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error"
1468 message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.",
1469 containerDirectoryPath, pithosAccount.name]
1471 pithosAccount.syncActive = NO;
1473 } else if (fileExists) {
1474 if (skipHidden && [[objectName lastPathComponent] hasPrefix:@"."]) {
1475 // Skip hidden directory and its descendants, or hidden file
1477 [dirEnumerator skipDescendants];
1478 // Remove stored state if any
1479 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1481 } else if ([[objectName pathComponents] count] == 1) {
1482 if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) {
1483 // Skip excluded directory and its descendants, or root file with same name
1485 [dirEnumerator skipDescendants];
1486 // Remove stored state if any
1487 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1489 } else if (!isDirectory && containerExcludeRootFiles) {
1490 // Skip excluded root file
1491 // Remove stored state if any
1492 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1496 // Include local object
1497 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
1498 if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1499 // New or modified existing local object, compute current state
1500 if (!storedLocalObjectState)
1501 // For new local object, also create empty stored state
1502 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1503 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
1504 blockHash:pithosContainer.blockHash
1505 blockSize:pithosContainer.blockSize]
1508 // Local object hasn't changed, set stored state also to current
1509 [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1513 [self saveLocalState];
1517 if (operation.isCancelled) {
1518 operation.completionBlock = nil;
1519 [self syncOperationFinishedWithSuccess:NO];
1524 // Add an empty stored state for legal new remote objects since last sync
1525 for (NSString *accountName in accountsNames) {
1526 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1527 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1528 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1529 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1530 objectForKey:pithosContainer.name];
1531 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1532 for (NSString *objectName in containerRemoteObjects) {
1533 if (operation.isCancelled) {
1534 operation.completionBlock = nil;
1535 [self saveLocalState];
1536 [self syncOperationFinishedWithSuccess:NO];
1541 ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName];
1542 NSString *localObjectName;
1543 if ([object.name hasSuffix:@"/"])
1544 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1546 localObjectName = [NSString stringWithString:object.name];
1547 NSArray *pathComponents = [localObjectName pathComponents];
1549 BOOL skipObject = NO;
1550 for (NSString *pathComponent in pathComponents) {
1551 if ([pathComponent hasPrefix:@"."]) {
1552 // Skip hidden directory and its descendants, or hidden file
1553 // Remove stored state if any
1554 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1562 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1563 // Skip excluded directory object and its descendants, or root file object with same name
1564 // Remove stored state if any
1565 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1567 } else if (containerExcludeRootFiles &&
1568 ([pathComponents count] == 1) &&
1569 ![PithosUtilities isContentTypeDirectory:object.contentType]) {
1570 // Skip root file object
1571 // Remove stored state if any
1572 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1575 if (![containerStoredLocalObjectStates objectForKey:object.name])
1576 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name];
1578 [self saveLocalState];
1582 if (operation.isCancelled) {
1583 operation.completionBlock = nil;
1584 [self syncOperationFinishedWithSuccess:NO];
1589 // For each stored state compare with current and remote state
1590 // Stored states of local objects that have been deleted,
1591 // haven't been checked for legality (only existing local remote objects)
1592 // These should be identified and skipped
1593 for (NSString *accountName in accountsNames) {
1594 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1595 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1596 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1597 BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
1598 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1599 objectForKey:pithosContainer.name];
1600 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1601 for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1602 if (operation.isCancelled) {
1603 operation.completionBlock = nil;
1604 [self syncOperationFinishedWithSuccess:NO];
1609 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1610 if ([objectName hasSuffix:@"/"])
1611 filePath = [filePath stringByAppendingString:@":"];
1612 ASIPithosObject *object = [ASIPithosObject object];
1613 object.name = objectName;
1614 NSLog(@"Sync::object name: %@", object.name);
1616 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
1617 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1618 if (!currentLocalObjectState) {
1619 // The stored state corresponds to a remote or deleted local object, that's why there is no current state
1620 // In the latter case it must be checked for legality, which can be determined by its stored state
1621 // If it existed locally, but was deleted, state.exists is true,
1622 // else if the stored state is an empty state that was created due to the server object, state.exists is false
1623 if (storedLocalObjectState.exists) {
1624 NSString *localObjectName;
1625 if ([object.name hasSuffix:@"/"])
1626 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1628 localObjectName = [NSString stringWithString:object.name];
1629 NSArray *pathComponents = [localObjectName pathComponents];
1631 BOOL skipObject = NO;
1632 for (NSString *pathComponent in pathComponents) {
1633 if ([pathComponent hasPrefix:@"."]) {
1634 // Skip hidden directory and its descendants, or hidden file
1635 // Remove stored state if any
1636 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1637 [self saveLocalState];
1645 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1646 // Skip excluded directory object and its descendants, or root file object with same name
1647 // Remove stored state
1648 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1649 [self saveLocalState];
1651 } else if (containerExcludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) {
1652 // Skip root file object
1653 // Remove stored state
1654 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1655 [self saveLocalState];
1659 // There is also the off case that a local object has been created in the meantime
1660 // This call works in any case, existent or non-existent local object
1661 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
1662 blockHash:pithosContainer.blockHash
1663 blockSize:pithosContainer.blockSize];
1666 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1667 ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
1669 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1670 remoteObjectState.isDirectory = YES;
1672 remoteObjectState.hash = remoteObject.objectHash;
1676 BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1677 BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1678 NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1679 if (!localStateHasChanged) {
1680 // Local state hasn't changed
1681 if (serverStateHasChanged) {
1682 // Server state has changed
1683 // Update local state to match that of the server
1684 object.bytes = remoteObject.bytes;
1685 object.version = remoteObject.version;
1686 object.contentType = remoteObject.contentType;
1687 object.objectHash = remoteObject.objectHash;
1688 [self updateLocalStateWithObject:object localFilePath:filePath
1689 accountName:accountName pithosContainer:pithosContainer];
1690 } else if (!remoteObject && ![currentLocalObjectState exists]) {
1691 // Server state hasn't changed
1692 // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1693 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1694 [self saveLocalState];
1697 // Local state has changed
1698 if (!serverStateHasChanged) {
1699 // Server state hasn't changed
1700 if (currentLocalObjectState.isDirectory)
1701 object.contentType = @"application/directory";
1703 object.objectHash = currentLocalObjectState.hash;
1704 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1705 accountName:accountName pithosContainer:pithosContainer];
1707 // Server state has also changed
1708 if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1709 // Both did the same change (directory)
1710 storedLocalObjectState.filePath = filePath;
1711 storedLocalObjectState.isDirectory = YES;
1712 [self saveLocalState];
1713 } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1714 // Both did the same change (object edit or delete)
1715 if (![remoteObjectState exists]) {
1716 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1718 storedLocalObjectState.filePath = filePath;
1719 storedLocalObjectState.hash = remoteObjectState.hash;
1721 [self saveLocalState];
1723 // Conflict, we ask the user which change to keep
1724 NSString *informativeText;
1725 NSString *firstButtonText;
1726 NSString *secondButtonText;
1728 if (![remoteObjectState exists]) {
1729 // Remote object has been deleted
1730 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.",
1731 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name ];
1732 firstButtonText = @"Delete local file";
1733 secondButtonText = @"Upload file to server";
1734 } else if (![currentLocalObjectState exists]) {
1735 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.",
1736 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1737 firstButtonText = @"Download file from server";
1738 secondButtonText = @"Delete file on server";
1740 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.",
1741 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1742 firstButtonText = @"Keep server version";
1743 secondButtonText = @"Keep local version";
1745 __block NSInteger choice;
1746 dispatch_sync(dispatch_get_main_queue(), ^{
1747 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1748 [alert setMessageText:@"Conflict"];
1749 [alert setInformativeText:informativeText];
1750 [alert addButtonWithTitle:firstButtonText];
1751 [alert addButtonWithTitle:secondButtonText];
1752 [alert addButtonWithTitle:@"Do nothing"];
1753 choice = [alert runModal];
1755 if (choice == NSAlertFirstButtonReturn) {
1756 object.bytes = remoteObject.bytes;
1757 object.version = remoteObject.version;
1758 object.contentType = remoteObject.contentType;
1759 object.objectHash = remoteObject.objectHash;
1760 [self updateLocalStateWithObject:object localFilePath:filePath
1761 accountName:accountName pithosContainer:pithosContainer];
1762 } if (choice == NSAlertSecondButtonReturn) {
1763 if (currentLocalObjectState.isDirectory)
1764 object.contentType = @"application/directory";
1766 object.objectHash = currentLocalObjectState.hash;
1767 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1768 accountName:accountName pithosContainer:pithosContainer];
1776 [self syncOperationFinishedWithSuccess:YES];
1778 [self listRequestFailed:containerRequest];
1783 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1784 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1785 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1786 if (operation.isCancelled) {
1792 if (containerRequest.isCancelled) {
1793 dispatch_async(dispatch_get_main_queue(), ^{
1794 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1795 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1799 [self syncOperationFinishedWithSuccess:NO];
1803 // If the server listing fails, the sync should start over, so just retrying is enough
1804 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1806 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1807 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1808 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1810 dispatch_async(dispatch_get_main_queue(), ^{
1811 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1812 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1816 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1817 [self syncOperationFinishedWithSuccess:NO];
1822 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1823 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1824 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1825 NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1826 if (operation.isCancelled) {
1827 [self requestFailed:objectRequest];
1828 } else if (objectRequest.responseStatusCode == 206) {
1829 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1830 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1831 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1832 NSFileManager *fileManager = [NSFileManager defaultManager];
1834 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1836 NSString *downloadsDirPath = self.tempDownloadsDirPath;
1837 if (!downloadsDirPath) {
1838 dispatch_async(dispatch_get_main_queue(), ^{
1839 [activityFacility endActivity:activity
1840 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1842 [self syncOperationFinishedWithSuccess:NO];
1847 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1848 objectForKey:pithosContainer.name]
1849 objectForKey:object.name];
1850 if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1851 NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1852 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1853 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1854 strcpy(tempFileNameCString, tempFileTemplateCString);
1855 int fileDescriptor = mkstemp(tempFileNameCString);
1856 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1857 free(tempFileNameCString);
1858 if (fileDescriptor == -1) {
1859 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
1860 message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]
1862 dispatch_async(dispatch_get_main_queue(), ^{
1863 [activityFacility endActivity:activity
1864 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1866 [self syncOperationFinishedWithSuccess:NO];
1870 close(fileDescriptor);
1871 storedState.tmpFilePath = tempFilePath;
1872 [self saveLocalState];
1875 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1876 NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1877 [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
1878 [tempFileHandle writeData:[objectRequest responseData]];
1879 [tempFileHandle closeFile];
1881 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1882 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1883 if (missingBlockIndex == NSNotFound) {
1884 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1885 NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1886 if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath
1887 accountName:accountName
1888 pithosContainer:pithosContainer]) {
1889 dispatch_async(dispatch_get_main_queue(), ^{
1890 [activityFacility endActivity:activity
1891 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1893 [self syncOperationFinishedWithSuccess:NO];
1896 } else if (![fileManager fileExistsAtPath:dirPath]) {
1897 // File doesn't exist but also the containing directory doesn't exist
1898 // In most cases this should have been resolved as an update of the corresponding local object,
1899 // but it never hurts to check
1901 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1903 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1904 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
1906 dispatch_async(dispatch_get_main_queue(), ^{
1907 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1908 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1910 [self syncOperationFinishedWithSuccess:NO];
1915 // Move file from tmp download
1917 [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1919 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
1920 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath]
1922 dispatch_async(dispatch_get_main_queue(), ^{
1923 [activityFacility endActivity:activity
1924 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1926 [self syncOperationFinishedWithSuccess:NO];
1930 dispatch_async(dispatch_get_main_queue(), ^{
1931 [activityFacility endActivity:activity
1932 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1933 totalBytes:activity.totalBytes
1934 currentBytes:activity.totalBytes];
1937 storedState.filePath = filePath;
1938 storedState.hash = object.objectHash;
1939 storedState.tmpFilePath = nil;
1940 [self saveLocalState];
1941 [self syncOperationFinishedWithSuccess:YES];
1945 if (newSyncRequested || syncLate || operation.isCancelled) {
1946 [self requestFailed:objectRequest];
1948 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1949 containerName:pithosContainer.name
1951 blockIndex:missingBlockIndex
1952 blockSize:pithosContainer.blockSize];
1953 if (![accountName isEqualToString:@""])
1954 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1955 newObjectRequest.delegate = self;
1956 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1957 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1958 newObjectRequest.userInfo = objectRequest.userInfo;
1959 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1960 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1961 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1962 [activityFacility updateActivity:activity
1963 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1964 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
1965 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1966 totalBytes:activity.totalBytes
1967 currentBytes:(activity.currentBytes + size)];
1969 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1972 } else if (objectRequest.responseStatusCode == 412) {
1973 // The object has changed on the server
1974 dispatch_async(dispatch_get_main_queue(), ^{
1975 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1976 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1978 [self syncOperationFinishedWithSuccess:NO];
1980 [self requestFailed:objectRequest];
1985 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1986 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1987 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1988 NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1989 if (operation.isCancelled) {
1990 [self requestFailed:objectRequest];
1991 } else if (objectRequest.responseStatusCode == 200) {
1992 if (newSyncRequested || syncLate || operation.isCancelled) {
1993 [self requestFailed:objectRequest];
1995 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1996 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1997 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1998 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1999 objectForKey:pithosContainer.name]
2000 objectForKey:object.name];
2001 if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
2002 [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
2003 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
2004 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath
2005 blockSize:pithosContainer.blockSize
2006 blockHash:pithosContainer.blockHash
2007 withHashes:[objectRequest hashes]];
2008 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2009 dispatch_async(dispatch_get_main_queue(), ^{
2010 [activityFacility updateActivity:activity
2011 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2012 [objectRequest.userInfo objectForKey:@"messagePrefix"],
2013 (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))]
2014 totalBytes:activity.totalBytes
2015 currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
2018 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
2019 containerName:pithosContainer.name
2021 blockIndex:missingBlockIndex
2022 blockSize:pithosContainer.blockSize];
2023 if (![accountName isEqualToString:@""])
2024 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
2025 newObjectRequest.delegate = self;
2026 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2027 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2028 newObjectRequest.userInfo = objectRequest.userInfo;
2029 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2030 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2031 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2032 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
2033 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
2034 [activityFacility updateActivity:activity
2035 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2036 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
2037 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2038 totalBytes:activity.totalBytes
2039 currentBytes:(activity.currentBytes + size)];
2041 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2044 [self requestFailed:objectRequest];
2049 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2050 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2051 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2052 NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
2053 if (operation.isCancelled) {
2054 [self requestFailed:objectRequest];
2055 } else if (objectRequest.responseStatusCode == 201) {
2056 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2057 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2058 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2059 storedState.isDirectory = YES;
2060 [self saveLocalState];
2061 dispatch_async(dispatch_get_main_queue(), ^{
2062 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2063 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2065 [self syncOperationFinishedWithSuccess:YES];
2067 [self requestFailed:objectRequest];
2072 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
2073 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2074 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2075 NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
2076 if (operation.isCancelled) {
2077 [self requestFailed:objectRequest];
2078 } else if (objectRequest.responseStatusCode == 201) {
2079 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2080 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2081 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2082 [self saveLocalState];
2083 dispatch_async(dispatch_get_main_queue(), ^{
2084 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2085 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2087 [self syncOperationFinishedWithSuccess:YES];
2089 [self requestFailed:objectRequest];
2094 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2095 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2096 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2097 NSLog(@"Sync::delete object finished: %@", objectRequest.url);
2098 if (operation.isCancelled) {
2099 [self requestFailed:objectRequest];
2100 } else if (objectRequest.responseStatusCode == 204) {
2101 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2102 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2103 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2104 [self saveLocalState];
2105 dispatch_async(dispatch_get_main_queue(), ^{
2106 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2107 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2109 [self syncOperationFinishedWithSuccess:YES];
2111 [self requestFailed:objectRequest];
2116 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
2117 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2118 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2119 NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
2120 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
2121 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
2122 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
2123 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
2124 objectForKey:pithosContainer.name]
2125 objectForKey:object.name];
2126 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
2127 NSUInteger totalBytes = activity.totalBytes;
2128 NSUInteger currentBytes = activity.currentBytes;
2129 if (operation.isCancelled) {
2130 [self requestFailed:objectRequest];
2131 } else if (objectRequest.responseStatusCode == 201) {
2132 NSLog(@"Sync::object created: %@", objectRequest.url);
2133 storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
2134 storedState.hash = object.objectHash;
2135 [self saveLocalState];
2136 dispatch_async(dispatch_get_main_queue(), ^{
2137 [activityFacility endActivity:activity
2138 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
2139 totalBytes:totalBytes
2140 currentBytes:totalBytes];
2142 [self syncOperationFinishedWithSuccess:YES];
2143 } else if (objectRequest.responseStatusCode == 409) {
2144 if (newSyncRequested || syncLate || operation.isCancelled) {
2145 [self requestFailed:objectRequest];
2147 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
2148 if (iteration == 0) {
2149 NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
2150 dispatch_async(dispatch_get_main_queue(), ^{
2151 [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
2153 [self syncOperationFinishedWithSuccess:NO];
2157 NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
2158 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
2159 withMissingHashes:[objectRequest hashes]];
2160 if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
2161 currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
2162 dispatch_async(dispatch_get_main_queue(), ^{
2163 [activityFacility updateActivity:activity
2164 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2165 [objectRequest.userInfo objectForKey:@"messagePrefix"],
2166 (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
2167 totalBytes:totalBytes
2168 currentBytes:currentBytes];
2170 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2171 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2172 containerName:pithosContainer.name
2173 blockSize:pithosContainer.blockSize
2174 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
2175 missingBlockIndex:missingBlockIndex
2176 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2177 newContainerRequest.delegate = self;
2178 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2179 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2180 newContainerRequest.userInfo = objectRequest.userInfo;
2181 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
2182 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2183 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2184 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2185 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
2186 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2187 [activityFacility updateActivity:activity
2188 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2189 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2190 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2191 totalBytes:activity.totalBytes
2192 currentBytes:(activity.currentBytes + size)];
2194 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2197 [self requestFailed:objectRequest];
2202 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
2203 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2204 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
2205 NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
2206 if (operation.isCancelled) {
2207 [self requestFailed:containerRequest];
2208 } else if (containerRequest.responseStatusCode == 202) {
2209 NSString *accountName = [containerRequest.userInfo objectForKey:@"accountName"];
2210 ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
2211 ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
2212 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
2213 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
2214 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
2215 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
2216 if (operation.isCancelled) {
2217 [self requestFailed:containerRequest];
2218 } else if (missingBlockIndex == NSNotFound) {
2219 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
2220 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
2221 containerName:pithosContainer.name
2222 objectName:object.name
2223 contentType:object.contentType
2224 blockSize:pithosContainer.blockSize
2225 blockHash:pithosContainer.blockHash
2226 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2229 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2230 newObjectRequest.delegate = self;
2231 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2232 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2233 newObjectRequest.userInfo = containerRequest.userInfo;
2234 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2235 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
2236 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
2237 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
2238 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2240 if (newSyncRequested || syncLate || operation.isCancelled) {
2241 [self requestFailed:containerRequest];
2243 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2244 containerName:pithosContainer.name
2245 blockSize:pithosContainer.blockSize
2246 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2247 missingBlockIndex:missingBlockIndex
2248 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2249 newContainerRequest.delegate = self;
2250 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2251 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2252 newContainerRequest.userInfo = containerRequest.userInfo;
2253 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2254 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2255 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2256 [activityFacility updateActivity:activity
2257 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2258 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2259 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2260 totalBytes:activity.totalBytes
2261 currentBytes:(activity.currentBytes + size)];
2263 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2267 [self requestFailed:containerRequest];
2272 - (void)requestFailed:(ASIPithosRequest *)request {
2273 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2274 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
2275 NSLog(@"Sync::request failed: %@", request.url);
2276 if (operation.isCancelled) {
2280 if (request.isCancelled || newSyncRequested || syncLate) {
2281 dispatch_async(dispatch_get_main_queue(), ^{
2282 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2283 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
2285 [self syncOperationFinishedWithSuccess:NO];
2289 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
2291 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
2292 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
2293 [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
2295 dispatch_async(dispatch_get_main_queue(), ^{
2296 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2297 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
2299 [self syncOperationFinishedWithSuccess:NO];