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, 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 resetLocalState:(BOOL)resetLocalState {
96 if ((self = [super init])) {
97 directoryPath = [aDirectoryPath copy];
98 pithosAccount = [aPithosAccount retain];
99 self.accountsDictionary = anAccountsDictionary;
100 self.pithos = pithosAccount.pithos;
102 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
105 [self resetLocalStateWithAll:YES];
107 [self resetLocalStateWithAll:NO];
109 networkQueue = [[ASINetworkQueue alloc] init];
110 networkQueue.showAccurateProgress = YES;
111 networkQueue.shouldCancelAllRequestsOnFailure = NO;
112 // networkQueue.maxConcurrentOperationCount = 1;
114 callbackQueue = [[NSOperationQueue alloc] init];
115 [callbackQueue setSuspended:YES];
116 callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
117 // callbackQueue.maxConcurrentOperationCount = 1;
119 [[NSNotificationCenter defaultCenter] addObserver:self
120 selector:@selector(applicationWillTerminate:)
121 name:NSApplicationWillTerminateNotification
122 object:[NSApplication sharedApplication]];
127 - (void)loadLocalState {
128 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
129 if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath])
130 self.storedLocalObjectStates = [NSKeyedUnarchiver unarchiveObjectWithFile:self.pithosStateFilePath];
132 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
133 if (!storedLocalObjectStates)
134 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
135 for (NSString *accountName in accountsNames) {
136 NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
137 if (!accountStoredLocalObjectStates) {
138 accountStoredLocalObjectStates = [NSMutableDictionary dictionary];
139 [storedLocalObjectStates setObject:accountStoredLocalObjectStates forKey:accountName];
141 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
142 if (![accountStoredLocalObjectStates objectForKey:pithosContainer.name])
143 [accountStoredLocalObjectStates setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
149 - (void)resetLocalStateWithAll:(BOOL)all {
150 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
151 self.lastCompletedSync = nil;
153 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
154 [self saveLocalState]; // Save an empty dictionary
155 [self loadLocalState]; // Load to populate with containers
156 [self saveLocalState]; // Save again
157 if (self.tempDownloadsDirPath)
158 [PithosUtilities removeContentsAtPath:self.tempDownloadsDirPath];
160 // Remove containers that don't interest us anymore and save
161 if (!storedLocalObjectStates)
162 [self loadLocalState];
163 for (NSString *accountName in storedLocalObjectStates) {
164 NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
165 NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
166 if (!containersDictionary) {
167 if (self.tempDownloadsDirPath) {
168 if ([accountName isEqualToString:@""]) {
169 for (NSString *containerName in accountStoredLocalObjectStates) {
170 [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]];
173 [PithosUtilities removeContentsAtPath:[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared to me"]
174 stringByAppendingPathComponent:accountName]];
177 [storedLocalObjectStates removeObjectForKey:accountName];
179 // Check the account's containers
180 for (NSString *containerName in accountStoredLocalObjectStates) {
181 if (![containersDictionary objectForKey:containerName]) {
182 if ([accountName isEqualToString:@""])
183 [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]];
185 [PithosUtilities removeContentsAtPath:[[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared to me"]
186 stringByAppendingPathComponent:accountName]
187 stringByAppendingPathComponent:containerName]];
188 [accountStoredLocalObjectStates removeObjectForKey:containerName];
193 [self saveLocalState];
198 - (void)saveLocalState {
199 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
200 [NSKeyedArchiver archiveRootObject:storedLocalObjectStates toFile:self.pithosStateFilePath];
204 - (void)resetDaemon {
205 @synchronized(self) {
210 [networkQueue reset];
211 [callbackQueue cancelAllOperations];
212 [callbackQueue setSuspended:YES];
213 [self emptyTempTrash];
215 syncOperationCount = 0;
217 @synchronized(self) {
222 - (void)startDaemon {
223 @synchronized(self) {
228 // In the improbable case of leftover operations
229 [networkQueue reset];
230 [callbackQueue cancelAllOperations];
232 syncOperationCount = 0;
233 newSyncRequested = NO;
237 [self loadLocalState];
240 [callbackQueue setSuspended:NO];
242 @synchronized(self) {
248 [[NSNotificationCenter defaultCenter] removeObserver:self];
250 [callbackQueue release];
251 [networkQueue release];
252 [tempTrashDirPath release];
253 [tempDownloadsDirPath release];
254 [pithosStateFilePath release];
255 [currentLocalObjectStates release];
256 [storedLocalObjectStates release];
257 [previousRemoteObjects release];
258 [remoteObjects release];
260 [lastCompletedSync release];
261 [accountsPithosContainers release];
262 [accountsNames release];
264 [accountsDictionary release];
265 [pithosAccount release];
266 [directoryPath release];
271 #pragma mark Observers
273 - (void)applicationWillTerminate:(NSNotification *)notification {
274 [self saveLocalState];
278 #pragma mark Properties
280 - (NSString *)pithosStateFilePath {
281 if (!pithosStateFilePath) {
282 pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
283 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
284 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive",
285 pithosAccount.uniqueName]] retain];
286 NSString *pithosStateFileDirPath = [pithosStateFilePath stringByDeletingLastPathComponent];
287 NSFileManager *fileManager = [NSFileManager defaultManager];
289 BOOL fileExists = [fileManager fileExistsAtPath:pithosStateFileDirPath isDirectory:&isDirectory];
290 NSError *error = nil;
291 if (fileExists && !isDirectory)
292 [fileManager removeItemAtPath:pithosStateFileDirPath error:&error];
293 if (!error && !fileExists)
294 [fileManager createDirectoryAtPath:pithosStateFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
296 // pithosStateFilePath = nil;
297 // XXX create a dir using mktmps?
299 return [[pithosStateFilePath copy] autorelease];
302 - (NSString *)tempDownloadsDirPath {
303 if (!tempDownloadsDirPath) {
304 tempDownloadsDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
305 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
306 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads",
307 pithosAccount.uniqueName]] retain];
308 NSFileManager *fileManager = [NSFileManager defaultManager];
310 BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
311 NSError *error = nil;
312 if (fileExists && !isDirectory)
313 [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
314 if (!error && !fileExists)
315 [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
317 // tempDownloadsDirPath = nil;
318 // XXX create a dir using mktmps?
320 return tempDownloadsDirPath;
323 - (NSString *)tempTrashDirPath {
324 if (!tempTrashDirPath) {
325 tempTrashDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
326 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
327 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash",
328 pithosAccount.uniqueName]] retain];
329 NSFileManager *fileManager = [NSFileManager defaultManager];
331 BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
332 NSError *error = nil;
333 if (fileExists && !isDirectory)
334 [fileManager removeItemAtPath:tempTrashDirPath error:&error];
335 if (!error && !fileExists)
336 [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
338 // tempTrashDirPath = nil;
339 // XXX create a dir using mktmps?
341 return tempTrashDirPath;
344 - (void)setDirectoryPath:(NSString *)aDirectoryPath {
345 if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
347 [self resetLocalStateWithAll:YES];
348 [directoryPath release];
349 directoryPath = [aDirectoryPath copy];
353 - (void)setAccountsDictionary:(NSDictionary *)anAccountsDictionary {
354 if (anAccountsDictionary && ![anAccountsDictionary isEqualToDictionary:accountsDictionary]) {
355 BOOL reset = (accountsDictionary != nil);
359 [accountsDictionary release];
360 accountsDictionary = [anAccountsDictionary copy];
362 accountsCount = [accountsDictionary count];
363 self.accountsNames = [NSMutableArray arrayWithCapacity:accountsCount];
364 self.accountsPithosContainers = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
365 NSDictionary *containersDictionary = [accountsDictionary objectForKey:@""];
366 if (containersDictionary && [containersDictionary count]) {
367 NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
368 for (NSString *containerName in containersDictionary) {
369 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
370 pithosContainer.name = containerName;
371 [pithosContainers addObject:pithosContainer];
373 [accountsNames addObject:@""];
374 [accountsPithosContainers setObject:pithosContainers forKey:@""];
376 for (NSString *accountName in accountsDictionary) {
377 NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
378 if (![accountsNames containsObject:accountName] && [containersDictionary count]) {
379 NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
380 for (NSString *containerName in containersDictionary) {
381 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
382 pithosContainer.name = containerName;
383 [pithosContainers addObject:pithosContainer];
385 [accountsNames addObject:accountName];
386 [accountsPithosContainers setObject:pithosContainers forKey:accountName];
391 [self resetLocalStateWithAll:NO];
395 - (void)setPithos:(ASIPithos *)aPithos {
397 pithos = [[ASIPithos pithos] retain];
398 pithos.authUser = [[aPithos.authUser copy] autorelease];
399 pithos.authToken = [[aPithos.authToken copy] autorelease];
400 pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
401 pithos.authURL = [[aPithos.authURL copy] autorelease];
402 pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
405 (![aPithos.authUser isEqualToString:pithos.authUser] ||
406 ![aPithos.authToken isEqualToString:pithos.authToken] ||
407 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
409 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
410 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
411 [self resetLocalStateWithAll:YES];
412 pithos.authUser = [[aPithos.authUser copy] autorelease];
413 pithos.authToken = [[aPithos.authToken copy] autorelease];
414 pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
415 pithos.authURL = [[aPithos.authURL copy] autorelease];
416 pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
421 #pragma mark Helper Methods
423 - (BOOL)createSyncDirectory:(NSString *)dirPath {
424 NSFileManager *fileManager = [NSFileManager defaultManager];
426 NSError *error = nil;
427 if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory]) {
428 if (![fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
429 dispatch_async(dispatch_get_main_queue(), ^{
430 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
431 message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'",
437 } else if (!isDirectory) {
438 dispatch_async(dispatch_get_main_queue(), ^{
439 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
440 message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'",
449 - (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
450 if ([accountName isEqualToString:@""])
451 return [directoryPath stringByAppendingPathComponent:containerName];
453 return [[[directoryPath stringByAppendingPathComponent:@"shared to me"]
454 stringByAppendingPathComponent:accountName]
455 stringByAppendingPathComponent:containerName];
458 - (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
459 if ([accountName isEqualToString:@""])
460 return containerName;
462 return [[[NSString stringWithString:@"shared to me"]
463 stringByAppendingPathComponent:accountName]
464 stringByAppendingPathComponent:containerName];
470 - (void)syncOperationStarted {
471 @synchronized(self) {
472 syncOperationCount++;
476 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
477 @synchronized(self) {
478 if (!operationSuccessfull)
479 syncIncomplete = YES;
480 if (syncOperationCount == 0) {
481 // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
482 NSLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
485 syncOperationCount--;
486 if (syncOperationCount == 0) {
487 if (!syncIncomplete) {
488 self.lastCompletedSync = [NSDate date];
489 dispatch_async(dispatch_get_main_queue(), ^{
490 [activityFacility startAndEndActivityWithType:PithosActivityOther
491 message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]
492 pithosAccount:pithosAccount];
496 [self emptyTempTrash];
497 if (newSyncRequested && daemonActive)
504 @synchronized(self) {
505 return ((syncOperationCount > 0) && daemonActive);
510 @synchronized(self) {
511 if ([self isSyncing])
517 @synchronized(self) {
518 if ([self isSyncing]) {
519 // If at least one operation is running return
520 newSyncRequested = YES;
522 } else if (daemonActive && accountsCount) {
523 // The first operation is the server listing
524 [self syncOperationStarted];
525 newSyncRequested = NO;
533 if (![self createSyncDirectory:directoryPath]) {
534 [self syncOperationFinishedWithSuccess:NO];
537 for (NSString *accountName in accountsNames) {
538 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
539 if (![self createSyncDirectory:[self dirPathForAccount:accountName container:pithosContainer.name]]) {
540 [self syncOperationFinishedWithSuccess:NO];
546 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
547 for (NSString *accountName in accountsNames) {
548 [remoteObjects setObject:[NSMutableDictionary dictionary] forKey:accountName];
552 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
553 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
554 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
555 containerName:pithosContainer.name
564 ifModifiedSince:pithosContainer.lastModified];
565 if (![accountName isEqualToString:@""])
566 [containerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
567 containerRequest.delegate = self;
568 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
569 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
570 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther
571 message:@"Sync: Getting server listing"
572 pithosAccount:pithosAccount];
573 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
574 activity, @"activity",
575 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage",
576 @"Sync: Getting server listing (failed)", @"failedActivityMessage",
577 @"Sync: Getting server listing (finished)", @"finishedActivityMessage",
578 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
579 [NSNumber numberWithUnsignedInteger:10], @"retries",
580 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector",
581 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector",
583 [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
586 - (void)emptyTempTrash {
587 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
588 NSString *trashDirPath = self.tempTrashDirPath;
590 NSFileManager *fileManager = [NSFileManager defaultManager];
591 NSError *error = nil;
592 // NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
593 NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
595 dispatch_async(dispatch_get_main_queue(), ^{
596 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
597 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath]
603 if ([subPaths count]) {
604 // NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
605 // for (NSString *subPath in subPaths) {
606 // [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
608 // [self syncOperationStarted];
609 // [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
611 // dispatch_async(dispatch_get_main_queue(), ^{
612 // [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
613 // message:@"Cannot move files to Trash"
617 // [self syncOperationFinishedWithSuccess:YES];
619 for (NSString *subPath in subPaths) {
620 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
622 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
623 dispatch_async(dispatch_get_main_queue(), ^{
624 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
625 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
634 - (BOOL)moveToTempTrashFile:(NSString *)filePath
635 accountName:(NSString *)accountName
636 pithosContainer:(ASIPithosContainer *)pithosContainer {
637 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
638 if (!self.tempTrashDirPath) {
642 NSFileManager *fileManager = [NSFileManager defaultManager];
644 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
645 NSError *error = nil;
646 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
647 NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath withString:self.tempTrashDirPath];
648 NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
649 if (fileExists && isDirectory) {
650 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
652 dispatch_async(dispatch_get_main_queue(), ^{
653 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
654 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath]
660 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
661 dispatch_async(dispatch_get_main_queue(), ^{
662 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
663 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
669 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
670 dispatch_async(dispatch_get_main_queue(), ^{
671 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
672 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
673 filePath, newFilePath]
679 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
681 currentState.filePath = newFilePath;
682 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
683 [currentLocalObjectStates removeObjectForKey:filePath];
685 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
686 blockHash:pithosContainer.blockHash
687 blockSize:pithosContainer.blockSize]
690 for (NSString *subPath in subPaths) {
691 NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
692 NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath
693 withString:self.tempTrashDirPath];
694 currentState = [currentLocalObjectStates objectForKey:subFilePath];
696 currentState.filePath = newSubFilePath;
697 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
698 [currentLocalObjectStates removeObjectForKey:subFilePath];
700 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
701 blockHash:pithosContainer.blockHash
702 blockSize:pithosContainer.blockSize]
703 forKey:newSubFilePath];
706 } else if (fileExists && !isDirectory) {
707 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
708 dispatch_async(dispatch_get_main_queue(), ^{
709 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
710 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
716 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
717 dispatch_async(dispatch_get_main_queue(), ^{
718 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
719 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
720 filePath, newFilePath]
726 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
728 currentState.filePath = newFilePath;
729 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
730 [currentLocalObjectStates removeObjectForKey:filePath];
732 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
733 blockHash:pithosContainer.blockHash
734 blockSize:pithosContainer.blockSize]
742 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
743 if ([hash length] != 64)
745 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
746 PithosLocalObjectState *localState;
747 NSFileManager *fileManager = [NSFileManager defaultManager];
749 NSError *error = nil;
750 for (NSString *localFilePath in currentLocalObjectStates) {
751 localState = [currentLocalObjectStates objectForKey:localFilePath];
752 if (!localState.isDirectory && [hash isEqualToString:localState.hash] &&
753 [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
754 if ([localFilePath hasPrefix:directoryPath]) {
755 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
756 dispatch_async(dispatch_get_main_queue(), ^{
757 [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
758 message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
759 localFilePath, filePath]
766 } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
767 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
768 dispatch_async(dispatch_get_main_queue(), ^{
769 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
770 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
771 localFilePath, filePath]
775 localState.filePath = filePath;
776 [currentLocalObjectStates setObject:localState forKey:filePath];
777 [currentLocalObjectStates removeObjectForKey:localFilePath];
788 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
789 localFilePath:(NSString *)filePath
790 accountName:(NSString *)accountName
791 pithosContainer:(ASIPithosContainer *)pithosContainer {
792 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
793 NSFileManager *fileManager = [NSFileManager defaultManager];
796 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
797 NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
798 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
799 objectForKey:pithosContainer.name];
800 PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
801 // Remote updated info
802 NSError *remoteError;
803 BOOL remoteIsDirectory;
804 BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos
805 containerName:pithosContainer.name
806 objectName:object.name
808 isDirectory:&remoteIsDirectory
809 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
810 if (!object || !object.objectHash) {
811 // Delete local object
812 if (![accountName isEqualToString:@""]) {
813 // If "shared to me" skip
817 if (remoteObjectExists) {
818 // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
819 syncIncomplete = YES;
821 NSLog(@"Sync::delete local object: %@", filePath);
822 if (!fileExists || [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer]) {
823 dispatch_async(dispatch_get_main_queue(), ^{
824 [activityFacility startAndEndActivityWithType:PithosActivityOther
825 message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)",
826 pithosContainer.name, object.name]
827 pithosAccount:pithosAccount];
829 [containerStoredLocalObjectStates removeObjectForKey:object.name];
830 [self saveLocalState];
832 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
833 // Create local directory object
834 if (!remoteObjectExists || !remoteIsDirectory) {
835 // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
836 syncIncomplete = YES;
840 NSLog(@"Sync::create local directory object: %@", filePath);
841 BOOL directoryCreated = NO;
842 if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
843 NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
845 if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
846 dispatch_async(dispatch_get_main_queue(), ^{
847 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
848 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
852 directoryCreated = YES;
853 storedState.filePath = filePath;
854 storedState.isDirectory = YES;
855 [self saveLocalState];
858 NSLog(@"Sync::local directory object exists: %@", filePath);
859 directoryCreated = YES;
861 if (directoryCreated)
862 dispatch_async(dispatch_get_main_queue(), ^{
863 [activityFacility startAndEndActivityWithType:PithosActivityOther
864 message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)",
865 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
867 pithosAccount:pithosAccount];
869 } else if (object.bytes == 0) {
870 // Create local object with zero length
871 if (!remoteObjectExists || remoteIsDirectory) {
872 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
873 syncIncomplete = YES;
877 NSLog(@"Sync::create local zero length object: %@", filePath);
878 BOOL fileCreated = NO;
880 ((isDirectory || [PithosUtilities bytesOfFile:filePath]) &&
881 [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
882 NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
883 // Create directory of the file, if it doesn't exist
885 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
886 dispatch_async(dispatch_get_main_queue(), ^{
887 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
888 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
893 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
894 dispatch_async(dispatch_get_main_queue(), ^{
895 [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
896 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
901 storedState.filePath = filePath;
902 storedState.hash = object.objectHash;
903 storedState.tmpFilePath = nil;
904 [self saveLocalState];
907 NSLog(@"Sync::local zero length object exists: %@", filePath);
911 dispatch_async(dispatch_get_main_queue(), ^{
912 [activityFacility startAndEndActivityWithType:PithosActivityOther
913 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
914 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
916 pithosAccount:pithosAccount];
918 } else if (storedState.tmpFilePath == nil) {
919 // Create new local object
920 if (!remoteObjectExists || remoteIsDirectory) {
921 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
922 syncIncomplete = YES;
926 // Create directory of the file, if it doesn't exist
928 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
929 dispatch_async(dispatch_get_main_queue(), ^{
930 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
931 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
935 // Check first if a local copy exists
936 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
937 storedState.filePath = filePath;
938 storedState.hash = object.objectHash;
939 [self saveLocalState];
940 dispatch_async(dispatch_get_main_queue(), ^{
941 [activityFacility startAndEndActivityWithType:PithosActivityOther
942 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
943 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
945 pithosAccount:pithosAccount];
948 [self syncOperationStarted];
949 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
950 containerName:pithosContainer.name
953 blockSize:pithosContainer.blockSize];
954 if (![accountName isEqualToString:@""])
955 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
956 objectRequest.delegate = self;
957 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
958 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
959 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
960 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
961 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
962 message:[messagePrefix stringByAppendingString:@" (0%%)"]
963 totalBytes:object.bytes
965 pithosAccount:pithosAccount];
966 dispatch_async(dispatch_get_main_queue(), ^{
967 [activityFacility updateActivity:activity withMessage:activity.message];
969 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
970 accountName, @"accountName",
971 pithosContainer, @"pithosContainer",
972 object, @"pithosObject",
973 messagePrefix, @"messagePrefix",
974 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks",
975 [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
976 filePath, @"filePath",
977 activity, @"activity",
978 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
979 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
980 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
981 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
982 [NSNumber numberWithUnsignedInteger:10], @"retries",
983 NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector",
984 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
986 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
987 [activityFacility updateActivity:activity
988 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
989 messagePrefix, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
990 totalBytes:activity.totalBytes
991 currentBytes:(activity.currentBytes + size)];
993 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
996 // Resume local object download
997 if (!remoteObjectExists || remoteIsDirectory) {
998 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
999 syncIncomplete = YES;
1003 // Create directory of the file, if it doesn't exist
1005 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
1006 dispatch_async(dispatch_get_main_queue(), ^{
1007 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1008 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
1012 // Check first if a local copy exists
1013 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
1014 storedState.filePath = filePath;
1015 storedState.hash = object.objectHash;
1016 // Delete incomplete temp download
1017 storedState.tmpFilePath = nil;
1018 [self saveLocalState];
1019 dispatch_async(dispatch_get_main_queue(), ^{
1020 [activityFacility startAndEndActivityWithType:PithosActivityOther
1021 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
1022 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
1024 pithosAccount:pithosAccount];
1027 [self syncOperationStarted];
1028 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
1029 containerName:pithosContainer.name
1030 objectName:object.name];
1031 if (![accountName isEqualToString:@""])
1032 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1033 objectRequest.delegate = self;
1034 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1035 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1036 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
1037 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1038 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1039 message:[messagePrefix stringByAppendingString:@" (0%%)"]
1040 totalBytes:object.bytes
1042 pithosAccount:pithosAccount];
1043 dispatch_async(dispatch_get_main_queue(), ^{
1044 [activityFacility updateActivity:activity withMessage:activity.message];
1046 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1047 accountName, @"accountName",
1048 pithosContainer, @"pithosContainer",
1049 object, @"pithosObject",
1050 messagePrefix, @"messagePrefix",
1051 filePath, @"filePath",
1052 activity, @"activity",
1053 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1054 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1055 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1056 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1057 [NSNumber numberWithUnsignedInteger:10], @"retries",
1058 NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector",
1059 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1061 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1067 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
1068 object:(ASIPithosObject *)object
1069 localFilePath:(NSString *)filePath
1070 accountName:(NSString *)accountName
1071 pithosContainer:(ASIPithosContainer *)pithosContainer {
1072 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1073 [self syncOperationStarted];
1074 NSFileManager *fileManager = [NSFileManager defaultManager];
1076 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1077 if (currentState.isDirectory) {
1078 // Create remote directory object
1079 if (![accountName isEqualToString:@""]) {
1080 if (!object.allowedTo) {
1081 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1082 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1083 while ([objectAncestorName length] && !object.allowedTo) {
1084 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1085 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1088 if (![object.allowedTo isEqualToString:@"write"]) {
1089 // If read-only "shared to me" skip
1090 [self syncOperationFinishedWithSuccess:YES];
1095 if (!fileExists || !isDirectory) {
1096 // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
1097 [self syncOperationFinishedWithSuccess:NO];
1101 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
1102 containerName:pithosContainer.name
1103 objectName:object.name
1105 contentType:@"application/directory"
1107 contentDisposition:nil
1110 isPublic:ASIPithosObjectRequestPublicIgnore
1112 data:[NSData data]];
1113 if (![accountName isEqualToString:@""])
1114 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1115 objectRequest.delegate = self;
1116 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1117 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1118 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Creating directory '%@/%@'",
1119 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1120 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1121 message:messagePrefix
1122 pithosAccount:pithosAccount];
1123 dispatch_async(dispatch_get_main_queue(), ^{
1124 [activityFacility updateActivity:activity withMessage:activity.message];
1126 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1127 accountName, @"accountName",
1128 pithosContainer, @"pithosContainer",
1129 object, @"pithosObject",
1130 messagePrefix, @"messagePrefix",
1131 activity, @"activity",
1132 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1133 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1134 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1135 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1136 [NSNumber numberWithUnsignedInteger:10], @"retries",
1137 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1138 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1140 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1141 } else if (![currentState exists]) {
1142 // Delete remote object
1143 if (![accountName isEqualToString:@""]) {
1144 // If "shared to me" skip
1145 [self syncOperationFinishedWithSuccess:YES];
1150 // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
1151 syncIncomplete = YES;
1153 if ([pithosContainer.name isEqualToString:@"trash"]) {
1155 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
1156 containerName:pithosContainer.name
1157 objectName:object.name];
1158 objectRequest.delegate = self;
1159 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1160 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1161 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Deleting '%@/%@'",
1162 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1163 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1164 message:messagePrefix
1165 pithosAccount:pithosAccount];
1166 dispatch_async(dispatch_get_main_queue(), ^{
1167 [activityFacility updateActivity:activity withMessage:activity.message];
1169 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1170 accountName, @"accountName",
1171 pithosContainer, @"pithosContainer",
1172 object, @"pithosObject",
1173 messagePrefix, @"messagePrefix",
1174 activity, @"activity",
1175 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1176 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1177 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1178 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1179 [NSNumber numberWithUnsignedInteger:10], @"retries",
1180 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
1181 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1183 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1185 // Move to container trash
1187 if ([PithosUtilities isContentTypeDirectory:object.contentType])
1188 safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
1190 safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
1192 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1193 containerName:pithosContainer.name
1194 objectName:object.name
1195 destinationContainerName:@"trash"
1196 destinationObjectName:safeName
1198 if (objectRequest) {
1199 objectRequest.delegate = self;
1200 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1201 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1202 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'",
1203 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1204 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1205 message:messagePrefix
1206 pithosAccount:pithosAccount];
1207 dispatch_async(dispatch_get_main_queue(), ^{
1208 [activityFacility updateActivity:activity withMessage:activity.message];
1210 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1211 accountName, @"accountName",
1212 pithosContainer, @"pithosContainer",
1213 object, @"pithosObject",
1214 messagePrefix, @"messagePrefix",
1215 activity, @"activity",
1216 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1217 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1218 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1219 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1220 [NSNumber numberWithUnsignedInteger:10], @"retries",
1221 NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector",
1222 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1224 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1226 [self syncOperationFinishedWithSuccess:NO];
1229 [self syncOperationFinishedWithSuccess:NO];
1233 // Upload file to remote object
1234 if (![accountName isEqualToString:@""]) {
1235 if (!object.allowedTo) {
1236 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1237 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1238 while ([objectAncestorName length] && !object.allowedTo) {
1239 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1240 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1243 if (![object.allowedTo isEqualToString:@"write"]) {
1244 // If read-only "shared to me" skip
1245 [self syncOperationFinishedWithSuccess:YES];
1250 if (!fileExists || isDirectory) {
1251 // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
1252 [self syncOperationFinishedWithSuccess:NO];
1256 NSError *error = nil;
1257 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1258 if (object.contentType == nil)
1259 object.contentType = @"application/octet-stream";
1261 NSLog(@"contentType detection error: %@", error);
1262 NSArray *hashes = nil;
1263 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1264 containerName:pithosContainer.name
1265 objectName:object.name
1266 contentType:object.contentType
1267 blockSize:pithosContainer.blockSize
1268 blockHash:pithosContainer.blockHash
1272 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
1273 if (objectRequest) {
1274 objectRequest.delegate = self;
1275 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1276 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1277 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Uploading '%@/%@'",
1278 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1279 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1280 message:[messagePrefix stringByAppendingString:@" (0%%)"]
1281 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1283 pithosAccount:pithosAccount];
1284 dispatch_async(dispatch_get_main_queue(), ^{
1285 [activityFacility updateActivity:activity withMessage:activity.message];
1287 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1288 [NSDictionary dictionaryWithObjectsAndKeys:
1289 accountName, @"accountName",
1290 pithosContainer, @"pithosContainer",
1291 object, @"pithosObject",
1292 messagePrefix, @"messagePrefix",
1293 filePath, @"filePath",
1295 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1296 activity, @"activity",
1297 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1298 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1299 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1300 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1301 [NSNumber numberWithUnsignedInteger:10], @"retries",
1302 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1303 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1305 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1307 [self syncOperationFinishedWithSuccess:NO];
1314 #pragma mark ASIHTTPRequestDelegate
1316 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1317 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1318 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1319 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1320 object:request] autorelease];
1321 operation.completionBlock = ^{
1322 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1323 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1324 dispatch_async(dispatch_get_main_queue(), ^{
1325 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1326 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1328 [self syncOperationFinishedWithSuccess:NO];
1332 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1333 [callbackQueue addOperation:operation];
1336 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1337 if (request.isCancelled) {
1338 // Request has been cancelled
1339 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1340 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1341 withObject:request];
1343 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1344 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1345 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1346 object:request] autorelease];
1347 operation.completionBlock = ^{
1348 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1349 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1350 dispatch_async(dispatch_get_main_queue(), ^{
1351 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1352 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1354 [self syncOperationFinishedWithSuccess:NO];
1358 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1359 [callbackQueue addOperation:operation];
1363 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1364 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1365 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1366 NSLog(@"Sync::list request finished: %@", containerRequest.url);
1367 if (operation.isCancelled) {
1368 [self listRequestFailed:containerRequest];
1369 } else if ((containerRequest.responseStatusCode == 200) ||
1370 (containerRequest.responseStatusCode == 304) ||
1371 (containerRequest.responseStatusCode == 403)) {
1372 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
1373 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1374 if (containerRequest.responseStatusCode == 200) {
1375 NSArray *someObjects = [containerRequest objects];
1376 if (objects == nil) {
1377 objects = [[NSMutableArray alloc] initWithArray:someObjects];
1379 [objects addObjectsFromArray:someObjects];
1381 if ([someObjects count] < 10000) {
1382 pithosContainer.blockHash = [containerRequest blockHash];
1383 pithosContainer.blockSize = [containerRequest blockSize];
1384 pithosContainer.lastModified = [containerRequest lastModified];
1385 NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1386 for (ASIPithosObject *object in objects) {
1387 [containerRemoteObjects setObject:object forKey:object.name];
1389 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1393 // Do an additional request to fetch more objects
1394 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1395 containerName:pithosContainer.name
1397 marker:[[someObjects lastObject] name]
1404 ifModifiedSince:pithosContainer.lastModified];
1405 if (![accountName isEqualToString:@""])
1406 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1407 newContainerRequest.delegate = self;
1408 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1409 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1410 newContainerRequest.userInfo = containerRequest.userInfo;
1411 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1412 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1416 } else if (containerRequest.responseStatusCode == 304) {
1417 NSMutableDictionary *containerRemoteObjects = [[previousRemoteObjects objectForKey:accountName]
1418 objectForKey:pithosContainer.name];
1419 if (containerRemoteObjects)
1420 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1421 } else if (containerRequest.responseStatusCode == 403) {
1422 [[remoteObjects objectForKey:accountName] setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
1426 if (containersIndex == [[accountsPithosContainers objectForKey:accountName] count]) {
1428 containersIndex = 0;
1430 if (accountsIndex < accountsCount) {
1431 accountName = [accountsNames objectAtIndex:accountsIndex];
1432 pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1433 // Do a request for the next container
1434 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1435 containerName:pithosContainer.name
1444 ifModifiedSince:pithosContainer.lastModified];
1445 if (![accountName isEqualToString:@""])
1446 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1447 newContainerRequest.delegate = self;
1448 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1449 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1450 newContainerRequest.userInfo = containerRequest.userInfo;
1451 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1452 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1456 self.previousRemoteObjects = remoteObjects;
1457 // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
1459 if (operation.isCancelled) {
1460 [self listRequestFailed:containerRequest];
1465 dispatch_async(dispatch_get_main_queue(), ^{
1466 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1467 withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1469 NSFileManager *fileManager = [NSFileManager defaultManager];
1471 // Compute current state of legal existing local objects
1472 // and add an empty stored state for legal new local objects since last sync
1473 self.currentLocalObjectStates = [NSMutableDictionary dictionary];
1474 for (NSString *accountName in accountsNames) {
1475 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1476 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1477 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1478 BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1479 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1480 objectForKey:pithosContainer.name];
1481 NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath];
1482 for (NSString *objectName in dirEnumerator) {
1483 objectName = [objectName precomposedStringWithCanonicalMapping];
1484 if (operation.isCancelled) {
1485 operation.completionBlock = nil;
1486 [self saveLocalState];
1487 [self syncOperationFinishedWithSuccess:NO];
1492 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1493 NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1495 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1496 if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) {
1497 dispatch_async(dispatch_get_main_queue(), ^{
1498 [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error"
1499 message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.",
1500 containerDirectoryPath, pithosAccount.name]
1503 pithosAccount.syncActive = NO;
1505 } else if (fileExists) {
1506 NSArray *pathComponents = [objectName pathComponents];
1507 if ([pathComponents count] == 1) {
1508 if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) {
1509 // Skip excluded directory and its descendants, or root file with same name
1511 [dirEnumerator skipDescendants];
1512 // Remove stored state if any
1513 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1515 } else if (!isDirectory && containerExludeRootFiles) {
1516 // Skip excluded root file
1517 // Remove stored state if any
1518 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1522 // Include local object
1523 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
1524 if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1525 // New or modified existing local object, compute current state
1526 if (!storedLocalObjectState)
1527 // For new local object, also create empty stored state
1528 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1529 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
1530 blockHash:pithosContainer.blockHash
1531 blockSize:pithosContainer.blockSize]
1534 // Local object hasn't changed, set stored state also to current
1535 [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1539 [self saveLocalState];
1543 if (operation.isCancelled) {
1544 operation.completionBlock = nil;
1545 [self syncOperationFinishedWithSuccess:NO];
1550 // Add an empty stored state for legal new remote objects since last sync
1551 for (NSString *accountName in accountsNames) {
1552 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1553 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1554 BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1555 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1556 objectForKey:pithosContainer.name];
1557 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1558 for (NSString *objectName in containerRemoteObjects) {
1559 if (operation.isCancelled) {
1560 operation.completionBlock = nil;
1561 [self saveLocalState];
1562 [self syncOperationFinishedWithSuccess:NO];
1567 ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName];
1568 NSString *localObjectName;
1569 if ([object.name hasSuffix:@"/"])
1570 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1572 localObjectName = [NSString stringWithString:object.name];
1573 NSArray *pathComponents = [localObjectName pathComponents];
1574 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1575 // Skip excluded directory object and its descendants, or root file object with same name
1576 // Remove stored state if any
1577 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1579 } else if (containerExludeRootFiles &&
1580 ([pathComponents count] == 1) &&
1581 ![PithosUtilities isContentTypeDirectory:object.contentType]) {
1582 // Skip root file object
1583 // Remove stored state if any
1584 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1587 if (![containerStoredLocalObjectStates objectForKey:object.name])
1588 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name];
1590 [self saveLocalState];
1594 if (operation.isCancelled) {
1595 operation.completionBlock = nil;
1596 [self syncOperationFinishedWithSuccess:NO];
1601 // For each stored state compare with current and remote state
1602 // Stored states of local objects that have been deleted,
1603 // haven't been checked for legality (only existing local remote objects)
1604 // These should be identified and skipped
1605 for (NSString *accountName in accountsNames) {
1606 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1607 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1608 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1609 BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1610 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1611 objectForKey:pithosContainer.name];
1612 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1613 for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1614 if (operation.isCancelled) {
1615 operation.completionBlock = nil;
1616 [self syncOperationFinishedWithSuccess:NO];
1621 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1622 if ([objectName hasSuffix:@"/"])
1623 filePath = [filePath stringByAppendingString:@":"];
1624 ASIPithosObject *object = [ASIPithosObject object];
1625 object.name = objectName;
1626 NSLog(@"Sync::object name: %@", object.name);
1628 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
1629 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1630 if (!currentLocalObjectState) {
1631 // The stored state corresponds to a remote or deleted local object, that's why there is no current state
1632 // In the latter case it must be checked for legality, which can be determined by its stored state
1633 // If it existed locally, but was deleted, state.exists is true,
1634 // else if the stored state is an empty state that was created due to the server object, state.exists is false
1635 if (storedLocalObjectState.exists) {
1636 NSString *localObjectName;
1637 if ([object.name hasSuffix:@"/"])
1638 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1640 localObjectName = [NSString stringWithString:object.name];
1641 NSArray *pathComponents = [localObjectName pathComponents];
1642 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1643 // Skip excluded directory object and its descendants, or root file object with same name
1644 // Remove stored state
1645 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1646 [self saveLocalState];
1648 } else if (containerExludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) {
1649 // Skip root file object
1650 // Remove stored state
1651 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1652 [self saveLocalState];
1656 // There is also the off case that a local object has been created in the meantime
1657 // This call works in any case, existent or non-existent local object
1658 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
1659 blockHash:pithosContainer.blockHash
1660 blockSize:pithosContainer.blockSize];
1663 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1664 ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
1666 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1667 remoteObjectState.isDirectory = YES;
1669 remoteObjectState.hash = remoteObject.objectHash;
1673 BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1674 BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1675 NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1676 if (!localStateHasChanged) {
1677 // Local state hasn't changed
1678 if (serverStateHasChanged) {
1679 // Server state has changed
1680 // Update local state to match that of the server
1681 object.bytes = remoteObject.bytes;
1682 object.version = remoteObject.version;
1683 object.contentType = remoteObject.contentType;
1684 object.objectHash = remoteObject.objectHash;
1685 [self updateLocalStateWithObject:object localFilePath:filePath
1686 accountName:accountName pithosContainer:pithosContainer];
1687 } else if (!remoteObject && ![currentLocalObjectState exists]) {
1688 // Server state hasn't changed
1689 // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1690 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1691 [self saveLocalState];
1694 // Local state has changed
1695 if (!serverStateHasChanged) {
1696 // Server state hasn't changed
1697 if (currentLocalObjectState.isDirectory)
1698 object.contentType = @"application/directory";
1700 object.objectHash = currentLocalObjectState.hash;
1701 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1702 accountName:accountName pithosContainer:pithosContainer];
1704 // Server state has also changed
1705 if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1706 // Both did the same change (directory)
1707 storedLocalObjectState.filePath = filePath;
1708 storedLocalObjectState.isDirectory = YES;
1709 [self saveLocalState];
1710 } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1711 // Both did the same change (object edit or delete)
1712 if (![remoteObjectState exists]) {
1713 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1715 storedLocalObjectState.filePath = filePath;
1716 storedLocalObjectState.hash = remoteObjectState.hash;
1718 [self saveLocalState];
1720 // Conflict, we ask the user which change to keep
1721 NSString *informativeText;
1722 NSString *firstButtonText;
1723 NSString *secondButtonText;
1725 if (![remoteObjectState exists]) {
1726 // Remote object has been deleted
1727 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.",
1728 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name ];
1729 firstButtonText = @"Delete local file";
1730 secondButtonText = @"Upload file to server";
1731 } else if (![currentLocalObjectState exists]) {
1732 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.",
1733 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1734 firstButtonText = @"Download file from server";
1735 secondButtonText = @"Delete file on server";
1737 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.",
1738 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1739 firstButtonText = @"Keep server version";
1740 secondButtonText = @"Keep local version";
1742 __block NSInteger choice;
1743 dispatch_sync(dispatch_get_main_queue(), ^{
1744 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1745 [alert setMessageText:@"Conflict"];
1746 [alert setInformativeText:informativeText];
1747 [alert addButtonWithTitle:firstButtonText];
1748 [alert addButtonWithTitle:secondButtonText];
1749 [alert addButtonWithTitle:@"Do nothing"];
1750 choice = [alert runModal];
1752 if (choice == NSAlertFirstButtonReturn) {
1753 object.bytes = remoteObject.bytes;
1754 object.version = remoteObject.version;
1755 object.contentType = remoteObject.contentType;
1756 object.objectHash = remoteObject.objectHash;
1757 [self updateLocalStateWithObject:object localFilePath:filePath
1758 accountName:accountName pithosContainer:pithosContainer];
1759 } if (choice == NSAlertSecondButtonReturn) {
1760 if (currentLocalObjectState.isDirectory)
1761 object.contentType = @"application/directory";
1763 object.objectHash = currentLocalObjectState.hash;
1764 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1765 accountName:accountName pithosContainer:pithosContainer];
1773 [self syncOperationFinishedWithSuccess:YES];
1775 [self listRequestFailed:containerRequest];
1780 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1781 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1782 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1783 if (operation.isCancelled) {
1789 if (containerRequest.isCancelled) {
1790 dispatch_async(dispatch_get_main_queue(), ^{
1791 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1792 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1796 [self syncOperationFinishedWithSuccess:NO];
1800 // If the server listing fails, the sync should start over, so just retrying is enough
1801 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1803 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1804 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1805 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1807 dispatch_async(dispatch_get_main_queue(), ^{
1808 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1809 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1813 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1814 [self syncOperationFinishedWithSuccess:NO];
1819 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1820 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1821 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1822 NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1823 if (operation.isCancelled) {
1824 [self requestFailed:objectRequest];
1825 } else if (objectRequest.responseStatusCode == 206) {
1826 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1827 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1828 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1829 NSFileManager *fileManager = [NSFileManager defaultManager];
1831 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1833 NSString *downloadsDirPath = self.tempDownloadsDirPath;
1834 if (!downloadsDirPath) {
1835 dispatch_async(dispatch_get_main_queue(), ^{
1836 [activityFacility endActivity:activity
1837 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1839 [self syncOperationFinishedWithSuccess:NO];
1844 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1845 objectForKey:pithosContainer.name]
1846 objectForKey:object.name];
1847 if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1848 NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1849 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1850 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1851 strcpy(tempFileNameCString, tempFileTemplateCString);
1852 int fileDescriptor = mkstemp(tempFileNameCString);
1853 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1854 free(tempFileNameCString);
1855 if (fileDescriptor == -1) {
1856 dispatch_async(dispatch_get_main_queue(), ^{
1857 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
1858 message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]
1860 [activityFacility endActivity:activity
1861 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1863 [self syncOperationFinishedWithSuccess:NO];
1867 close(fileDescriptor);
1868 storedState.tmpFilePath = tempFilePath;
1869 [self saveLocalState];
1872 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1873 NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1874 [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
1875 [tempFileHandle writeData:[objectRequest responseData]];
1876 [tempFileHandle closeFile];
1878 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1879 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1880 if (missingBlockIndex == NSNotFound) {
1881 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1882 NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1883 if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath
1884 accountName:accountName
1885 pithosContainer:pithosContainer]) {
1886 dispatch_async(dispatch_get_main_queue(), ^{
1887 [activityFacility endActivity:activity
1888 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1890 [self syncOperationFinishedWithSuccess:NO];
1893 } else if (![fileManager fileExistsAtPath:dirPath]) {
1894 // File doesn't exist but also the containing directory doesn't exist
1895 // In most cases this should have been resolved as an update of the corresponding local object,
1896 // but it never hurts to check
1898 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1900 dispatch_async(dispatch_get_main_queue(), ^{
1901 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1902 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
1904 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1905 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1907 [self syncOperationFinishedWithSuccess:NO];
1912 // Move file from tmp download
1914 [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1916 dispatch_async(dispatch_get_main_queue(), ^{
1917 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
1918 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath]
1920 [activityFacility endActivity:activity
1921 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1923 [self syncOperationFinishedWithSuccess:NO];
1927 dispatch_async(dispatch_get_main_queue(), ^{
1928 [activityFacility endActivity:activity
1929 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1930 totalBytes:activity.totalBytes
1931 currentBytes:activity.totalBytes];
1934 storedState.filePath = filePath;
1935 storedState.hash = object.objectHash;
1936 storedState.tmpFilePath = nil;
1937 [self saveLocalState];
1938 [self syncOperationFinishedWithSuccess:YES];
1942 if (newSyncRequested || syncLate || operation.isCancelled) {
1943 [self requestFailed:objectRequest];
1945 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1946 containerName:pithosContainer.name
1948 blockIndex:missingBlockIndex
1949 blockSize:pithosContainer.blockSize];
1950 if (![accountName isEqualToString:@""])
1951 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1952 newObjectRequest.delegate = self;
1953 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1954 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1955 newObjectRequest.userInfo = objectRequest.userInfo;
1956 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1957 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1958 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1959 [activityFacility updateActivity:activity
1960 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1961 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
1962 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1963 totalBytes:activity.totalBytes
1964 currentBytes:(activity.currentBytes + size)];
1966 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1969 } else if (objectRequest.responseStatusCode == 412) {
1970 // The object has changed on the server
1971 dispatch_async(dispatch_get_main_queue(), ^{
1972 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1973 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1975 [self syncOperationFinishedWithSuccess:NO];
1977 [self requestFailed:objectRequest];
1982 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1983 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1984 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1985 NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1986 if (operation.isCancelled) {
1987 [self requestFailed:objectRequest];
1988 } else if (objectRequest.responseStatusCode == 200) {
1989 if (newSyncRequested || syncLate || operation.isCancelled) {
1990 [self requestFailed:objectRequest];
1992 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1993 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1994 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1995 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1996 objectForKey:pithosContainer.name]
1997 objectForKey:object.name];
1998 if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1999 [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
2000 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
2001 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath
2002 blockSize:pithosContainer.blockSize
2003 blockHash:pithosContainer.blockHash
2004 withHashes:[objectRequest hashes]];
2005 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2006 dispatch_async(dispatch_get_main_queue(), ^{
2007 [activityFacility updateActivity:activity
2008 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2009 [objectRequest.userInfo objectForKey:@"messagePrefix"],
2010 (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))]
2011 totalBytes:activity.totalBytes
2012 currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
2015 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
2016 containerName:pithosContainer.name
2018 blockIndex:missingBlockIndex
2019 blockSize:pithosContainer.blockSize];
2020 if (![accountName isEqualToString:@""])
2021 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
2022 newObjectRequest.delegate = self;
2023 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2024 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2025 newObjectRequest.userInfo = objectRequest.userInfo;
2026 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2027 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2028 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2029 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
2030 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
2031 [activityFacility updateActivity:activity
2032 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2033 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
2034 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2035 totalBytes:activity.totalBytes
2036 currentBytes:(activity.currentBytes + size)];
2038 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2041 [self requestFailed:objectRequest];
2046 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2047 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2048 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2049 NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
2050 if (operation.isCancelled) {
2051 [self requestFailed:objectRequest];
2052 } else if (objectRequest.responseStatusCode == 201) {
2053 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2054 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2055 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2056 storedState.isDirectory = YES;
2057 [self saveLocalState];
2058 dispatch_async(dispatch_get_main_queue(), ^{
2059 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2060 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2062 [self syncOperationFinishedWithSuccess:YES];
2064 [self requestFailed:objectRequest];
2069 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
2070 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2071 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2072 NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
2073 if (operation.isCancelled) {
2074 [self requestFailed:objectRequest];
2075 } else if (objectRequest.responseStatusCode == 201) {
2076 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2077 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2078 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2079 [self saveLocalState];
2080 dispatch_async(dispatch_get_main_queue(), ^{
2081 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2082 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2084 [self syncOperationFinishedWithSuccess:YES];
2086 [self requestFailed:objectRequest];
2091 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2092 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2093 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2094 NSLog(@"Sync::delete object finished: %@", objectRequest.url);
2095 if (operation.isCancelled) {
2096 [self requestFailed:objectRequest];
2097 } else if (objectRequest.responseStatusCode == 204) {
2098 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2099 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2100 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2101 [self saveLocalState];
2102 dispatch_async(dispatch_get_main_queue(), ^{
2103 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2104 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2106 [self syncOperationFinishedWithSuccess:YES];
2108 [self requestFailed:objectRequest];
2113 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
2114 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2115 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2116 NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
2117 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
2118 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
2119 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
2120 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
2121 objectForKey:pithosContainer.name]
2122 objectForKey:object.name];
2123 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
2124 NSUInteger totalBytes = activity.totalBytes;
2125 NSUInteger currentBytes = activity.currentBytes;
2126 if (operation.isCancelled) {
2127 [self requestFailed:objectRequest];
2128 } else if (objectRequest.responseStatusCode == 201) {
2129 NSLog(@"Sync::object created: %@", objectRequest.url);
2130 storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
2131 storedState.hash = object.objectHash;
2132 [self saveLocalState];
2133 dispatch_async(dispatch_get_main_queue(), ^{
2134 [activityFacility endActivity:activity
2135 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
2136 totalBytes:totalBytes
2137 currentBytes:totalBytes];
2139 [self syncOperationFinishedWithSuccess:YES];
2140 } else if (objectRequest.responseStatusCode == 409) {
2141 if (newSyncRequested || syncLate || operation.isCancelled) {
2142 [self requestFailed:objectRequest];
2144 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
2145 if (iteration == 0) {
2146 NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
2147 dispatch_async(dispatch_get_main_queue(), ^{
2148 [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
2150 [self syncOperationFinishedWithSuccess:NO];
2154 NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
2155 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
2156 withMissingHashes:[objectRequest hashes]];
2157 if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
2158 currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
2159 dispatch_async(dispatch_get_main_queue(), ^{
2160 [activityFacility updateActivity:activity
2161 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2162 [objectRequest.userInfo objectForKey:@"messagePrefix"],
2163 (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
2164 totalBytes:totalBytes
2165 currentBytes:currentBytes];
2167 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2168 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2169 containerName:pithosContainer.name
2170 blockSize:pithosContainer.blockSize
2171 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
2172 missingBlockIndex:missingBlockIndex
2173 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2174 newContainerRequest.delegate = self;
2175 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2176 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2177 newContainerRequest.userInfo = objectRequest.userInfo;
2178 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
2179 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2180 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2181 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2182 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
2183 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2184 [activityFacility updateActivity:activity
2185 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2186 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2187 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2188 totalBytes:activity.totalBytes
2189 currentBytes:(activity.currentBytes + size)];
2191 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2194 [self requestFailed:objectRequest];
2199 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
2200 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2201 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
2202 NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
2203 if (operation.isCancelled) {
2204 [self requestFailed:containerRequest];
2205 } else if (containerRequest.responseStatusCode == 202) {
2206 NSString *accountName = [containerRequest.userInfo objectForKey:@"accountName"];
2207 ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
2208 ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
2209 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
2210 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
2211 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
2212 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
2213 if (operation.isCancelled) {
2214 [self requestFailed:containerRequest];
2215 } else if (missingBlockIndex == NSNotFound) {
2216 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
2217 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
2218 containerName:pithosContainer.name
2219 objectName:object.name
2220 contentType:object.contentType
2221 blockSize:pithosContainer.blockSize
2222 blockHash:pithosContainer.blockHash
2223 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2226 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2227 newObjectRequest.delegate = self;
2228 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2229 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2230 newObjectRequest.userInfo = containerRequest.userInfo;
2231 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2232 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
2233 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
2234 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
2235 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2237 if (newSyncRequested || syncLate || operation.isCancelled) {
2238 [self requestFailed:containerRequest];
2240 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2241 containerName:pithosContainer.name
2242 blockSize:pithosContainer.blockSize
2243 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2244 missingBlockIndex:missingBlockIndex
2245 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2246 newContainerRequest.delegate = self;
2247 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2248 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2249 newContainerRequest.userInfo = containerRequest.userInfo;
2250 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2251 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2252 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2253 [activityFacility updateActivity:activity
2254 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2255 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2256 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2257 totalBytes:activity.totalBytes
2258 currentBytes:(activity.currentBytes + size)];
2260 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2264 [self requestFailed:containerRequest];
2269 - (void)requestFailed:(ASIPithosRequest *)request {
2270 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2271 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
2272 NSLog(@"Sync::request failed: %@", request.url);
2273 if (operation.isCancelled) {
2277 if (request.isCancelled || newSyncRequested || syncLate) {
2278 dispatch_async(dispatch_get_main_queue(), ^{
2279 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2280 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
2282 [self syncOperationFinishedWithSuccess:NO];
2286 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
2288 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
2289 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
2290 [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
2292 dispatch_async(dispatch_get_main_queue(), ^{
2293 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2294 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
2296 [self syncOperationFinishedWithSuccess:NO];