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 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
430 message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'",
435 } else if (!isDirectory) {
436 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
437 message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'",
445 - (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
446 if ([accountName isEqualToString:@""])
447 return [directoryPath stringByAppendingPathComponent:containerName];
449 return [[[directoryPath stringByAppendingPathComponent:@"shared to me"]
450 stringByAppendingPathComponent:accountName]
451 stringByAppendingPathComponent:containerName];
454 - (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
455 if ([accountName isEqualToString:@""])
456 return containerName;
458 return [[[NSString stringWithString:@"shared to me"]
459 stringByAppendingPathComponent:accountName]
460 stringByAppendingPathComponent:containerName];
466 - (void)syncOperationStarted {
467 @synchronized(self) {
468 syncOperationCount++;
472 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
473 @synchronized(self) {
474 if (!operationSuccessfull)
475 syncIncomplete = YES;
476 if (syncOperationCount == 0) {
477 // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
478 NSLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
481 syncOperationCount--;
482 if (syncOperationCount == 0) {
483 if (!syncIncomplete) {
484 self.lastCompletedSync = [NSDate date];
485 dispatch_async(dispatch_get_main_queue(), ^{
486 [activityFacility startAndEndActivityWithType:PithosActivityOther
487 message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]
488 pithosAccount:pithosAccount];
492 [self emptyTempTrash];
493 if (newSyncRequested && daemonActive)
500 @synchronized(self) {
501 return ((syncOperationCount > 0) && daemonActive);
506 @synchronized(self) {
507 if ([self isSyncing])
513 @synchronized(self) {
514 if ([self isSyncing]) {
515 // If at least one operation is running return
516 newSyncRequested = YES;
518 } else if (daemonActive && accountsCount) {
519 // The first operation is the server listing
520 [self syncOperationStarted];
521 newSyncRequested = NO;
529 if (![self createSyncDirectory:directoryPath]) {
530 [self syncOperationFinishedWithSuccess:NO];
533 for (NSString *accountName in accountsNames) {
534 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
535 if (![self createSyncDirectory:[self dirPathForAccount:accountName container:pithosContainer.name]]) {
536 [self syncOperationFinishedWithSuccess:NO];
542 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
543 for (NSString *accountName in accountsNames) {
544 [remoteObjects setObject:[NSMutableDictionary dictionary] forKey:accountName];
548 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
549 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
550 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
551 containerName:pithosContainer.name
560 ifModifiedSince:pithosContainer.lastModified];
561 if (![accountName isEqualToString:@""])
562 [containerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
563 containerRequest.delegate = self;
564 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
565 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
566 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther
567 message:@"Sync: Getting server listing"
568 pithosAccount:pithosAccount];
569 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
570 activity, @"activity",
571 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage",
572 @"Sync: Getting server listing (failed)", @"failedActivityMessage",
573 @"Sync: Getting server listing (finished)", @"finishedActivityMessage",
574 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
575 [NSNumber numberWithUnsignedInteger:10], @"retries",
576 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector",
577 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector",
579 [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
582 - (void)emptyTempTrash {
583 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
584 NSString *trashDirPath = self.tempTrashDirPath;
586 NSFileManager *fileManager = [NSFileManager defaultManager];
587 NSError *error = nil;
588 // NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
589 NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
591 dispatch_async(dispatch_get_main_queue(), ^{
592 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
593 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath]
599 if ([subPaths count]) {
600 // NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
601 // for (NSString *subPath in subPaths) {
602 // [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
604 // [self syncOperationStarted];
605 // [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
607 // dispatch_async(dispatch_get_main_queue(), ^{
608 // [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
609 // message:@"Cannot move files to Trash"
613 // [self syncOperationFinishedWithSuccess:YES];
615 for (NSString *subPath in subPaths) {
616 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
618 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
619 dispatch_async(dispatch_get_main_queue(), ^{
620 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
621 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
630 - (BOOL)moveToTempTrashFile:(NSString *)filePath
631 accountName:(NSString *)accountName
632 pithosContainer:(ASIPithosContainer *)pithosContainer {
633 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
634 if (!self.tempTrashDirPath) {
638 NSFileManager *fileManager = [NSFileManager defaultManager];
640 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
641 NSError *error = nil;
642 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
643 NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath withString:self.tempTrashDirPath];
644 NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
645 if (fileExists && isDirectory) {
646 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
648 dispatch_async(dispatch_get_main_queue(), ^{
649 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
650 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath]
656 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
657 dispatch_async(dispatch_get_main_queue(), ^{
658 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
659 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
665 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
666 dispatch_async(dispatch_get_main_queue(), ^{
667 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
668 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
669 filePath, newFilePath]
675 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
677 currentState.filePath = newFilePath;
678 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
679 [currentLocalObjectStates removeObjectForKey:filePath];
681 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
682 blockHash:pithosContainer.blockHash
683 blockSize:pithosContainer.blockSize]
686 for (NSString *subPath in subPaths) {
687 NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
688 NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath
689 withString:self.tempTrashDirPath];
690 currentState = [currentLocalObjectStates objectForKey:subFilePath];
692 currentState.filePath = newSubFilePath;
693 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
694 [currentLocalObjectStates removeObjectForKey:subFilePath];
696 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
697 blockHash:pithosContainer.blockHash
698 blockSize:pithosContainer.blockSize]
699 forKey:newSubFilePath];
702 } else if (fileExists && !isDirectory) {
703 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
704 dispatch_async(dispatch_get_main_queue(), ^{
705 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
706 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
712 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
713 dispatch_async(dispatch_get_main_queue(), ^{
714 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
715 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
716 filePath, newFilePath]
722 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
724 currentState.filePath = newFilePath;
725 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
726 [currentLocalObjectStates removeObjectForKey:filePath];
728 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
729 blockHash:pithosContainer.blockHash
730 blockSize:pithosContainer.blockSize]
738 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
739 if ([hash length] != 64)
741 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
742 PithosLocalObjectState *localState;
743 NSFileManager *fileManager = [NSFileManager defaultManager];
745 NSError *error = nil;
746 for (NSString *localFilePath in currentLocalObjectStates) {
747 localState = [currentLocalObjectStates objectForKey:localFilePath];
748 if (!localState.isDirectory && [hash isEqualToString:localState.hash] &&
749 [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
750 if ([localFilePath hasPrefix:directoryPath]) {
751 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
752 dispatch_async(dispatch_get_main_queue(), ^{
753 [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
754 message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
755 localFilePath, filePath]
762 } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
763 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
764 dispatch_async(dispatch_get_main_queue(), ^{
765 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
766 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
767 localFilePath, filePath]
771 localState.filePath = filePath;
772 [currentLocalObjectStates setObject:localState forKey:filePath];
773 [currentLocalObjectStates removeObjectForKey:localFilePath];
784 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
785 localFilePath:(NSString *)filePath
786 accountName:(NSString *)accountName
787 pithosContainer:(ASIPithosContainer *)pithosContainer {
788 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
789 NSFileManager *fileManager = [NSFileManager defaultManager];
792 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
793 NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
794 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
795 objectForKey:pithosContainer.name];
796 PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
797 // Remote updated info
798 NSError *remoteError;
799 BOOL remoteIsDirectory;
800 BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos
801 containerName:pithosContainer.name
802 objectName:object.name
804 isDirectory:&remoteIsDirectory
805 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
806 if (!object || !object.objectHash) {
807 // Delete local object
808 if (![accountName isEqualToString:@""]) {
809 // If "shared to me" skip
813 if (remoteObjectExists) {
814 // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
815 syncIncomplete = YES;
817 NSLog(@"Sync::delete local object: %@", filePath);
818 if (!fileExists || [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer]) {
819 dispatch_async(dispatch_get_main_queue(), ^{
820 [activityFacility startAndEndActivityWithType:PithosActivityOther
821 message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)",
822 pithosContainer.name, object.name]
823 pithosAccount:pithosAccount];
825 [containerStoredLocalObjectStates removeObjectForKey:object.name];
826 [self saveLocalState];
828 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
829 // Create local directory object
830 if (!remoteObjectExists || !remoteIsDirectory) {
831 // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
832 syncIncomplete = YES;
836 NSLog(@"Sync::create local directory object: %@", filePath);
837 BOOL directoryCreated = NO;
838 if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
839 NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
841 if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
842 dispatch_async(dispatch_get_main_queue(), ^{
843 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
844 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
848 directoryCreated = YES;
849 storedState.filePath = filePath;
850 storedState.isDirectory = YES;
851 [self saveLocalState];
854 NSLog(@"Sync::local directory object exists: %@", filePath);
855 directoryCreated = YES;
857 if (directoryCreated)
858 dispatch_async(dispatch_get_main_queue(), ^{
859 [activityFacility startAndEndActivityWithType:PithosActivityOther
860 message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)",
861 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
863 pithosAccount:pithosAccount];
865 } else if (object.bytes == 0) {
866 // Create local object with zero length
867 if (!remoteObjectExists || remoteIsDirectory) {
868 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
869 syncIncomplete = YES;
873 NSLog(@"Sync::create local zero length object: %@", filePath);
874 BOOL fileCreated = NO;
876 ((isDirectory || [PithosUtilities bytesOfFile:filePath]) &&
877 [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
878 NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
879 // Create directory of the file, if it doesn't exist
881 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
882 dispatch_async(dispatch_get_main_queue(), ^{
883 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
884 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
889 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
890 dispatch_async(dispatch_get_main_queue(), ^{
891 [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
892 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
897 storedState.filePath = filePath;
898 storedState.hash = object.objectHash;
899 storedState.tmpFilePath = nil;
900 [self saveLocalState];
903 NSLog(@"Sync::local zero length object exists: %@", filePath);
907 dispatch_async(dispatch_get_main_queue(), ^{
908 [activityFacility startAndEndActivityWithType:PithosActivityOther
909 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
910 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
912 pithosAccount:pithosAccount];
914 } else if (storedState.tmpFilePath == nil) {
915 // Create new local object
916 if (!remoteObjectExists || remoteIsDirectory) {
917 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
918 syncIncomplete = YES;
922 // Create directory of the file, if it doesn't exist
924 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
925 dispatch_async(dispatch_get_main_queue(), ^{
926 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
927 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
931 // Check first if a local copy exists
932 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
933 storedState.filePath = filePath;
934 storedState.hash = object.objectHash;
935 [self saveLocalState];
936 dispatch_async(dispatch_get_main_queue(), ^{
937 [activityFacility startAndEndActivityWithType:PithosActivityOther
938 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
939 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
941 pithosAccount:pithosAccount];
944 [self syncOperationStarted];
945 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
946 containerName:pithosContainer.name
949 blockSize:pithosContainer.blockSize];
950 if (![accountName isEqualToString:@""])
951 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
952 objectRequest.delegate = self;
953 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
954 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
955 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
956 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
957 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
958 message:[messagePrefix stringByAppendingString:@" (0%%)"]
959 totalBytes:object.bytes
961 pithosAccount:pithosAccount];
962 dispatch_async(dispatch_get_main_queue(), ^{
963 [activityFacility updateActivity:activity withMessage:activity.message];
965 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
966 accountName, @"accountName",
967 pithosContainer, @"pithosContainer",
968 object, @"pithosObject",
969 messagePrefix, @"messagePrefix",
970 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks",
971 [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
972 filePath, @"filePath",
973 activity, @"activity",
974 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
975 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
976 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
977 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
978 [NSNumber numberWithUnsignedInteger:10], @"retries",
979 NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector",
980 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
982 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
983 [activityFacility updateActivity:activity
984 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
985 messagePrefix, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
986 totalBytes:activity.totalBytes
987 currentBytes:(activity.currentBytes + size)];
989 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
992 // Resume local object download
993 if (!remoteObjectExists || remoteIsDirectory) {
994 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
995 syncIncomplete = YES;
999 // Create directory of the file, if it doesn't exist
1001 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
1002 dispatch_async(dispatch_get_main_queue(), ^{
1003 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1004 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
1008 // Check first if a local copy exists
1009 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
1010 storedState.filePath = filePath;
1011 storedState.hash = object.objectHash;
1012 // Delete incomplete temp download
1013 storedState.tmpFilePath = nil;
1014 [self saveLocalState];
1015 dispatch_async(dispatch_get_main_queue(), ^{
1016 [activityFacility startAndEndActivityWithType:PithosActivityOther
1017 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
1018 [self relativeDirPathForAccount:accountName container:pithosContainer.name],
1020 pithosAccount:pithosAccount];
1023 [self syncOperationStarted];
1024 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
1025 containerName:pithosContainer.name
1026 objectName:object.name];
1027 if (![accountName isEqualToString:@""])
1028 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1029 objectRequest.delegate = self;
1030 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1031 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1032 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
1033 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1034 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1035 message:[messagePrefix stringByAppendingString:@" (0%%)"]
1036 totalBytes:object.bytes
1038 pithosAccount:pithosAccount];
1039 dispatch_async(dispatch_get_main_queue(), ^{
1040 [activityFacility updateActivity:activity withMessage:activity.message];
1042 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1043 accountName, @"accountName",
1044 pithosContainer, @"pithosContainer",
1045 object, @"pithosObject",
1046 messagePrefix, @"messagePrefix",
1047 filePath, @"filePath",
1048 activity, @"activity",
1049 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1050 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1051 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1052 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1053 [NSNumber numberWithUnsignedInteger:10], @"retries",
1054 NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector",
1055 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1057 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1063 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
1064 object:(ASIPithosObject *)object
1065 localFilePath:(NSString *)filePath
1066 accountName:(NSString *)accountName
1067 pithosContainer:(ASIPithosContainer *)pithosContainer {
1068 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1069 [self syncOperationStarted];
1070 NSFileManager *fileManager = [NSFileManager defaultManager];
1072 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1073 if (currentState.isDirectory) {
1074 // Create remote directory object
1075 if (![accountName isEqualToString:@""]) {
1076 if (!object.allowedTo) {
1077 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1078 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1079 while ([objectAncestorName length] && !object.allowedTo) {
1080 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1081 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1084 if (![object.allowedTo isEqualToString:@"write"]) {
1085 // If read-only "shared to me" skip
1086 [self syncOperationFinishedWithSuccess:YES];
1091 if (!fileExists || !isDirectory) {
1092 // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
1093 [self syncOperationFinishedWithSuccess:NO];
1097 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
1098 containerName:pithosContainer.name
1099 objectName:object.name
1101 contentType:@"application/directory"
1103 contentDisposition:nil
1106 isPublic:ASIPithosObjectRequestPublicIgnore
1108 data:[NSData data]];
1109 if (![accountName isEqualToString:@""])
1110 [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1111 objectRequest.delegate = self;
1112 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1113 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1114 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Creating directory '%@/%@'",
1115 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1116 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1117 message:messagePrefix
1118 pithosAccount:pithosAccount];
1119 dispatch_async(dispatch_get_main_queue(), ^{
1120 [activityFacility updateActivity:activity withMessage:activity.message];
1122 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1123 accountName, @"accountName",
1124 pithosContainer, @"pithosContainer",
1125 object, @"pithosObject",
1126 messagePrefix, @"messagePrefix",
1127 activity, @"activity",
1128 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1129 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1130 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1131 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1132 [NSNumber numberWithUnsignedInteger:10], @"retries",
1133 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1134 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1136 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1137 } else if (![currentState exists]) {
1138 // Delete remote object
1139 if (![accountName isEqualToString:@""]) {
1140 // If "shared to me" skip
1141 [self syncOperationFinishedWithSuccess:YES];
1146 // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
1147 syncIncomplete = YES;
1149 if ([pithosContainer.name isEqualToString:@"trash"]) {
1151 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
1152 containerName:pithosContainer.name
1153 objectName:object.name];
1154 objectRequest.delegate = self;
1155 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1156 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1157 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Deleting '%@/%@'",
1158 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1159 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1160 message:messagePrefix
1161 pithosAccount:pithosAccount];
1162 dispatch_async(dispatch_get_main_queue(), ^{
1163 [activityFacility updateActivity:activity withMessage:activity.message];
1165 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1166 accountName, @"accountName",
1167 pithosContainer, @"pithosContainer",
1168 object, @"pithosObject",
1169 messagePrefix, @"messagePrefix",
1170 activity, @"activity",
1171 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1172 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1173 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1174 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1175 [NSNumber numberWithUnsignedInteger:10], @"retries",
1176 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
1177 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1179 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1181 // Move to container trash
1183 if ([PithosUtilities isContentTypeDirectory:object.contentType])
1184 safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
1186 safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
1188 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1189 containerName:pithosContainer.name
1190 objectName:object.name
1191 destinationContainerName:@"trash"
1192 destinationObjectName:safeName
1194 if (objectRequest) {
1195 objectRequest.delegate = self;
1196 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1197 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1198 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'",
1199 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1200 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1201 message:messagePrefix
1202 pithosAccount:pithosAccount];
1203 dispatch_async(dispatch_get_main_queue(), ^{
1204 [activityFacility updateActivity:activity withMessage:activity.message];
1206 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1207 accountName, @"accountName",
1208 pithosContainer, @"pithosContainer",
1209 object, @"pithosObject",
1210 messagePrefix, @"messagePrefix",
1211 activity, @"activity",
1212 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1213 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1214 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1215 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1216 [NSNumber numberWithUnsignedInteger:10], @"retries",
1217 NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector",
1218 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1220 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1222 [self syncOperationFinishedWithSuccess:NO];
1225 [self syncOperationFinishedWithSuccess:NO];
1229 // Upload file to remote object
1230 if (![accountName isEqualToString:@""]) {
1231 if (!object.allowedTo) {
1232 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1233 NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
1234 while ([objectAncestorName length] && !object.allowedTo) {
1235 object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
1236 objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
1239 if (![object.allowedTo isEqualToString:@"write"]) {
1240 // If read-only "shared to me" skip
1241 [self syncOperationFinishedWithSuccess:YES];
1246 if (!fileExists || isDirectory) {
1247 // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
1248 [self syncOperationFinishedWithSuccess:NO];
1252 NSError *error = nil;
1253 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1254 if (object.contentType == nil)
1255 object.contentType = @"application/octet-stream";
1257 NSLog(@"contentType detection error: %@", error);
1258 NSArray *hashes = nil;
1259 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1260 containerName:pithosContainer.name
1261 objectName:object.name
1262 contentType:object.contentType
1263 blockSize:pithosContainer.blockSize
1264 blockHash:pithosContainer.blockHash
1268 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
1269 if (objectRequest) {
1270 objectRequest.delegate = self;
1271 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1272 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1273 NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Uploading '%@/%@'",
1274 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1275 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1276 message:[messagePrefix stringByAppendingString:@" (0%%)"]
1277 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1279 pithosAccount:pithosAccount];
1280 dispatch_async(dispatch_get_main_queue(), ^{
1281 [activityFacility updateActivity:activity withMessage:activity.message];
1283 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1284 [NSDictionary dictionaryWithObjectsAndKeys:
1285 accountName, @"accountName",
1286 pithosContainer, @"pithosContainer",
1287 object, @"pithosObject",
1288 messagePrefix, @"messagePrefix",
1289 filePath, @"filePath",
1291 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1292 activity, @"activity",
1293 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1294 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1295 [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
1296 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1297 [NSNumber numberWithUnsignedInteger:10], @"retries",
1298 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1299 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1301 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1303 [self syncOperationFinishedWithSuccess:NO];
1310 #pragma mark ASIHTTPRequestDelegate
1312 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1313 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1314 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1315 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1316 object:request] autorelease];
1317 operation.completionBlock = ^{
1318 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1319 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1320 dispatch_async(dispatch_get_main_queue(), ^{
1321 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1322 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1324 [self syncOperationFinishedWithSuccess:NO];
1328 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1329 [callbackQueue addOperation:operation];
1332 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1333 if (request.isCancelled) {
1334 // Request has been cancelled
1335 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1336 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1337 withObject:request];
1339 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1340 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1341 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1342 object:request] autorelease];
1343 operation.completionBlock = ^{
1344 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1345 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1346 dispatch_async(dispatch_get_main_queue(), ^{
1347 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1348 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1350 [self syncOperationFinishedWithSuccess:NO];
1354 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1355 [callbackQueue addOperation:operation];
1359 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1360 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1361 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1362 NSLog(@"Sync::list request finished: %@", containerRequest.url);
1363 if (operation.isCancelled) {
1364 [self listRequestFailed:containerRequest];
1365 } else if ((containerRequest.responseStatusCode == 200) ||
1366 (containerRequest.responseStatusCode == 304) ||
1367 (containerRequest.responseStatusCode == 403)) {
1368 NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
1369 ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1370 if (containerRequest.responseStatusCode == 200) {
1371 NSArray *someObjects = [containerRequest objects];
1372 if (objects == nil) {
1373 objects = [[NSMutableArray alloc] initWithArray:someObjects];
1375 [objects addObjectsFromArray:someObjects];
1377 if ([someObjects count] < 10000) {
1378 pithosContainer.blockHash = [containerRequest blockHash];
1379 pithosContainer.blockSize = [containerRequest blockSize];
1380 pithosContainer.lastModified = [containerRequest lastModified];
1381 NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1382 for (ASIPithosObject *object in objects) {
1383 [containerRemoteObjects setObject:object forKey:object.name];
1385 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1389 // Do an additional request to fetch more objects
1390 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1391 containerName:pithosContainer.name
1393 marker:[[someObjects lastObject] name]
1400 ifModifiedSince:pithosContainer.lastModified];
1401 if (![accountName isEqualToString:@""])
1402 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1403 newContainerRequest.delegate = self;
1404 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1405 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1406 newContainerRequest.userInfo = containerRequest.userInfo;
1407 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1408 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1412 } else if (containerRequest.responseStatusCode == 304) {
1413 NSMutableDictionary *containerRemoteObjects = [[previousRemoteObjects objectForKey:accountName]
1414 objectForKey:pithosContainer.name];
1415 if (containerRemoteObjects)
1416 [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
1417 } else if (containerRequest.responseStatusCode == 403) {
1418 [[remoteObjects objectForKey:accountName] setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
1422 if (containersIndex == [[accountsPithosContainers objectForKey:accountName] count]) {
1424 containersIndex = 0;
1426 if (accountsIndex < accountsCount) {
1427 accountName = [accountsNames objectAtIndex:accountsIndex];
1428 pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
1429 // Do a request for the next container
1430 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1431 containerName:pithosContainer.name
1440 ifModifiedSince:pithosContainer.lastModified];
1441 if (![accountName isEqualToString:@""])
1442 [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1443 newContainerRequest.delegate = self;
1444 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1445 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1446 newContainerRequest.userInfo = containerRequest.userInfo;
1447 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1448 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1452 self.previousRemoteObjects = remoteObjects;
1453 // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
1455 if (operation.isCancelled) {
1456 [self listRequestFailed:containerRequest];
1461 dispatch_async(dispatch_get_main_queue(), ^{
1462 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1463 withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1465 NSFileManager *fileManager = [NSFileManager defaultManager];
1467 // Compute current state of legal existing local objects
1468 // and add an empty stored state for legal new local objects since last sync
1469 self.currentLocalObjectStates = [NSMutableDictionary dictionary];
1470 for (NSString *accountName in accountsNames) {
1471 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1472 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1473 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1474 BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1475 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1476 objectForKey:pithosContainer.name];
1477 NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath];
1478 for (NSString *objectName in dirEnumerator) {
1479 if (operation.isCancelled) {
1480 operation.completionBlock = nil;
1481 [self saveLocalState];
1482 [self syncOperationFinishedWithSuccess:NO];
1487 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1488 NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1490 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1491 if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) {
1492 dispatch_async(dispatch_get_main_queue(), ^{
1493 [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error"
1494 message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.",
1495 containerDirectoryPath, pithosAccount.name]
1498 pithosAccount.syncActive = NO;
1500 } else if (fileExists) {
1501 NSArray *pathComponents = [objectName pathComponents];
1502 if ([pathComponents count] == 1) {
1503 if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) {
1504 // Skip excluded directory and its descendants, or root file with same name
1506 [dirEnumerator skipDescendants];
1507 // Remove stored state if any
1508 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1510 } else if (!isDirectory && containerExludeRootFiles) {
1511 // Skip excluded root file
1512 // Remove stored state if any
1513 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1517 // Include local object
1518 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
1519 if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1520 // New or modified existing local object, compute current state
1521 if (!storedLocalObjectState)
1522 // For new local object, also create empty stored state
1523 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1524 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
1525 blockHash:pithosContainer.blockHash
1526 blockSize:pithosContainer.blockSize]
1529 // Local object hasn't changed, set stored state also to current
1530 [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1534 [self saveLocalState];
1538 if (operation.isCancelled) {
1539 operation.completionBlock = nil;
1540 [self syncOperationFinishedWithSuccess:NO];
1545 // Add an empty stored state for legal new remote objects since last sync
1546 for (NSString *accountName in accountsNames) {
1547 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1548 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1549 BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1550 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1551 objectForKey:pithosContainer.name];
1552 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1553 for (NSString *objectName in containerRemoteObjects) {
1554 if (operation.isCancelled) {
1555 operation.completionBlock = nil;
1556 [self saveLocalState];
1557 [self syncOperationFinishedWithSuccess:NO];
1562 ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName];
1563 NSString *localObjectName;
1564 if ([object.name hasSuffix:@"/"])
1565 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1567 localObjectName = [NSString stringWithString:object.name];
1568 NSArray *pathComponents = [localObjectName pathComponents];
1569 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1570 // Skip excluded directory object and its descendants, or root file object with same name
1571 // Remove stored state if any
1572 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1574 } else if (containerExludeRootFiles &&
1575 ([pathComponents count] == 1) &&
1576 ![PithosUtilities isContentTypeDirectory:object.contentType]) {
1577 // Skip root file object
1578 // Remove stored state if any
1579 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1582 if (![containerStoredLocalObjectStates objectForKey:object.name])
1583 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name];
1585 [self saveLocalState];
1589 if (operation.isCancelled) {
1590 operation.completionBlock = nil;
1591 [self syncOperationFinishedWithSuccess:NO];
1596 // For each stored state compare with current and remote state
1597 // Stored states of local objects that have been deleted,
1598 // haven't been checked for legality (only existing local remote objects)
1599 // These should be identified and skipped
1600 for (NSString *accountName in accountsNames) {
1601 for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
1602 NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
1603 NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
1604 BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1605 NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
1606 objectForKey:pithosContainer.name];
1607 NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
1608 for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1609 if (operation.isCancelled) {
1610 operation.completionBlock = nil;
1611 [self syncOperationFinishedWithSuccess:NO];
1616 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1617 if ([objectName hasSuffix:@"/"])
1618 filePath = [filePath stringByAppendingString:@":"];
1619 ASIPithosObject *object = [ASIPithosObject object];
1620 object.name = objectName;
1621 NSLog(@"Sync::object name: %@", object.name);
1623 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
1624 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1625 if (!currentLocalObjectState) {
1626 // The stored state corresponds to a remote or deleted local object, that's why there is no current state
1627 // In the latter case it must be checked for legality, which can be determined by its stored state
1628 // If it existed locally, but was deleted, state.exists is true,
1629 // else if the stored state is an empty state that was created due to the server object, state.exists is false
1630 if (storedLocalObjectState.exists) {
1631 NSString *localObjectName;
1632 if ([object.name hasSuffix:@"/"])
1633 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1635 localObjectName = [NSString stringWithString:object.name];
1636 NSArray *pathComponents = [localObjectName pathComponents];
1637 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1638 // Skip excluded directory object and its descendants, or root file object with same name
1639 // Remove stored state
1640 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1641 [self saveLocalState];
1643 } else if (containerExludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) {
1644 // Skip root file object
1645 // Remove stored state
1646 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1647 [self saveLocalState];
1651 // There is also the off case that a local object has been created in the meantime
1652 // This call works in any case, existent or non-existent local object
1653 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
1654 blockHash:pithosContainer.blockHash
1655 blockSize:pithosContainer.blockSize];
1658 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1659 ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
1661 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1662 remoteObjectState.isDirectory = YES;
1664 remoteObjectState.hash = remoteObject.objectHash;
1668 BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1669 BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1670 NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1671 if (!localStateHasChanged) {
1672 // Local state hasn't changed
1673 if (serverStateHasChanged) {
1674 // Server state has changed
1675 // Update local state to match that of the server
1676 object.bytes = remoteObject.bytes;
1677 object.version = remoteObject.version;
1678 object.contentType = remoteObject.contentType;
1679 object.objectHash = remoteObject.objectHash;
1680 [self updateLocalStateWithObject:object localFilePath:filePath
1681 accountName:accountName pithosContainer:pithosContainer];
1682 } else if (!remoteObject && ![currentLocalObjectState exists]) {
1683 // Server state hasn't changed
1684 // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1685 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1686 [self saveLocalState];
1689 // Local state has changed
1690 if (!serverStateHasChanged) {
1691 // Server state hasn't changed
1692 if (currentLocalObjectState.isDirectory)
1693 object.contentType = @"application/directory";
1695 object.objectHash = currentLocalObjectState.hash;
1696 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1697 accountName:accountName pithosContainer:pithosContainer];
1699 // Server state has also changed
1700 if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1701 // Both did the same change (directory)
1702 storedLocalObjectState.filePath = filePath;
1703 storedLocalObjectState.isDirectory = YES;
1704 [self saveLocalState];
1705 } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1706 // Both did the same change (object edit or delete)
1707 if (![remoteObjectState exists]) {
1708 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1710 storedLocalObjectState.filePath = filePath;
1711 storedLocalObjectState.hash = remoteObjectState.hash;
1713 [self saveLocalState];
1715 // Conflict, we ask the user which change to keep
1716 NSString *informativeText;
1717 NSString *firstButtonText;
1718 NSString *secondButtonText;
1720 if (![remoteObjectState exists]) {
1721 // Remote object has been deleted
1722 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.",
1723 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name ];
1724 firstButtonText = @"Delete local file";
1725 secondButtonText = @"Upload file to server";
1726 } else if (![currentLocalObjectState exists]) {
1727 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.",
1728 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1729 firstButtonText = @"Download file from server";
1730 secondButtonText = @"Delete file on server";
1732 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.",
1733 [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
1734 firstButtonText = @"Keep server version";
1735 secondButtonText = @"Keep local version";
1737 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1738 [alert setMessageText:@"Conflict"];
1739 [alert setInformativeText:informativeText];
1740 [alert addButtonWithTitle:firstButtonText];
1741 [alert addButtonWithTitle:secondButtonText];
1742 [alert addButtonWithTitle:@"Do nothing"];
1743 NSInteger choice = [alert runModal];
1744 if (choice == NSAlertFirstButtonReturn) {
1745 object.bytes = remoteObject.bytes;
1746 object.version = remoteObject.version;
1747 object.contentType = remoteObject.contentType;
1748 object.objectHash = remoteObject.objectHash;
1749 [self updateLocalStateWithObject:object localFilePath:filePath
1750 accountName:accountName pithosContainer:pithosContainer];
1751 } if (choice == NSAlertSecondButtonReturn) {
1752 if (currentLocalObjectState.isDirectory)
1753 object.contentType = @"application/directory";
1755 object.objectHash = currentLocalObjectState.hash;
1756 [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
1757 accountName:accountName pithosContainer:pithosContainer];
1765 [self syncOperationFinishedWithSuccess:YES];
1767 [self listRequestFailed:containerRequest];
1772 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1773 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1774 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1775 if (operation.isCancelled) {
1781 if (containerRequest.isCancelled) {
1782 dispatch_async(dispatch_get_main_queue(), ^{
1783 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1784 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1788 [self syncOperationFinishedWithSuccess:NO];
1792 // If the server listing fails, the sync should start over, so just retrying is enough
1793 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1795 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1796 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1797 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1799 dispatch_async(dispatch_get_main_queue(), ^{
1800 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1801 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1805 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1806 [self syncOperationFinishedWithSuccess:NO];
1811 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1812 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1813 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1814 NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1815 if (operation.isCancelled) {
1816 [self requestFailed:objectRequest];
1817 } else if (objectRequest.responseStatusCode == 206) {
1818 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1819 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1820 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1821 NSFileManager *fileManager = [NSFileManager defaultManager];
1823 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1825 NSString *downloadsDirPath = self.tempDownloadsDirPath;
1826 if (!downloadsDirPath) {
1827 dispatch_async(dispatch_get_main_queue(), ^{
1828 [activityFacility endActivity:activity
1829 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1831 [self syncOperationFinishedWithSuccess:NO];
1836 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1837 objectForKey:pithosContainer.name]
1838 objectForKey:object.name];
1839 if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1840 NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1841 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1842 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1843 strcpy(tempFileNameCString, tempFileTemplateCString);
1844 int fileDescriptor = mkstemp(tempFileNameCString);
1845 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1846 free(tempFileNameCString);
1847 if (fileDescriptor == -1) {
1848 dispatch_async(dispatch_get_main_queue(), ^{
1849 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
1850 message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]
1852 [activityFacility endActivity:activity
1853 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1855 [self syncOperationFinishedWithSuccess:NO];
1859 close(fileDescriptor);
1860 storedState.tmpFilePath = tempFilePath;
1861 [self saveLocalState];
1864 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1865 NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1866 [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
1867 [tempFileHandle writeData:[objectRequest responseData]];
1868 [tempFileHandle closeFile];
1870 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1871 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1872 if (missingBlockIndex == NSNotFound) {
1873 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1874 NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1875 if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath
1876 accountName:accountName
1877 pithosContainer:pithosContainer]) {
1878 dispatch_async(dispatch_get_main_queue(), ^{
1879 [activityFacility endActivity:activity
1880 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1882 [self syncOperationFinishedWithSuccess:NO];
1885 } else if (![fileManager fileExistsAtPath:dirPath]) {
1886 // File doesn't exist but also the containing directory doesn't exist
1887 // In most cases this should have been resolved as an update of the corresponding local object,
1888 // but it never hurts to check
1890 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1892 dispatch_async(dispatch_get_main_queue(), ^{
1893 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1894 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
1896 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1897 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1899 [self syncOperationFinishedWithSuccess:NO];
1904 // Move file from tmp download
1906 [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1908 dispatch_async(dispatch_get_main_queue(), ^{
1909 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
1910 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath]
1912 [activityFacility endActivity:activity
1913 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1915 [self syncOperationFinishedWithSuccess:NO];
1919 dispatch_async(dispatch_get_main_queue(), ^{
1920 [activityFacility endActivity:activity
1921 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1922 totalBytes:activity.totalBytes
1923 currentBytes:activity.totalBytes];
1926 storedState.filePath = filePath;
1927 storedState.hash = object.objectHash;
1928 storedState.tmpFilePath = nil;
1929 [self saveLocalState];
1930 [self syncOperationFinishedWithSuccess:YES];
1934 if (newSyncRequested || syncLate || operation.isCancelled) {
1935 [self requestFailed:objectRequest];
1937 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1938 containerName:pithosContainer.name
1940 blockIndex:missingBlockIndex
1941 blockSize:pithosContainer.blockSize];
1942 if (![accountName isEqualToString:@""])
1943 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
1944 newObjectRequest.delegate = self;
1945 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1946 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1947 newObjectRequest.userInfo = objectRequest.userInfo;
1948 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1949 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1950 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1951 [activityFacility updateActivity:activity
1952 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
1953 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
1954 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1955 totalBytes:activity.totalBytes
1956 currentBytes:(activity.currentBytes + size)];
1958 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1961 } else if (objectRequest.responseStatusCode == 412) {
1962 // The object has changed on the server
1963 dispatch_async(dispatch_get_main_queue(), ^{
1964 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1965 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1967 [self syncOperationFinishedWithSuccess:NO];
1969 [self requestFailed:objectRequest];
1974 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1975 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1976 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1977 NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1978 if (operation.isCancelled) {
1979 [self requestFailed:objectRequest];
1980 } else if (objectRequest.responseStatusCode == 200) {
1981 if (newSyncRequested || syncLate || operation.isCancelled) {
1982 [self requestFailed:objectRequest];
1984 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
1985 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1986 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1987 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
1988 objectForKey:pithosContainer.name]
1989 objectForKey:object.name];
1990 if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1991 [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
1992 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1993 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath
1994 blockSize:pithosContainer.blockSize
1995 blockHash:pithosContainer.blockHash
1996 withHashes:[objectRequest hashes]];
1997 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1998 dispatch_async(dispatch_get_main_queue(), ^{
1999 [activityFacility updateActivity:activity
2000 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2001 [objectRequest.userInfo objectForKey:@"messagePrefix"],
2002 (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))]
2003 totalBytes:activity.totalBytes
2004 currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
2007 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
2008 containerName:pithosContainer.name
2010 blockIndex:missingBlockIndex
2011 blockSize:pithosContainer.blockSize];
2012 if (![accountName isEqualToString:@""])
2013 [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
2014 newObjectRequest.delegate = self;
2015 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2016 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2017 newObjectRequest.userInfo = objectRequest.userInfo;
2018 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2019 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2020 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2021 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
2022 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
2023 [activityFacility updateActivity:activity
2024 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2025 [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
2026 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2027 totalBytes:activity.totalBytes
2028 currentBytes:(activity.currentBytes + size)];
2030 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2033 [self requestFailed:objectRequest];
2038 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2039 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2040 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2041 NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
2042 if (operation.isCancelled) {
2043 [self requestFailed:objectRequest];
2044 } else if (objectRequest.responseStatusCode == 201) {
2045 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2046 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2047 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2048 storedState.isDirectory = YES;
2049 [self saveLocalState];
2050 dispatch_async(dispatch_get_main_queue(), ^{
2051 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2052 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2054 [self syncOperationFinishedWithSuccess:YES];
2056 [self requestFailed:objectRequest];
2061 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
2062 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2063 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2064 NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
2065 if (operation.isCancelled) {
2066 [self requestFailed:objectRequest];
2067 } else if (objectRequest.responseStatusCode == 201) {
2068 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2069 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2070 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2071 [self saveLocalState];
2072 dispatch_async(dispatch_get_main_queue(), ^{
2073 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2074 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2076 [self syncOperationFinishedWithSuccess:YES];
2078 [self requestFailed:objectRequest];
2083 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
2084 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2085 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2086 NSLog(@"Sync::delete object finished: %@", objectRequest.url);
2087 if (operation.isCancelled) {
2088 [self requestFailed:objectRequest];
2089 } else if (objectRequest.responseStatusCode == 204) {
2090 [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
2091 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
2092 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
2093 [self saveLocalState];
2094 dispatch_async(dispatch_get_main_queue(), ^{
2095 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2096 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2098 [self syncOperationFinishedWithSuccess:YES];
2100 [self requestFailed:objectRequest];
2105 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
2106 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2107 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
2108 NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
2109 NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
2110 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
2111 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
2112 PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
2113 objectForKey:pithosContainer.name]
2114 objectForKey:object.name];
2115 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
2116 NSUInteger totalBytes = activity.totalBytes;
2117 NSUInteger currentBytes = activity.currentBytes;
2118 if (operation.isCancelled) {
2119 [self requestFailed:objectRequest];
2120 } else if (objectRequest.responseStatusCode == 201) {
2121 NSLog(@"Sync::object created: %@", objectRequest.url);
2122 storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
2123 storedState.hash = object.objectHash;
2124 [self saveLocalState];
2125 dispatch_async(dispatch_get_main_queue(), ^{
2126 [activityFacility endActivity:activity
2127 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
2128 totalBytes:totalBytes
2129 currentBytes:totalBytes];
2131 [self syncOperationFinishedWithSuccess:YES];
2132 } else if (objectRequest.responseStatusCode == 409) {
2133 if (newSyncRequested || syncLate || operation.isCancelled) {
2134 [self requestFailed:objectRequest];
2136 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
2137 if (iteration == 0) {
2138 NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
2139 dispatch_async(dispatch_get_main_queue(), ^{
2140 [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
2142 [self syncOperationFinishedWithSuccess:NO];
2146 NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
2147 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
2148 withMissingHashes:[objectRequest hashes]];
2149 if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
2150 currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
2151 dispatch_async(dispatch_get_main_queue(), ^{
2152 [activityFacility updateActivity:activity
2153 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2154 [objectRequest.userInfo objectForKey:@"messagePrefix"],
2155 (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
2156 totalBytes:totalBytes
2157 currentBytes:currentBytes];
2159 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2160 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2161 containerName:pithosContainer.name
2162 blockSize:pithosContainer.blockSize
2163 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
2164 missingBlockIndex:missingBlockIndex
2165 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2166 newContainerRequest.delegate = self;
2167 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2168 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2169 newContainerRequest.userInfo = objectRequest.userInfo;
2170 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
2171 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2172 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2173 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2174 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
2175 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2176 [activityFacility updateActivity:activity
2177 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2178 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2179 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2180 totalBytes:activity.totalBytes
2181 currentBytes:(activity.currentBytes + size)];
2183 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2186 [self requestFailed:objectRequest];
2191 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
2192 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2193 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
2194 NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
2195 if (operation.isCancelled) {
2196 [self requestFailed:containerRequest];
2197 } else if (containerRequest.responseStatusCode == 202) {
2198 NSString *accountName = [containerRequest.userInfo objectForKey:@"accountName"];
2199 ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
2200 ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
2201 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
2202 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
2203 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
2204 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
2205 if (operation.isCancelled) {
2206 [self requestFailed:containerRequest];
2207 } else if (missingBlockIndex == NSNotFound) {
2208 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
2209 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
2210 containerName:pithosContainer.name
2211 objectName:object.name
2212 contentType:object.contentType
2213 blockSize:pithosContainer.blockSize
2214 blockHash:pithosContainer.blockHash
2215 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2218 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2219 newObjectRequest.delegate = self;
2220 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2221 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2222 newObjectRequest.userInfo = containerRequest.userInfo;
2223 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2224 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
2225 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
2226 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
2227 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2229 if (newSyncRequested || syncLate || operation.isCancelled) {
2230 [self requestFailed:containerRequest];
2232 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2233 containerName:pithosContainer.name
2234 blockSize:pithosContainer.blockSize
2235 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2236 missingBlockIndex:missingBlockIndex
2237 sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
2238 newContainerRequest.delegate = self;
2239 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2240 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2241 newContainerRequest.userInfo = containerRequest.userInfo;
2242 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2243 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2244 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2245 [activityFacility updateActivity:activity
2246 withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
2247 [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
2248 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2249 totalBytes:activity.totalBytes
2250 currentBytes:(activity.currentBytes + size)];
2252 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2256 [self requestFailed:containerRequest];
2261 - (void)requestFailed:(ASIPithosRequest *)request {
2262 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2263 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
2264 NSLog(@"Sync::request failed: %@", request.url);
2265 if (operation.isCancelled) {
2269 if (request.isCancelled || newSyncRequested || syncLate) {
2270 dispatch_async(dispatch_get_main_queue(), ^{
2271 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2272 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
2274 [self syncOperationFinishedWithSuccess:NO];
2278 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
2280 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
2281 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
2282 [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
2284 dispatch_async(dispatch_get_main_queue(), ^{
2285 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2286 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
2288 [self syncOperationFinishedWithSuccess:NO];