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)moveToTempTrashFile:(NSString *)filePath pithosContainer:(ASIPithosContainer *)pithosContainer;
57 - (void)emptyTempTrash;
58 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
60 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
61 localFilePath:(NSString *)filePath
62 pithosContainer:(ASIPithosContainer *)pithosContainer;
63 - (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
64 object:(ASIPithosObject *)object
65 localFilePath:(NSString *)filePath
66 pithosContainer:(ASIPithosContainer *)pithosContainer;
67 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest;
68 - (void)requestFailed:(ASIPithosRequest *)request;
70 - (void)syncOperationStarted;
71 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull;
75 @implementation PithosSyncDaemon
76 @synthesize directoryPath, accountsDictionary, pithos;
77 @synthesize containersDictionary, pithosContainers;
78 @synthesize lastCompletedSync, remoteObjects, previousRemoteObjects, storedLocalObjectStates, currentLocalObjectStates;
79 @synthesize pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
82 #pragma Object Lifecycle
84 - (id)initWithDirectoryPath:(NSString *)aDirectoryPath
85 pithosAccount:(PithosAccount *)aPithosAccount
86 accountsDictionary:(NSDictionary *)anAccountsDictionary
87 resetLocalState:(BOOL)resetLocalState {
88 if ((self = [super init])) {
89 directoryPath = [aDirectoryPath copy];
90 pithosAccount = [aPithosAccount retain];
91 accountsDictionary = [anAccountsDictionary copy];
92 self.pithos = pithosAccount.pithos;
94 self.containersDictionary = [accountsDictionary objectForKey:@""];
95 if (!containersDictionary)
96 self.containersDictionary = [NSDictionary dictionary];
97 containersCount = [containersDictionary count];
98 self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount];
99 for (NSString *containerName in containersDictionary) {
100 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
101 pithosContainer.name = containerName;
102 [pithosContainers addObject:pithosContainer];
105 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
108 [self resetLocalStateWithAll:YES];
110 [self resetLocalStateWithAll:NO];
112 networkQueue = [[ASINetworkQueue alloc] init];
113 networkQueue.showAccurateProgress = YES;
114 networkQueue.shouldCancelAllRequestsOnFailure = NO;
115 // networkQueue.maxConcurrentOperationCount = 1;
117 callbackQueue = [[NSOperationQueue alloc] init];
118 [callbackQueue setSuspended:YES];
119 callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
120 // callbackQueue.maxConcurrentOperationCount = 1;
122 [[NSNotificationCenter defaultCenter] addObserver:self
123 selector:@selector(applicationWillTerminate:)
124 name:NSApplicationWillTerminateNotification
125 object:[NSApplication sharedApplication]];
130 - (void)loadLocalState {
131 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
132 if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath])
133 self.storedLocalObjectStates = [NSKeyedUnarchiver unarchiveObjectWithFile:self.pithosStateFilePath];
135 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
136 if (!storedLocalObjectStates)
137 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
138 for (ASIPithosContainer *pithosContainer in pithosContainers) {
139 if (![storedLocalObjectStates objectForKey:pithosContainer.name]) {
140 [storedLocalObjectStates setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
146 - (void)resetLocalStateWithAll:(BOOL)all {
147 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
148 self.lastCompletedSync = nil;
149 NSFileManager *fileManager = [NSFileManager defaultManager];
152 self.storedLocalObjectStates = [NSMutableDictionary dictionary];
153 [self saveLocalState]; // Save an empty dictionary
154 [self loadLocalState]; // Load to populate with containers
155 [self saveLocalState]; // Save again
156 if (self.tempDownloadsDirPath) {
158 for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
160 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
161 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath]
165 NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
166 if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
167 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
168 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
175 // Remove containers that don't interest us anymore and save
176 if (!storedLocalObjectStates)
177 [self loadLocalState];
178 for (NSString *containerName in storedLocalObjectStates) {
179 if (![containersDictionary objectForKey:containerName]) {
180 [storedLocalObjectStates removeObjectForKey:containerName];
181 if (self.tempDownloadsDirPath) {
182 NSString *containerTempDownloadsDirPath = [self.tempDownloadsDirPath stringByAppendingPathComponent:containerName];
184 BOOL fileExists = [fileManager fileExistsAtPath:containerTempDownloadsDirPath isDirectory:&isDirectory];
185 if (fileExists && isDirectory) {
187 for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:containerTempDownloadsDirPath error:&error]) {
189 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
190 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'",
191 containerTempDownloadsDirPath]
195 NSString *subFilePath = [containerTempDownloadsDirPath stringByAppendingPathComponent:subPath];
196 if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
197 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
198 message:[NSString stringWithFormat:@"Cannot remove file at '%@'",
204 } else if (fileExists && !isDirectory) {
206 if (![fileManager removeItemAtPath:containerTempDownloadsDirPath error:&error] || error) {
207 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
208 message:[NSString stringWithFormat:@"Cannot remove file at '%@'",
209 containerTempDownloadsDirPath]
216 [self saveLocalState];
221 - (void)saveLocalState {
222 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
223 [NSKeyedArchiver archiveRootObject:storedLocalObjectStates toFile:self.pithosStateFilePath];
227 - (void)resetDaemon {
228 @synchronized(self) {
233 [networkQueue reset];
234 [callbackQueue cancelAllOperations];
235 [callbackQueue setSuspended:YES];
236 [self emptyTempTrash];
238 syncOperationCount = 0;
240 @synchronized(self) {
245 - (void)startDaemon {
246 @synchronized(self) {
251 // In the improbable case of leftover operations
252 [networkQueue reset];
253 [callbackQueue cancelAllOperations];
255 syncOperationCount = 0;
256 newSyncRequested = NO;
260 [self loadLocalState];
263 [callbackQueue setSuspended:NO];
265 @synchronized(self) {
271 [[NSNotificationCenter defaultCenter] removeObserver:self];
273 [callbackQueue release];
274 [networkQueue release];
275 [tempTrashDirPath release];
276 [tempDownloadsDirPath release];
277 [pithosStateFilePath release];
278 [currentLocalObjectStates release];
279 [storedLocalObjectStates release];
280 [previousRemoteObjects release];
281 [remoteObjects release];
283 [lastCompletedSync release];
284 [pithosContainers release];
285 [containersDictionary release];
287 [accountsDictionary release];
288 [pithosAccount release];
289 [directoryPath release];
294 #pragma mark Observers
296 - (void)applicationWillTerminate:(NSNotification *)notification {
297 [self saveLocalState];
301 #pragma mark Properties
303 - (NSString *)pithosStateFilePath {
304 if (!pithosStateFilePath) {
305 pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
306 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
307 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive",
308 pithosAccount.uniqueName]] retain];
309 NSString *pithosStateFileDirPath = [pithosStateFilePath stringByDeletingLastPathComponent];
310 NSFileManager *fileManager = [NSFileManager defaultManager];
312 BOOL fileExists = [fileManager fileExistsAtPath:pithosStateFileDirPath isDirectory:&isDirectory];
313 NSError *error = nil;
314 if (fileExists && !isDirectory)
315 [fileManager removeItemAtPath:pithosStateFileDirPath error:&error];
316 if (!error && !fileExists)
317 [fileManager createDirectoryAtPath:pithosStateFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
319 // pithosStateFilePath = nil;
320 // XXX create a dir using mktmps?
322 return [[pithosStateFilePath copy] autorelease];
325 - (NSString *)tempDownloadsDirPath {
326 if (!tempDownloadsDirPath) {
327 tempDownloadsDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
328 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
329 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads",
330 pithosAccount.uniqueName]] retain];
331 NSFileManager *fileManager = [NSFileManager defaultManager];
333 BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
334 NSError *error = nil;
335 if (fileExists && !isDirectory)
336 [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
337 if (!error && !fileExists)
338 [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
340 // tempDownloadsDirPath = nil;
341 // XXX create a dir using mktmps?
343 return tempDownloadsDirPath;
346 - (NSString *)tempTrashDirPath {
347 if (!tempTrashDirPath) {
348 tempTrashDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
349 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
350 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash",
351 pithosAccount.uniqueName]] retain];
352 NSFileManager *fileManager = [NSFileManager defaultManager];
354 BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
355 NSError *error = nil;
356 if (fileExists && !isDirectory)
357 [fileManager removeItemAtPath:tempTrashDirPath error:&error];
358 if (!error && !fileExists)
359 [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
361 // tempTrashDirPath = nil;
362 // XXX create a dir using mktmps?
364 return tempTrashDirPath;
367 - (void)setDirectoryPath:(NSString *)aDirectoryPath {
368 if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
370 [self resetLocalStateWithAll:YES];
371 [directoryPath release];
372 directoryPath = [aDirectoryPath copy];
376 - (void)setAccountsDictionary:(NSDictionary *)anAccountsDictionary {
377 if (anAccountsDictionary && ![anAccountsDictionary isEqualToDictionary:accountsDictionary]) {
379 [accountsDictionary release];
380 accountsDictionary = [anAccountsDictionary copy];
382 self.containersDictionary = [accountsDictionary objectForKey:@""];
383 if (!containersDictionary)
384 self.containersDictionary = [NSDictionary dictionary];
385 containersCount = [containersDictionary count];
386 self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount];
387 for (NSString *containerName in containersDictionary) {
388 ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
389 pithosContainer.name = containerName;
390 [pithosContainers addObject:pithosContainer];
392 [self resetLocalStateWithAll:NO];
396 - (void)setPithos:(ASIPithos *)aPithos {
398 pithos = [[ASIPithos pithos] retain];
399 pithos.authUser = [[aPithos.authUser copy] autorelease];
400 pithos.authToken = [[aPithos.authToken copy] autorelease];
401 pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
402 pithos.authURL = [[aPithos.authURL copy] autorelease];
403 pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
406 (![aPithos.authUser isEqualToString:pithos.authUser] ||
407 ![aPithos.authToken isEqualToString:pithos.authToken] ||
408 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
410 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
411 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
412 [self resetLocalStateWithAll:YES];
413 pithos.authUser = [[aPithos.authUser copy] autorelease];
414 pithos.authToken = [[aPithos.authToken copy] autorelease];
415 pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
416 pithos.authURL = [[aPithos.authURL copy] autorelease];
417 pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
424 - (void)syncOperationStarted {
425 @synchronized(self) {
426 syncOperationCount++;
430 - (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
431 @synchronized(self) {
432 if (!operationSuccessfull)
433 syncIncomplete = YES;
434 if (syncOperationCount == 0) {
435 // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
436 NSLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
439 syncOperationCount--;
440 if (syncOperationCount == 0) {
441 if (!syncIncomplete) {
442 self.lastCompletedSync = [NSDate date];
443 dispatch_async(dispatch_get_main_queue(), ^{
444 [activityFacility startAndEndActivityWithType:PithosActivityOther
445 message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]
446 pithosAccount:pithosAccount];
450 [self emptyTempTrash];
451 if (newSyncRequested && daemonActive)
458 @synchronized(self) {
459 return ((syncOperationCount > 0) && daemonActive);
464 @synchronized(self) {
465 if ([self isSyncing])
471 @synchronized(self) {
472 if ([self isSyncing]) {
473 // If at least one operation is running return
474 newSyncRequested = YES;
476 } else if (daemonActive && containersCount) {
477 // The first operation is the server listing
478 [self syncOperationStarted];
479 newSyncRequested = NO;
487 NSFileManager *fileManager = [NSFileManager defaultManager];
489 NSError *error = nil;
490 if (![fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory]) {
491 if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error] ||
493 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
494 message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", directoryPath]
496 [self syncOperationFinishedWithSuccess:NO];
499 } else if (!isDirectory) {
500 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
501 message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", directoryPath]
503 [self syncOperationFinishedWithSuccess:NO];
506 for (ASIPithosContainer *pithosContainer in pithosContainers) {
507 NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
509 if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
510 if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] ||
512 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
513 message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'",
514 containerDirectoryPath]
516 [self syncOperationFinishedWithSuccess:NO];
519 } else if (!isDirectory) {
520 [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
521 message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'",
522 containerDirectoryPath]
524 [self syncOperationFinishedWithSuccess:NO];
529 self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:containersCount];
530 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
531 containerName:[[pithosContainers objectAtIndex:containersIndex] name]
540 ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
541 containerRequest.delegate = self;
542 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
543 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
544 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther
545 message:@"Sync: Getting server listing"
546 pithosAccount:pithosAccount];
547 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
548 activity, @"activity",
549 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage",
550 @"Sync: Getting server listing (failed)", @"failedActivityMessage",
551 @"Sync: Getting server listing (finished)", @"finishedActivityMessage",
552 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
553 [NSNumber numberWithUnsignedInteger:10], @"retries",
554 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector",
555 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector",
557 [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
560 - (void)emptyTempTrash {
561 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
562 NSString *trashDirPath = self.tempTrashDirPath;
564 NSFileManager *fileManager = [NSFileManager defaultManager];
565 NSError *error = nil;
566 // NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
567 NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
569 dispatch_async(dispatch_get_main_queue(), ^{
570 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
571 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath]
577 if ([subPaths count]) {
578 // NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
579 // for (NSString *subPath in subPaths) {
580 // [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
582 // [self syncOperationStarted];
583 // [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
585 // dispatch_async(dispatch_get_main_queue(), ^{
586 // [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
587 // message:@"Cannot move files to Trash"
591 // [self syncOperationFinishedWithSuccess:YES];
593 for (NSString *subPath in subPaths) {
594 NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
596 if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
597 dispatch_async(dispatch_get_main_queue(), ^{
598 [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
599 message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
608 - (BOOL)moveToTempTrashFile:(NSString *)filePath pithosContainer:(ASIPithosContainer *)pithosContainer {
609 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
610 if (!self.tempTrashDirPath) {
614 NSFileManager *fileManager = [NSFileManager defaultManager];
616 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
617 NSError *error = nil;
618 NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
619 NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath withString:self.tempTrashDirPath];
620 NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
621 if (fileExists && isDirectory) {
622 NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
624 dispatch_async(dispatch_get_main_queue(), ^{
625 [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
626 message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath]
632 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
633 dispatch_async(dispatch_get_main_queue(), ^{
634 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
635 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
641 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
642 dispatch_async(dispatch_get_main_queue(), ^{
643 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
644 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
645 filePath, newFilePath]
651 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
653 currentState.filePath = newFilePath;
654 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
655 [currentLocalObjectStates removeObjectForKey:filePath];
657 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
658 blockHash:pithosContainer.blockHash
659 blockSize:pithosContainer.blockSize]
662 for (NSString *subPath in subPaths) {
663 NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
664 NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath
665 withString:self.tempTrashDirPath];
666 currentState = [currentLocalObjectStates objectForKey:subFilePath];
668 currentState.filePath = newSubFilePath;
669 [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
670 [currentLocalObjectStates removeObjectForKey:subFilePath];
672 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
673 blockHash:pithosContainer.blockHash
674 blockSize:pithosContainer.blockSize]
675 forKey:newSubFilePath];
678 } else if (fileExists && !isDirectory) {
679 if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
680 dispatch_async(dispatch_get_main_queue(), ^{
681 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
682 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
688 if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
689 dispatch_async(dispatch_get_main_queue(), ^{
690 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
691 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
692 filePath, newFilePath]
698 PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
700 currentState.filePath = newFilePath;
701 [currentLocalObjectStates setObject:currentState forKey:newFilePath];
702 [currentLocalObjectStates removeObjectForKey:filePath];
704 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
705 blockHash:pithosContainer.blockHash
706 blockSize:pithosContainer.blockSize]
714 - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
715 if ([hash length] != 64)
717 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
718 PithosLocalObjectState *localState;
719 NSFileManager *fileManager = [NSFileManager defaultManager];
721 NSError *error = nil;
722 for (NSString *localFilePath in currentLocalObjectStates) {
723 localState = [currentLocalObjectStates objectForKey:localFilePath];
724 if (!localState.isDirectory && [hash isEqualToString:localState.hash] &&
725 [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
726 if ([localFilePath hasPrefix:directoryPath]) {
727 if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
728 dispatch_async(dispatch_get_main_queue(), ^{
729 [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
730 message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
731 localFilePath, filePath]
738 } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
739 if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
740 dispatch_async(dispatch_get_main_queue(), ^{
741 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
742 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
743 localFilePath, filePath]
747 localState.filePath = filePath;
748 [currentLocalObjectStates setObject:localState forKey:filePath];
749 [currentLocalObjectStates removeObjectForKey:localFilePath];
760 - (void)updateLocalStateWithObject:(ASIPithosObject *)object
761 localFilePath:(NSString *)filePath
762 pithosContainer:(ASIPithosContainer *)pithosContainer {
763 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
764 NSFileManager *fileManager = [NSFileManager defaultManager];
767 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
768 NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
769 NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
770 PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
771 // Remote updated info
772 NSError *remoteError;
773 BOOL remoteIsDirectory;
774 BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos
775 containerName:pithosContainer.name
776 objectName:object.name
778 isDirectory:&remoteIsDirectory
780 if (!object || !object.objectHash) {
781 // Delete local object
782 if (remoteObjectExists) {
783 // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
784 syncIncomplete = YES;
786 NSLog(@"Sync::delete local object: %@", filePath);
787 if (!fileExists || [self moveToTempTrashFile:filePath pithosContainer:pithosContainer]) {
788 dispatch_async(dispatch_get_main_queue(), ^{
789 [activityFacility startAndEndActivityWithType:PithosActivityOther
790 message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)",
791 pithosContainer.name, object.name]
792 pithosAccount:pithosAccount];
794 [containerStoredLocalObjectStates removeObjectForKey:object.name];
795 [self saveLocalState];
797 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
798 // Create local directory object
799 if (!remoteObjectExists || !remoteIsDirectory) {
800 // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
801 syncIncomplete = YES;
805 NSLog(@"Sync::create local directory object: %@", filePath);
806 BOOL directoryCreated = NO;
807 if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath pithosContainer:pithosContainer])) {
808 NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
810 if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
811 dispatch_async(dispatch_get_main_queue(), ^{
812 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
813 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
817 directoryCreated = YES;
818 storedState.filePath = filePath;
819 storedState.isDirectory = YES;
820 [self saveLocalState];
823 NSLog(@"Sync::local directory object exists: %@", filePath);
824 directoryCreated = YES;
826 if (directoryCreated)
827 dispatch_async(dispatch_get_main_queue(), ^{
828 [activityFacility startAndEndActivityWithType:PithosActivityOther
829 message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)",
830 pithosContainer.name, object.name]
831 pithosAccount:pithosAccount];
833 } else if (object.bytes == 0) {
834 // Create local object with zero length
835 if (!remoteObjectExists || remoteIsDirectory) {
836 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
837 syncIncomplete = YES;
841 NSLog(@"Sync::create local zero length object: %@", filePath);
842 BOOL fileCreated = NO;
843 if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath pithosContainer:pithosContainer])) {
844 NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
845 // Create directory of the file, if it doesn't exist
847 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
848 dispatch_async(dispatch_get_main_queue(), ^{
849 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
850 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
855 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
856 dispatch_async(dispatch_get_main_queue(), ^{
857 [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
858 message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
863 storedState.filePath = filePath;
864 storedState.hash = object.objectHash;
865 storedState.tmpFilePath = nil;
866 [self saveLocalState];
869 NSLog(@"Sync::local zero length object exists: %@", filePath);
873 dispatch_async(dispatch_get_main_queue(), ^{
874 [activityFacility startAndEndActivityWithType:PithosActivityOther
875 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
876 pithosContainer.name, object.name]
877 pithosAccount:pithosAccount];
879 } else if (storedState.tmpFilePath == nil) {
880 // Create new local object
881 if (!remoteObjectExists || remoteIsDirectory) {
882 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
883 syncIncomplete = YES;
887 // Create directory of the file, if it doesn't exist
889 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
890 dispatch_async(dispatch_get_main_queue(), ^{
891 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
892 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
896 // Check first if a local copy exists
897 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
898 storedState.filePath = filePath;
899 storedState.hash = object.objectHash;
900 [self saveLocalState];
901 dispatch_async(dispatch_get_main_queue(), ^{
902 [activityFacility startAndEndActivityWithType:PithosActivityOther
903 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
904 pithosContainer.name, object.name]
905 pithosAccount:pithosAccount];
908 [self syncOperationStarted];
909 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
910 containerName:pithosContainer.name
913 blockSize:pithosContainer.blockSize];
914 objectRequest.delegate = self;
915 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
916 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
917 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
918 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (0%%)",
919 pithosContainer.name, object.name]
920 totalBytes:object.bytes
922 pithosAccount:pithosAccount];
923 dispatch_async(dispatch_get_main_queue(), ^{
924 [activityFacility updateActivity:activity withMessage:activity.message];
926 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
927 pithosContainer, @"pithosContainer",
928 object, @"pithosObject",
929 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks",
930 [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
931 filePath, @"filePath",
932 activity, @"activity",
933 [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage",
934 [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage",
935 [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", pithosContainer.name, object.name], @"finishedActivityMessage",
936 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
937 [NSNumber numberWithUnsignedInteger:10], @"retries",
938 NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector",
939 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
941 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
942 [activityFacility updateActivity:activity
943 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)",
944 objectRequest.containerName,
945 [[objectRequest.userInfo objectForKey:@"pithosObject"] name],
946 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
947 totalBytes:activity.totalBytes
948 currentBytes:(activity.currentBytes + size)];
950 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
953 // Resume local object download
954 if (!remoteObjectExists || remoteIsDirectory) {
955 // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
956 syncIncomplete = YES;
960 // Create directory of the file, if it doesn't exist
962 if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
963 dispatch_async(dispatch_get_main_queue(), ^{
964 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
965 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
969 // Check first if a local copy exists
970 if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
971 storedState.filePath = filePath;
972 storedState.hash = object.objectHash;
973 // Delete incomplete temp download
974 storedState.tmpFilePath = nil;
975 [self saveLocalState];
976 dispatch_async(dispatch_get_main_queue(), ^{
977 [activityFacility startAndEndActivityWithType:PithosActivityOther
978 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
979 pithosContainer.name, object.name]
980 pithosAccount:pithosAccount];
983 [self syncOperationStarted];
984 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
985 containerName:pithosContainer.name
986 objectName:object.name];
987 objectRequest.delegate = self;
988 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
989 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
990 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
991 message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (0%%)",
992 pithosContainer.name, object.name]
993 totalBytes:object.bytes
995 pithosAccount:pithosAccount];
996 dispatch_async(dispatch_get_main_queue(), ^{
997 [activityFacility updateActivity:activity withMessage:activity.message];
999 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1000 pithosContainer, @"pithosContainer",
1001 object, @"pithosObject",
1002 filePath, @"filePath",
1003 activity, @"activity",
1004 [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage",
1005 [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage",
1006 [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", pithosContainer.name, object.name], @"finishedActivityMessage",
1007 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1008 [NSNumber numberWithUnsignedInteger:10], @"retries",
1009 NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector",
1010 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1012 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1018 -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
1019 object:(ASIPithosObject *)object
1020 localFilePath:(NSString *)filePath
1021 pithosContainer:(ASIPithosContainer *)pithosContainer {
1022 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1023 [self syncOperationStarted];
1024 NSFileManager *fileManager = [NSFileManager defaultManager];
1026 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1027 if (currentState.isDirectory) {
1028 // Create remote directory object
1029 if (!fileExists || !isDirectory) {
1030 // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
1031 [self syncOperationFinishedWithSuccess:NO];
1035 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
1036 containerName:pithosContainer.name
1037 objectName:object.name
1039 contentType:@"application/directory"
1041 contentDisposition:nil
1044 isPublic:ASIPithosObjectRequestPublicIgnore
1046 data:[NSData data]];
1047 objectRequest.delegate = self;
1048 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1049 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1050 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1051 message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@'",
1052 pithosContainer.name, object.name]
1053 pithosAccount:pithosAccount];
1054 dispatch_async(dispatch_get_main_queue(), ^{
1055 [activityFacility updateActivity:activity withMessage:activity.message];
1057 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1058 pithosContainer, @"pithosContainer",
1059 object, @"pithosObject",
1060 activity, @"activity",
1061 [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage",
1062 [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage",
1063 [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage",
1064 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1065 [NSNumber numberWithUnsignedInteger:10], @"retries",
1066 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1067 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1069 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1070 } else if (![currentState exists]) {
1071 // Delete remote object
1073 // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
1074 syncIncomplete = YES;
1076 if ([pithosContainer.name isEqualToString:@"trash"]) {
1078 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
1079 containerName:pithosContainer.name
1080 objectName:object.name];
1081 objectRequest.delegate = self;
1082 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1083 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1084 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1085 message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@'",
1086 pithosContainer.name, object.name]
1087 pithosAccount:pithosAccount];
1088 dispatch_async(dispatch_get_main_queue(), ^{
1089 [activityFacility updateActivity:activity withMessage:activity.message];
1091 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1092 pithosContainer, @"pithosContainer",
1093 object, @"pithosObject",
1094 activity, @"activity",
1095 [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage",
1096 [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage",
1097 [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage",
1098 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1099 [NSNumber numberWithUnsignedInteger:10], @"retries",
1100 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
1101 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1103 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1105 // Move to container trash
1107 if ([PithosUtilities isContentTypeDirectory:object.contentType])
1108 safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
1110 safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
1112 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1113 containerName:pithosContainer.name
1114 objectName:object.name
1115 destinationContainerName:@"trash"
1116 destinationObjectName:safeName
1118 if (objectRequest) {
1119 objectRequest.delegate = self;
1120 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1121 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1122 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1123 message:[NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'",
1124 pithosContainer.name, object.name]
1125 pithosAccount:pithosAccount];
1126 dispatch_async(dispatch_get_main_queue(), ^{
1127 [activityFacility updateActivity:activity withMessage:activity.message];
1129 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1130 pithosContainer, @"pithosContainer",
1131 object, @"pithosObject",
1132 activity, @"activity",
1133 [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage",
1134 [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage",
1135 [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage",
1136 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1137 [NSNumber numberWithUnsignedInteger:10], @"retries",
1138 NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector",
1139 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1141 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1143 [self syncOperationFinishedWithSuccess:NO];
1146 [self syncOperationFinishedWithSuccess:NO];
1150 // Upload file to remote object
1151 if (!fileExists || isDirectory) {
1152 // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
1153 [self syncOperationFinishedWithSuccess:NO];
1157 NSError *error = nil;
1158 object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1159 if (object.contentType == nil)
1160 object.contentType = @"application/octet-stream";
1162 NSLog(@"contentType detection error: %@", error);
1163 NSArray *hashes = nil;
1164 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1165 containerName:pithosContainer.name
1166 objectName:object.name
1167 contentType:object.contentType
1168 blockSize:pithosContainer.blockSize
1169 blockHash:pithosContainer.blockHash
1173 sharingAccount:nil];
1174 if (objectRequest) {
1175 objectRequest.delegate = self;
1176 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1177 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1178 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1179 message:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (0%%)",
1180 pithosContainer.name, object.name]
1181 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1183 pithosAccount:pithosAccount];
1184 dispatch_async(dispatch_get_main_queue(), ^{
1185 [activityFacility updateActivity:activity withMessage:activity.message];
1187 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1188 [NSDictionary dictionaryWithObjectsAndKeys:
1189 pithosContainer, @"pithosContainer",
1190 object, @"pithosObject",
1191 filePath, @"filePath",
1193 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1194 activity, @"activity",
1195 [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage",
1196 [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage",
1197 [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage",
1198 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1199 [NSNumber numberWithUnsignedInteger:10], @"retries",
1200 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1201 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1203 [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1205 [self syncOperationFinishedWithSuccess:NO];
1212 #pragma mark ASIHTTPRequestDelegate
1214 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1215 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1216 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1217 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1218 object:request] autorelease];
1219 operation.completionBlock = ^{
1220 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1221 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1222 dispatch_async(dispatch_get_main_queue(), ^{
1223 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1224 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1226 [self syncOperationFinishedWithSuccess:NO];
1230 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1231 [callbackQueue addOperation:operation];
1234 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1235 if (request.isCancelled) {
1236 // Request has been cancelled
1237 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1238 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1239 withObject:request];
1241 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1242 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1243 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1244 object:request] autorelease];
1245 operation.completionBlock = ^{
1246 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1247 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1248 dispatch_async(dispatch_get_main_queue(), ^{
1249 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1250 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1252 [self syncOperationFinishedWithSuccess:NO];
1256 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1257 [callbackQueue addOperation:operation];
1261 - (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1262 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1263 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1264 NSLog(@"Sync::list request finished: %@", containerRequest.url);
1265 if (operation.isCancelled) {
1266 [self listRequestFailed:containerRequest];
1267 } else if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
1268 if (containerRequest.responseStatusCode == 200) {
1269 NSArray *someObjects = [containerRequest objects];
1270 if (objects == nil) {
1271 objects = [[NSMutableArray alloc] initWithArray:someObjects];
1273 [objects addObjectsFromArray:someObjects];
1275 if ([someObjects count] < 10000) {
1276 ASIPithosContainer *pithosContainer = [pithosContainers objectAtIndex:containersIndex];
1277 pithosContainer.blockHash = [containerRequest blockHash];
1278 pithosContainer.blockSize = [containerRequest blockSize];
1279 pithosContainer.lastModified = [containerRequest lastModified];
1280 NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1281 for (ASIPithosObject *object in objects) {
1282 [containerRemoteObjects setObject:object forKey:object.name];
1284 [remoteObjects setObject:containerRemoteObjects forKey:pithosContainer.name];
1288 // Do an additional request to fetch more objects
1289 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1290 containerName:[[pithosContainers objectAtIndex:containersIndex] name]
1292 marker:[[someObjects lastObject] name]
1299 ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
1300 newContainerRequest.delegate = self;
1301 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1302 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1303 newContainerRequest.userInfo = containerRequest.userInfo;
1304 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1305 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1310 ASIPithosContainer *pithosContainer = [pithosContainers objectAtIndex:containersIndex];
1311 NSMutableDictionary *containerRemoteObjects = [previousRemoteObjects objectForKey:pithosContainer.name];
1312 if (containerRemoteObjects)
1313 [remoteObjects setObject:containerRemoteObjects forKey:pithosContainer.name];
1316 if (containersIndex < containersCount) {
1317 // Do a request for the next container
1318 ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
1319 containerName:[[pithosContainers objectAtIndex:containersIndex] name]
1328 ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
1329 newContainerRequest.delegate = self;
1330 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1331 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1332 newContainerRequest.userInfo = containerRequest.userInfo;
1333 [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1334 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1338 self.previousRemoteObjects = remoteObjects;
1339 // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
1341 if (operation.isCancelled) {
1342 [self listRequestFailed:containerRequest];
1347 dispatch_async(dispatch_get_main_queue(), ^{
1348 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1349 withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1351 NSFileManager *fileManager = [NSFileManager defaultManager];
1353 // Compute current state of legal existing local objects
1354 // and add an empty stored state for legal new local objects since last sync
1355 self.currentLocalObjectStates = [NSMutableDictionary dictionary];
1356 for (ASIPithosContainer *pithosContainer in pithosContainers) {
1357 NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
1358 NSArray *containerExcludedDirectories = [containersDictionary objectForKey:pithosContainer.name];
1359 BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1360 NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
1361 NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath];
1362 for (NSString *objectName in dirEnumerator) {
1363 if (operation.isCancelled) {
1364 operation.completionBlock = nil;
1365 [self saveLocalState];
1366 [self syncOperationFinishedWithSuccess:NO];
1371 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1372 NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1374 BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1375 if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) {
1376 dispatch_async(dispatch_get_main_queue(), ^{
1377 [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error"
1378 message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.",
1379 containerDirectoryPath, pithosAccount.name]
1382 pithosAccount.syncActive = NO;
1384 } else if (fileExists) {
1385 NSArray *pathComponents = [objectName pathComponents];
1386 if ([pathComponents count] == 1) {
1387 if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) {
1388 // Skip excluded directory and its descendants, or root file with same name
1390 [dirEnumerator skipDescendants];
1391 // Remove stored state if any
1392 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1394 } else if (!isDirectory && containerExludeRootFiles) {
1395 // Skip excluded root file
1396 // Remove stored state if any
1397 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1401 // Include local object
1402 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
1403 if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1404 // New or modified existing local object, compute current state
1405 if (!storedLocalObjectState)
1406 // For new local object, also create empty stored state
1407 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1408 [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
1409 blockHash:pithosContainer.blockHash
1410 blockSize:pithosContainer.blockSize]
1413 // Local object hasn't changed, set stored state also to current
1414 [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1418 [self saveLocalState];
1421 if (operation.isCancelled) {
1422 operation.completionBlock = nil;
1423 [self syncOperationFinishedWithSuccess:NO];
1428 // Add an empty stored state for legal new remote objects since last sync
1429 for (ASIPithosContainer *pithosContainer in pithosContainers) {
1430 NSArray *containerExcludedDirectories = [containersDictionary objectForKey:pithosContainer.name];
1431 BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1432 NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
1433 NSMutableDictionary *containerRemoteObjects = [remoteObjects objectForKey:pithosContainer.name];
1434 for (NSString *objectName in containerRemoteObjects) {
1435 if (operation.isCancelled) {
1436 operation.completionBlock = nil;
1437 [self saveLocalState];
1438 [self syncOperationFinishedWithSuccess:NO];
1443 ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName];
1444 NSString *localObjectName;
1445 if ([object.name hasSuffix:@"/"])
1446 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1448 localObjectName = [NSString stringWithString:object.name];
1449 NSArray *pathComponents = [localObjectName pathComponents];
1450 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1451 // Skip excluded directory object and its descendants, or root file object with same name
1452 // Remove stored state if any
1453 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1455 } else if (containerExludeRootFiles &&
1456 ([pathComponents count] == 1) &&
1457 ![PithosUtilities isContentTypeDirectory:object.contentType]) {
1458 // Skip root file object
1459 // Remove stored state if any
1460 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1463 if (![containerStoredLocalObjectStates objectForKey:object.name])
1464 [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name];
1466 [self saveLocalState];
1469 if (operation.isCancelled) {
1470 operation.completionBlock = nil;
1471 [self syncOperationFinishedWithSuccess:NO];
1476 // For each stored state compare with current and remote state
1477 // Stored states of local objects that have been deleted,
1478 // haven't been checked for legality (only existing local remote objects)
1479 // These should be identified and skipped
1480 for (ASIPithosContainer *pithosContainer in pithosContainers) {
1481 NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
1482 NSArray *containerExcludedDirectories = [containersDictionary objectForKey:pithosContainer.name];
1483 BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1484 NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
1485 NSMutableDictionary *containerRemoteObjects = [remoteObjects objectForKey:pithosContainer.name];
1486 for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1487 if (operation.isCancelled) {
1488 operation.completionBlock = nil;
1489 [self syncOperationFinishedWithSuccess:NO];
1494 NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1495 if ([objectName hasSuffix:@"/"])
1496 filePath = [filePath stringByAppendingString:@":"];
1497 ASIPithosObject *object = [ASIPithosObject object];
1498 object.name = objectName;
1499 NSLog(@"Sync::object name: %@", object.name);
1501 PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
1502 PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1503 if (!currentLocalObjectState) {
1504 // The stored state corresponds to a remote or deleted local object, that's why there is no current state
1505 // In the latter case it must be checked for legality, which can be determined by its stored state
1506 // If it existed locally, but was deleted, state.exists is true,
1507 // else if the stored state is an empty state that was created due to the server object, state.exists is false
1508 if (storedLocalObjectState.exists) {
1509 NSString *localObjectName;
1510 if ([object.name hasSuffix:@"/"])
1511 localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1513 localObjectName = [NSString stringWithString:object.name];
1514 NSArray *pathComponents = [localObjectName pathComponents];
1515 if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1516 // Skip excluded directory object and its descendants, or root file object with same name
1517 // Remove stored state
1518 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1519 [self saveLocalState];
1521 } else if (containerExludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) {
1522 // Skip root file object
1523 // Remove stored state
1524 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1525 [self saveLocalState];
1529 // There is also the off case that a local object has been created in the meantime
1530 // This call works in any case, existent or non-existent local object
1531 currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
1532 blockHash:pithosContainer.blockHash
1533 blockSize:pithosContainer.blockSize];
1536 PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1537 ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
1539 if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1540 remoteObjectState.isDirectory = YES;
1542 remoteObjectState.hash = remoteObject.objectHash;
1546 BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1547 BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1548 NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1549 if (!localStateHasChanged) {
1550 // Local state hasn't changed
1551 if (serverStateHasChanged) {
1552 // Server state has changed
1553 // Update local state to match that of the server
1554 object.bytes = remoteObject.bytes;
1555 object.version = remoteObject.version;
1556 object.contentType = remoteObject.contentType;
1557 object.objectHash = remoteObject.objectHash;
1558 [self updateLocalStateWithObject:object localFilePath:filePath pithosContainer:pithosContainer];
1559 } else if (!remoteObject && ![currentLocalObjectState exists]) {
1560 // Server state hasn't changed
1561 // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1562 [containerStoredLocalObjectStates removeObjectForKey:objectName];
1563 [self saveLocalState];
1566 // Local state has changed
1567 if (!serverStateHasChanged) {
1568 // Server state hasn't changed
1569 if (currentLocalObjectState.isDirectory)
1570 object.contentType = @"application/directory";
1572 object.objectHash = currentLocalObjectState.hash;
1573 [self updateServerStateWithCurrentState:currentLocalObjectState
1575 localFilePath:filePath
1576 pithosContainer:pithosContainer];
1578 // Server state has also changed
1579 if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1580 // Both did the same change (directory)
1581 storedLocalObjectState.filePath = filePath;
1582 storedLocalObjectState.isDirectory = YES;
1583 [self saveLocalState];
1584 } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1585 // Both did the same change (object edit or delete)
1586 if (![remoteObjectState exists]) {
1587 [containerStoredLocalObjectStates removeObjectForKey:object.name];
1589 storedLocalObjectState.filePath = filePath;
1590 storedLocalObjectState.hash = remoteObjectState.hash;
1592 [self saveLocalState];
1594 // Conflict, we ask the user which change to keep
1595 NSString *informativeText;
1596 NSString *firstButtonText;
1597 NSString *secondButtonText;
1599 if (![remoteObjectState exists]) {
1600 // Remote object has been deleted
1601 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.",
1602 pithosContainer.name, object.name ];
1603 firstButtonText = @"Delete local file";
1604 secondButtonText = @"Upload file to server";
1605 } else if (![currentLocalObjectState exists]) {
1606 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.",
1607 pithosContainer.name, object.name];
1608 firstButtonText = @"Download file from server";
1609 secondButtonText = @"Delete file on server";
1611 informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.",
1612 pithosContainer.name, object.name];
1613 firstButtonText = @"Keep server version";
1614 secondButtonText = @"Keep local version";
1616 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1617 [alert setMessageText:@"Conflict"];
1618 [alert setInformativeText:informativeText];
1619 [alert addButtonWithTitle:firstButtonText];
1620 [alert addButtonWithTitle:secondButtonText];
1621 [alert addButtonWithTitle:@"Do nothing"];
1622 NSInteger choice = [alert runModal];
1623 if (choice == NSAlertFirstButtonReturn) {
1624 object.bytes = remoteObject.bytes;
1625 object.version = remoteObject.version;
1626 object.contentType = remoteObject.contentType;
1627 object.objectHash = remoteObject.objectHash;
1628 [self updateLocalStateWithObject:object localFilePath:filePath pithosContainer:pithosContainer];
1629 } if (choice == NSAlertSecondButtonReturn) {
1630 if (currentLocalObjectState.isDirectory)
1631 object.contentType = @"application/directory";
1633 object.objectHash = currentLocalObjectState.hash;
1634 [self updateServerStateWithCurrentState:currentLocalObjectState
1636 localFilePath:filePath
1637 pithosContainer:pithosContainer];
1644 [self syncOperationFinishedWithSuccess:YES];
1646 [self listRequestFailed:containerRequest];
1651 - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1652 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1653 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1654 if (operation.isCancelled) {
1660 if (containerRequest.isCancelled) {
1661 dispatch_async(dispatch_get_main_queue(), ^{
1662 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1663 withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1667 [self syncOperationFinishedWithSuccess:NO];
1671 // If the server listing fails, the sync should start over, so just retrying is enough
1672 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1674 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1675 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1676 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1678 dispatch_async(dispatch_get_main_queue(), ^{
1679 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1680 withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1684 // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1685 [self syncOperationFinishedWithSuccess:NO];
1690 - (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1691 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1692 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1693 NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1694 if (operation.isCancelled) {
1695 [self requestFailed:objectRequest];
1696 } else if (objectRequest.responseStatusCode == 206) {
1697 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1698 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1699 NSFileManager *fileManager = [NSFileManager defaultManager];
1701 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1703 NSString *downloadsDirPath = self.tempDownloadsDirPath;
1704 if (!downloadsDirPath) {
1705 dispatch_async(dispatch_get_main_queue(), ^{
1706 [activityFacility endActivity:activity
1707 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1709 [self syncOperationFinishedWithSuccess:NO];
1714 PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
1715 if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1716 NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1717 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1718 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1719 strcpy(tempFileNameCString, tempFileTemplateCString);
1720 int fileDescriptor = mkstemp(tempFileNameCString);
1721 NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1722 free(tempFileNameCString);
1723 if (fileDescriptor == -1) {
1724 dispatch_async(dispatch_get_main_queue(), ^{
1725 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
1726 message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]
1728 [activityFacility endActivity:activity
1729 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1731 [self syncOperationFinishedWithSuccess:NO];
1735 close(fileDescriptor);
1736 storedState.tmpFilePath = tempFilePath;
1737 [self saveLocalState];
1741 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1742 NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1743 [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
1744 [tempFileHandle writeData:[objectRequest responseData]];
1745 [tempFileHandle closeFile];
1747 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1748 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1749 if (missingBlockIndex == NSNotFound) {
1750 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1751 NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1752 if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath pithosContainer:pithosContainer]) {
1753 dispatch_async(dispatch_get_main_queue(), ^{
1754 [activityFacility endActivity:activity
1755 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1757 [self syncOperationFinishedWithSuccess:NO];
1760 } else if (![fileManager fileExistsAtPath:dirPath]) {
1761 // File doesn't exist but also the containing directory doesn't exist
1762 // In most cases this should have been resolved as an update of the corresponding local object,
1763 // but it never hurts to check
1765 [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1767 dispatch_async(dispatch_get_main_queue(), ^{
1768 [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
1769 message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
1771 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1772 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1774 [self syncOperationFinishedWithSuccess:NO];
1779 // Move file from tmp download
1781 [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1783 dispatch_async(dispatch_get_main_queue(), ^{
1784 [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
1785 message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath]
1787 [activityFacility endActivity:activity
1788 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1790 [self syncOperationFinishedWithSuccess:NO];
1794 dispatch_async(dispatch_get_main_queue(), ^{
1795 [activityFacility endActivity:activity
1796 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1797 totalBytes:activity.totalBytes
1798 currentBytes:activity.totalBytes];
1801 storedState.filePath = filePath;
1802 storedState.hash = object.objectHash;
1803 storedState.tmpFilePath = nil;
1804 [self saveLocalState];
1805 [self syncOperationFinishedWithSuccess:YES];
1809 if (newSyncRequested || syncLate || operation.isCancelled) {
1810 [self requestFailed:objectRequest];
1812 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1813 containerName:pithosContainer.name
1815 blockIndex:missingBlockIndex
1816 blockSize:pithosContainer.blockSize];
1817 newObjectRequest.delegate = self;
1818 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1819 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1820 newObjectRequest.userInfo = objectRequest.userInfo;
1821 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1822 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1823 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1824 [activityFacility updateActivity:activity
1825 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)",
1826 newObjectRequest.containerName, object.name,
1827 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1828 totalBytes:activity.totalBytes
1829 currentBytes:(activity.currentBytes + size)];
1831 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1834 } else if (objectRequest.responseStatusCode == 412) {
1835 // The object has changed on the server
1836 dispatch_async(dispatch_get_main_queue(), ^{
1837 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1838 withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1840 [self syncOperationFinishedWithSuccess:NO];
1842 [self requestFailed:objectRequest];
1847 - (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1848 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1849 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1850 NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1851 if (operation.isCancelled) {
1852 [self requestFailed:objectRequest];
1853 } else if (objectRequest.responseStatusCode == 200) {
1854 if (newSyncRequested || syncLate || operation.isCancelled) {
1855 [self requestFailed:objectRequest];
1857 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1858 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1859 PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
1860 if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1861 [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
1862 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1863 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath
1864 blockSize:pithosContainer.blockSize
1865 blockHash:pithosContainer.blockHash
1866 withHashes:[objectRequest hashes]];
1867 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1868 dispatch_async(dispatch_get_main_queue(), ^{
1869 [activityFacility updateActivity:activity
1870 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)",
1871 pithosContainer.name, object.name,
1872 (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))]
1873 totalBytes:activity.totalBytes
1874 currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
1877 __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
1878 containerName:pithosContainer.name
1880 blockIndex:missingBlockIndex
1881 blockSize:pithosContainer.blockSize];
1882 newObjectRequest.delegate = self;
1883 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1884 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1885 newObjectRequest.userInfo = objectRequest.userInfo;
1886 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1887 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1888 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1889 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1890 [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1891 [activityFacility updateActivity:activity
1892 withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)",
1893 newObjectRequest.containerName, object.name,
1894 (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1895 totalBytes:activity.totalBytes
1896 currentBytes:(activity.currentBytes + size)];
1898 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1901 [self requestFailed:objectRequest];
1906 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1907 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1908 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1909 NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1910 if (operation.isCancelled) {
1911 [self requestFailed:objectRequest];
1912 } else if (objectRequest.responseStatusCode == 201) {
1913 PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
1914 objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1915 storedState.isDirectory = YES;
1916 [self saveLocalState];
1917 dispatch_async(dispatch_get_main_queue(), ^{
1918 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1919 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1921 [self syncOperationFinishedWithSuccess:YES];
1923 [self requestFailed:objectRequest];
1928 - (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1929 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1930 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1931 NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1932 if (operation.isCancelled) {
1933 [self requestFailed:objectRequest];
1934 } else if (objectRequest.responseStatusCode == 201) {
1935 [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
1936 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1937 [self saveLocalState];
1938 dispatch_async(dispatch_get_main_queue(), ^{
1939 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1940 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1942 [self syncOperationFinishedWithSuccess:YES];
1944 [self requestFailed:objectRequest];
1949 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1950 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1951 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1952 NSLog(@"Sync::delete object finished: %@", objectRequest.url);
1953 if (operation.isCancelled) {
1954 [self requestFailed:objectRequest];
1955 } else if (objectRequest.responseStatusCode == 204) {
1956 [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
1957 removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1958 [self saveLocalState];
1959 dispatch_async(dispatch_get_main_queue(), ^{
1960 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1961 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1963 [self syncOperationFinishedWithSuccess:YES];
1965 [self requestFailed:objectRequest];
1970 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1971 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1972 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1973 NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1974 ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1975 ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1976 PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
1977 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1978 NSUInteger totalBytes = activity.totalBytes;
1979 NSUInteger currentBytes = activity.currentBytes;
1980 if (operation.isCancelled) {
1981 [self requestFailed:objectRequest];
1982 } else if (objectRequest.responseStatusCode == 201) {
1983 NSLog(@"Sync::object created: %@", objectRequest.url);
1984 storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1985 storedState.hash = object.objectHash;
1986 [self saveLocalState];
1987 dispatch_async(dispatch_get_main_queue(), ^{
1988 [activityFacility endActivity:activity
1989 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1990 totalBytes:totalBytes
1991 currentBytes:totalBytes];
1993 [self syncOperationFinishedWithSuccess:YES];
1994 } else if (objectRequest.responseStatusCode == 409) {
1995 if (newSyncRequested || syncLate || operation.isCancelled) {
1996 [self requestFailed:objectRequest];
1998 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1999 if (iteration == 0) {
2000 NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
2001 dispatch_async(dispatch_get_main_queue(), ^{
2002 [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
2004 [self syncOperationFinishedWithSuccess:NO];
2008 NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
2009 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
2010 withMissingHashes:[objectRequest hashes]];
2011 if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
2012 currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
2013 dispatch_async(dispatch_get_main_queue(), ^{
2014 [activityFacility updateActivity:activity
2015 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)",
2016 pithosContainer.name, object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
2017 totalBytes:totalBytes
2018 currentBytes:currentBytes];
2020 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2021 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2022 containerName:pithosContainer.name
2023 blockSize:pithosContainer.blockSize
2024 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
2025 missingBlockIndex:missingBlockIndex
2026 sharingAccount:nil];
2027 newContainerRequest.delegate = self;
2028 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2029 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2030 newContainerRequest.userInfo = objectRequest.userInfo;
2031 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
2032 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2033 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2034 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2035 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
2036 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2037 [activityFacility updateActivity:activity
2038 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)",
2039 newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2040 totalBytes:activity.totalBytes
2041 currentBytes:(activity.currentBytes + size)];
2043 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2046 [self requestFailed:objectRequest];
2051 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
2052 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2053 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
2054 NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
2055 if (operation.isCancelled) {
2056 [self requestFailed:containerRequest];
2057 } else if (containerRequest.responseStatusCode == 202) {
2058 ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
2059 ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
2060 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
2061 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
2062 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
2063 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
2064 if (operation.isCancelled) {
2065 [self requestFailed:containerRequest];
2066 } else if (missingBlockIndex == NSNotFound) {
2067 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
2068 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
2069 containerName:pithosContainer.name
2070 objectName:object.name
2071 contentType:object.contentType
2072 blockSize:pithosContainer.blockSize
2073 blockHash:pithosContainer.blockHash
2074 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2077 sharingAccount:nil];
2078 newObjectRequest.delegate = self;
2079 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2080 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2081 newObjectRequest.userInfo = containerRequest.userInfo;
2082 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2083 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
2084 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
2085 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
2086 [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2088 if (newSyncRequested || syncLate || operation.isCancelled) {
2089 [self requestFailed:containerRequest];
2091 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
2092 containerName:pithosContainer.name
2093 blockSize:pithosContainer.blockSize
2094 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
2095 missingBlockIndex:missingBlockIndex
2096 sharingAccount:nil];
2097 newContainerRequest.delegate = self;
2098 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2099 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2100 newContainerRequest.userInfo = containerRequest.userInfo;
2101 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2102 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2103 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2104 [activityFacility updateActivity:activity
2105 withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)",
2106 newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
2107 totalBytes:activity.totalBytes
2108 currentBytes:(activity.currentBytes + size)];
2110 [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2114 [self requestFailed:containerRequest];
2119 - (void)requestFailed:(ASIPithosRequest *)request {
2120 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2121 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
2122 NSLog(@"Sync::request failed: %@", request.url);
2123 if (operation.isCancelled) {
2127 if (request.isCancelled || newSyncRequested || syncLate) {
2128 dispatch_async(dispatch_get_main_queue(), ^{
2129 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2130 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
2132 [self syncOperationFinishedWithSuccess:NO];
2136 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
2138 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
2139 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
2140 [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
2142 dispatch_async(dispatch_get_main_queue(), ^{
2143 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
2144 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
2146 [self syncOperationFinishedWithSuccess:NO];