// PithosSyncDaemon.m
// pithos-macos
//
-// Copyright 2011 GRNET S.A. All rights reserved.
+// Copyright 2011-2012 GRNET S.A. All rights reserved.
//
// Redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the following
// or implied, of GRNET S.A.
#import "PithosSyncDaemon.h"
+#import "PithosAccount.h"
#import "PithosLocalObjectState.h"
#import "PithosActivityFacility.h"
#import "PithosUtilities.h"
#import "ASINetworkQueue.h"
#import "ASIPithosRequest.h"
+#import "ASIPithos.h"
+#import "ASIPithosContainer.h"
#import "ASIPithosContainerRequest.h"
#import "ASIPithosObjectRequest.h"
#import "ASIPithosObject.h"
@interface PithosSyncDaemon (Private)
-- (NSString *)pithosStateFilePath;
+- (void)loadLocalState;
+- (void)resetLocalStateWithAll:(BOOL)all;
- (void)saveLocalState;
-- (BOOL)moveToTempTrashFile:(NSString *)filePath;
+- (BOOL)createSyncDirectory:(NSString *)dirPath;
+- (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName;
+- (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName;
+
+- (BOOL)moveToTempTrashFile:(NSString *)filePath
+ accountName:(NSString *)accountName
+ pithosContainer:(ASIPithosContainer *)pithosContainer;
- (void)emptyTempTrash;
- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
-- (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath;
+- (void)updateLocalStateWithObject:(ASIPithosObject *)object
+ localFilePath:(NSString *)filePath
+ accountName:(NSString *)accountName
+ pithosContainer:(ASIPithosContainer *)pithosContainer;
- (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
object:(ASIPithosObject *)object
- localFilePath:(NSString *)filePath;
+ localFilePath:(NSString *)filePath
+ accountName:(NSString *)accountName
+ pithosContainer:(ASIPithosContainer *)pithosContainer;
+- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest;
- (void)requestFailed:(ASIPithosRequest *)request;
-- (void)increaseSyncOperationCount;
-- (void)decreaseSyncOperationCount;
+- (void)syncOperationStarted;
+- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull;
@end
@implementation PithosSyncDaemon
-@synthesize blockHash, blockSize, lastModified, lastCompletedSync, remoteObjects, storedLocalObjectStates, currentLocalObjectStates;
-@synthesize directoryPath, containerDirectoryPath, pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
+@synthesize directoryPath, accountsDictionary, skipHidden, pithos;
+@synthesize accountsNames, accountsPithosContainers;
+@synthesize lastCompletedSync, remoteObjects, previousRemoteObjects, storedLocalObjectStates, currentLocalObjectStates;
+@synthesize pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
#pragma mark -
#pragma Object Lifecycle
- (id)initWithDirectoryPath:(NSString *)aDirectoryPath
- containerName:(NSString *)aContainerName
- timeInterval:(NSTimeInterval)aTimeInterval
+ pithosAccount:(PithosAccount *)aPithosAccount
+ accountsDictionary:(NSDictionary *)anAccountsDictionary
+ skipHidden:(BOOL)aSkipHidden
resetLocalState:(BOOL)resetLocalState {
if ((self = [super init])) {
directoryPath = [aDirectoryPath copy];
- containerName = [aContainerName copy];
- timeInterval = aTimeInterval;
-
- syncOperationCount = 0;
- newSyncRequested = NO;
- containerDirectoryPath = [[directoryPath stringByAppendingPathComponent:containerName] copy];
+ pithosAccount = aPithosAccount;
+ self.accountsDictionary = anAccountsDictionary;
+ skipHidden = aSkipHidden;
+ self.pithos = pithosAccount.pithos;
activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
- NSFileManager *fileManager = [NSFileManager defaultManager];
- if (resetLocalState) {
- NSError *error = nil;
- if ([fileManager fileExistsAtPath:self.pithosStateFilePath] &&
- (![fileManager removeItemAtPath:self.pithosStateFilePath error:&error] || error))
- [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
- message:[NSString stringWithFormat:@"Cannot remove file at '%@'", self.pithosStateFilePath]
- error:error];
- if (self.tempDownloadsDirPath) {
- error = nil;
- for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
- if (error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
- message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath]
- error:error];
- break;
- }
- NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
- if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
- message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
- error:error];
- }
- error = nil;
- }
- }
- }
- if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath]) {
- NSData *data = [NSData dataWithContentsOfFile:self.pithosStateFilePath];
- NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
- self.storedLocalObjectStates = [unarchiver decodeObjectForKey:containerName];
- [unarchiver finishDecoding];
- } else {
- self.storedLocalObjectStates = [NSMutableDictionary dictionary];
- }
+ if (resetLocalState)
+ [self resetLocalStateWithAll:YES];
+ else
+ [self resetLocalStateWithAll:NO];
+
+ networkQueue = [[ASINetworkQueue alloc] init];
+ networkQueue.showAccurateProgress = YES;
+ networkQueue.shouldCancelAllRequestsOnFailure = NO;
+// networkQueue.maxConcurrentOperationCount = 1;
- queue = [[ASINetworkQueue alloc] init];
- queue.showAccurateProgress = YES;
- queue.shouldCancelAllRequestsOnFailure = NO;
- [queue go];
+ callbackQueue = [[NSOperationQueue alloc] init];
+ [callbackQueue setSuspended:YES];
+ callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
+// callbackQueue.maxConcurrentOperationCount = 1;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:NSApplicationWillTerminateNotification
object:[NSApplication sharedApplication]];
+ }
+ return self;
+}
+
+- (void)loadLocalState {
+ @autoreleasepool {
+ if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath])
+ self.storedLocalObjectStates = [NSKeyedUnarchiver unarchiveObjectWithFile:self.pithosStateFilePath];
+ else
+ self.storedLocalObjectStates = [NSMutableDictionary dictionary];
+ if (!storedLocalObjectStates)
+ self.storedLocalObjectStates = [NSMutableDictionary dictionary];
+ for (NSString *accountName in accountsNames) {
+ NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
+ if (!accountStoredLocalObjectStates) {
+ accountStoredLocalObjectStates = [NSMutableDictionary dictionary];
+ [storedLocalObjectStates setObject:accountStoredLocalObjectStates forKey:accountName];
+ }
+ for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
+ if (![accountStoredLocalObjectStates objectForKey:pithosContainer.name])
+ [accountStoredLocalObjectStates setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
+ }
+ }
+ }
+}
+
+- (void)resetLocalStateWithAll:(BOOL)all {
+ @autoreleasepool {
+ self.lastCompletedSync = nil;
+ if (all) {
+ self.storedLocalObjectStates = [NSMutableDictionary dictionary];
+ [self saveLocalState]; // Save an empty dictionary
+ [self loadLocalState]; // Load to populate with containers
+ [self saveLocalState]; // Save again
+ if (self.tempDownloadsDirPath)
+ [PithosUtilities removeContentsAtPath:self.tempDownloadsDirPath];
+ } else {
+ // Remove containers that don't interest us anymore and save
+ if (!storedLocalObjectStates)
+ [self loadLocalState];
+ for (NSString *accountName in storedLocalObjectStates) {
+ NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
+ NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
+ if (!containersDictionary) {
+ if (self.tempDownloadsDirPath) {
+ if ([accountName isEqualToString:@""]) {
+ for (NSString *containerName in accountStoredLocalObjectStates) {
+ [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]];
+ }
+ } else {
+ [PithosUtilities removeContentsAtPath:[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared with me"]
+ stringByAppendingPathComponent:accountName]];
+ }
+ }
+ [storedLocalObjectStates removeObjectForKey:accountName];
+ } else {
+ // Check the account's containers
+ for (NSString *containerName in accountStoredLocalObjectStates) {
+ if (![containersDictionary objectForKey:containerName]) {
+ if ([accountName isEqualToString:@""])
+ [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]];
+ else
+ [PithosUtilities removeContentsAtPath:[[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared with me"]
+ stringByAppendingPathComponent:accountName]
+ stringByAppendingPathComponent:containerName]];
+ [accountStoredLocalObjectStates removeObjectForKey:containerName];
+ }
+ }
+ }
+ }
+ [self saveLocalState];
+ }
+ }
+}
+
+- (void)saveLocalState {
+ @autoreleasepool {
+ [NSKeyedArchiver archiveRootObject:storedLocalObjectStates toFile:self.pithosStateFilePath];
+ }
+}
+
+- (void)resetDaemon {
+ @synchronized(self) {
+ if (!daemonActive)
+ return;
+ }
+
+ [networkQueue reset];
+ [callbackQueue cancelAllOperations];
+ [callbackQueue setSuspended:YES];
+ [self emptyTempTrash];
+
+ syncOperationCount = 0;
- timer = [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(sync) userInfo:nil repeats:YES] retain];
- [timer fire];
+ @synchronized(self) {
+ daemonActive = NO;
}
+}
+
+- (void)startDaemon {
+ @synchronized(self) {
+ if (daemonActive)
+ return;
+ }
+
+ // In the improbable case of leftover operations
+ [networkQueue reset];
+ [callbackQueue cancelAllOperations];
- return self;
+ syncOperationCount = 0;
+ newSyncRequested = NO;
+ syncIncomplete = NO;
+ syncLate = NO;
+
+ [self loadLocalState];
+
+ [networkQueue go];
+ [callbackQueue setSuspended:NO];
+
+ @synchronized(self) {
+ daemonActive = YES;
+ }
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
- [queue cancelAllOperations];
- [queue release];
- [timer invalidate];
- [timer release];
- [tempTrashDirPath release];
- [tempDownloadsDirPath release];
- [pithosStateFilePath release];
- [containerDirectoryPath release];
- [currentLocalObjectStates release];
- [storedLocalObjectStates release];
- [remoteObjects release];
- [objects release];
- [lastCompletedSync release];
- [lastModified release];
- [blockHash release];
- [containerName release];
- [directoryPath release];
- [super dealloc];
+ [self resetDaemon];
}
#pragma mark -
#pragma mark Properties
- (NSString *)pithosStateFilePath {
- if (!pithosStateFilePath)
- pithosStateFilePath = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"PithosLocalObjectStates.archive"] retain];
+ if (!pithosStateFilePath) {
+ pithosStateFilePath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
+ stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive",
+ pithosAccount.uniqueName]];
+ NSString *pithosStateFileDirPath = [pithosStateFilePath stringByDeletingLastPathComponent];
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ BOOL isDirectory;
+ BOOL fileExists = [fileManager fileExistsAtPath:pithosStateFileDirPath isDirectory:&isDirectory];
+ NSError *error = nil;
+ if (fileExists && !isDirectory)
+ [fileManager removeItemAtPath:pithosStateFileDirPath error:&error];
+ if (!error && !fileExists)
+ [fileManager createDirectoryAtPath:pithosStateFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
+ //if (error)
+ // pithosStateFilePath = nil;
+ // XXX create a dir using mktmps?
+ }
return [pithosStateFilePath copy];
}
- (NSString *)tempDownloadsDirPath {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- if (!tempDownloadsDirPath || ![fileManager fileExistsAtPath:tempDownloadsDirPath]) {
- // Get the path from user defaults
- NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
- tempDownloadsDirPath = [userDefaults stringForKey:@"PithosSyncTempDownloadsDirPath"];
- if (tempDownloadsDirPath) {
- // Check if the path exists
- BOOL isDirectory;
- BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
- NSError *error = nil;
- if (fileExists && !isDirectory)
- [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
- if (!error & !fileExists)
- [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
- if (error)
- tempDownloadsDirPath = nil;
- }
- if (!tempDownloadsDirPath) {
- NSString *tempDirTemplate = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Temp Downloads XXXXXX"];
- const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
- char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
- strcpy(tempDirNameCString, tempDirTemplateCString);
- tempDirNameCString = mkdtemp(tempDirNameCString);
- if (tempDirNameCString != NULL)
- tempDownloadsDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
- free(tempDirNameCString);
- }
- if (tempDownloadsDirPath)
- [userDefaults setObject:tempDownloadsDirPath forKey:@"PithosSyncTempDownloadsDirPath"];
- [tempDownloadsDirPath retain];
+ if (!tempDownloadsDirPath) {
+ tempDownloadsDirPath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
+ stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads",
+ pithosAccount.uniqueName]];
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ BOOL isDirectory;
+ BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
+ NSError *error = nil;
+ if (fileExists && !isDirectory)
+ [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
+ if (!error && !fileExists)
+ [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
+ //if (error)
+ // tempDownloadsDirPath = nil;
+ // XXX create a dir using mktmps?
}
- return [tempDownloadsDirPath copy];
+ return tempDownloadsDirPath;
}
- (NSString *)tempTrashDirPath {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- if (!tempTrashDirPath || ![fileManager fileExistsAtPath:tempTrashDirPath]) {
- // Get the path from user defaults
- NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
- tempTrashDirPath = [userDefaults stringForKey:@"PithosSyncTempTrashDirPath"];
- if (tempTrashDirPath) {
- // Check if the path exists
- BOOL isDirectory;
- BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
- NSError *error = nil;
- if (fileExists && !isDirectory)
- [fileManager removeItemAtPath:tempTrashDirPath error:&error];
- if (!error & !fileExists)
- [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
- if (error)
- tempTrashDirPath = nil;
+ if (!tempTrashDirPath) {
+ tempTrashDirPath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
+ stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash",
+ pithosAccount.uniqueName]];
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ BOOL isDirectory;
+ BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
+ NSError *error = nil;
+ if (fileExists && !isDirectory)
+ [fileManager removeItemAtPath:tempTrashDirPath error:&error];
+ if (!error && !fileExists)
+ [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
+ //if (error)
+ // tempTrashDirPath = nil;
+ // XXX create a dir using mktmps?
+ }
+ return tempTrashDirPath;
+}
+
+- (void)setDirectoryPath:(NSString *)aDirectoryPath {
+ if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
+ [self resetDaemon];
+ [self resetLocalStateWithAll:YES];
+ directoryPath = [aDirectoryPath copy];
+ }
+}
+
+- (void)setAccountsDictionary:(NSDictionary *)anAccountsDictionary {
+ if (anAccountsDictionary && ![anAccountsDictionary isEqualToDictionary:accountsDictionary]) {
+ BOOL reset = (accountsDictionary != nil);
+ if (reset)
+ [self resetDaemon];
+
+ accountsDictionary = [anAccountsDictionary copy];
+
+ accountsCount = [accountsDictionary count];
+ self.accountsNames = [NSMutableArray arrayWithCapacity:accountsCount];
+ self.accountsPithosContainers = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
+ NSDictionary *containersDictionary = [accountsDictionary objectForKey:@""];
+ if (containersDictionary && [containersDictionary count]) {
+ NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
+ for (NSString *containerName in containersDictionary) {
+ ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
+ pithosContainer.name = containerName;
+ [pithosContainers addObject:pithosContainer];
+ }
+ [accountsNames addObject:@""];
+ [accountsPithosContainers setObject:pithosContainers forKey:@""];
}
- if (!tempTrashDirPath) {
- NSString *tempDirTemplate = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Temp Trash XXXXXX"];
- const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
- char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
- strcpy(tempDirNameCString, tempDirTemplateCString);
- tempDirNameCString = mkdtemp(tempDirNameCString);
- if (tempDirNameCString != NULL)
- tempTrashDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
- free(tempDirNameCString);
+ for (NSString *accountName in accountsDictionary) {
+ NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
+ if (![accountsNames containsObject:accountName] && [containersDictionary count]) {
+ NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]];
+ for (NSString *containerName in containersDictionary) {
+ ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
+ pithosContainer.name = containerName;
+ [pithosContainers addObject:pithosContainer];
+ }
+ [accountsNames addObject:accountName];
+ [accountsPithosContainers setObject:pithosContainers forKey:accountName];
+ }
}
- if (tempTrashDirPath)
- [userDefaults setObject:tempTrashDirPath forKey:@"PithosSyncTempTrashDirPath"];
- [tempTrashDirPath retain];
+
+ if (reset)
+ [self resetLocalStateWithAll:NO];
+ }
+}
+
+- (void)setPithos:(ASIPithos *)aPithos {
+ if (!pithos) {
+ pithos = [ASIPithos pithos];
+ pithos.authUser = [aPithos.authUser copy];
+ pithos.authToken = [aPithos.authToken copy];
+ pithos.storageURLPrefix = [aPithos.storageURLPrefix copy];
+ pithos.authURL = [aPithos.authURL copy];
+ pithos.publicURLPrefix = [aPithos.publicURLPrefix copy];
+ pithos.userCatalogURL = [aPithos.userCatalogURL copy];
+ }
+ if (aPithos &&
+ (![aPithos.authUser isEqualToString:pithos.authUser] ||
+ ![aPithos.authToken isEqualToString:pithos.authToken] ||
+ ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
+ [self resetDaemon];
+ if (![aPithos.authUser isEqualToString:pithos.authUser] ||
+ ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
+ [self resetLocalStateWithAll:YES];
+ pithos.authUser = [aPithos.authUser copy];
+ pithos.authToken = [aPithos.authToken copy];
+ pithos.storageURLPrefix = [aPithos.storageURLPrefix copy];
+ pithos.authURL = [aPithos.authURL copy];
+ pithos.publicURLPrefix = [aPithos.publicURLPrefix copy];
+ pithos.userCatalogURL = [aPithos.userCatalogURL copy];
}
- return [tempTrashDirPath copy];
}
#pragma mark -
-#pragma mark Sync
+#pragma mark Helper Methods
-- (void)saveLocalState {
- NSMutableData *data = [NSMutableData data];
- NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
- [archiver encodeObject:storedLocalObjectStates forKey:containerName];
- [archiver finishEncoding];
- [data writeToFile:self.pithosStateFilePath atomically:YES];
+- (BOOL)createSyncDirectory:(NSString *)dirPath {
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ BOOL isDirectory;
+ NSError *error = nil;
+ if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory]) {
+ if (![fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
+ message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'",
+ dirPath]
+ error:error];
+ return NO;
+ }
+ } else if (!isDirectory) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
+ message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'",
+ dirPath]
+ error:nil];
+ return NO;
+ }
+ return YES;
+}
+
+- (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
+ if ([accountName isEqualToString:@""])
+ return [directoryPath stringByAppendingPathComponent:containerName];
+ else
+ return [[[directoryPath stringByAppendingPathComponent:@"shared with me"]
+ stringByAppendingPathComponent:accountName]
+ stringByAppendingPathComponent:containerName];
+}
+
+- (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
+ if ([accountName isEqualToString:@""])
+ return containerName;
+ else
+ return [[@"shared with me" stringByAppendingPathComponent:accountName] stringByAppendingPathComponent:containerName];
}
-- (void)increaseSyncOperationCount {
+#pragma mark -
+#pragma mark Sync
+
+- (void)syncOperationStarted {
@synchronized(self) {
syncOperationCount++;
}
}
-- (void)decreaseSyncOperationCount {
+- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
@synchronized(self) {
+ if (!operationSuccessfull)
+ syncIncomplete = YES;
+ if (syncOperationCount == 0) {
+ // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
+ DLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
+ return;
+ }
syncOperationCount--;
- if (!syncOperationCount) {
+ if (syncOperationCount == 0) {
if (!syncIncomplete) {
self.lastCompletedSync = [NSDate date];
- [activityFacility startAndEndActivityWithType:PithosActivityOther
- message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility startAndEndActivityWithType:PithosActivityOther
+ message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]
+ pithosAccount:pithosAccount];
+
+ });
}
[self emptyTempTrash];
+ if (newSyncRequested && daemonActive)
+ [self sync];
}
}
}
+- (BOOL)isSyncing {
+ @synchronized(self) {
+ return ((syncOperationCount > 0) && daemonActive);
+ }
+}
+
+- (void)syncLate {
+ @synchronized(self) {
+ if ([self isSyncing])
+ syncLate = YES;
+ }
+}
+
- (void)sync {
@synchronized(self) {
- if (syncOperationCount) {
+ if ([self isSyncing]) {
// If at least one operation is running return
newSyncRequested = YES;
return;
- } else {
+ } else if (daemonActive && accountsCount) {
// The first operation is the server listing
- syncOperationCount = 1;
+ [self syncOperationStarted];
newSyncRequested = NO;
syncIncomplete = NO;
+ syncLate = NO;
+ } else {
+ return;
}
}
- NSFileManager *fileManager = [NSFileManager defaultManager];
- BOOL isDirectory;
- NSError *error = nil;
- if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
- if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] ||
- error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
- message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", containerDirectoryPath]
- error:error];
- @synchronized(self) {
- syncOperationCount = 0;
+ if (![self createSyncDirectory:directoryPath]) {
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+ for (NSString *accountName in accountsNames) {
+ for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
+ if (![self createSyncDirectory:[self dirPathForAccount:accountName container:pithosContainer.name]]) {
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
}
- return;
- }
- } else if (!isDirectory) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
- message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", containerDirectoryPath]
- error:nil];
- @synchronized(self) {
- syncOperationCount = 0;
}
- return;
}
-
- ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName
- limit:0
- marker:nil
- prefix:nil
- delimiter:nil
- path:nil
- meta:nil
- shared:NO
- until:nil
- ifModifiedSince:lastModified];
+
+ self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:accountsCount];
+ for (NSString *accountName in accountsNames) {
+ [remoteObjects setObject:[NSMutableDictionary dictionary] forKey:accountName];
+ }
+ accountsIndex = 0;
+ containersIndex = 0;
+ NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
+ ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
+ ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ limit:0
+ marker:nil
+ prefix:nil
+ delimiter:nil
+ path:nil
+ meta:nil
+ shared:NO
+ until:nil
+ ifModifiedSince:pithosContainer.lastModified];
+ if (![accountName isEqualToString:@""])
+ [containerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
containerRequest.delegate = self;
- containerRequest.didFinishSelector = @selector(listRequestFinished:);
- containerRequest.didFailSelector = @selector(listRequestFailed:);
+ containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther
- message:@"Sync: Getting server listing"];
+ message:@"Sync: Getting server listing"
+ pithosAccount:pithosAccount];
containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
activity, @"activity",
@"Sync: Getting server listing (stopped)", @"stoppedActivityMessage",
@"Sync: Getting server listing (failed)", @"failedActivityMessage",
@"Sync: Getting server listing (finished)", @"finishedActivityMessage",
- [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
[NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector",
nil];
- [queue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh]];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
}
- (void)emptyTempTrash {
- NSString *trashDirPath = self.tempTrashDirPath;
- if (trashDirPath) {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSError *error = nil;
-// NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
- NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
- if (error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
- message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
- error:error];
- return;
- }
- if ([subPaths count]) {
-// NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
-// for (NSString *subPath in subPaths) {
-// [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
-// }
-// syncOperationCount = 1;
-// [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
-// if (error) {
-// [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
-// message:@"Cannot move files to Trash"
-// error:error];
-// }
-// syncOperationCount = 0;
-// }];
- for (NSString *subPath in subPaths) {
- NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
- error = nil;
- if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
- [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
- message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
- error:error];
+ @autoreleasepool {
+ NSString *trashDirPath = self.tempTrashDirPath;
+ if (trashDirPath) {
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSError *error = nil;
+// NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
+ NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
+ if (error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
+ message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath]
+ error:error];
+ return;
+ }
+ if ([subPaths count]) {
+// NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
+// for (NSString *subPath in subPaths) {
+// [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
+// }
+// [self syncOperationStarted];
+// [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
+// if (error) {
+// [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error"
+// message:@"Cannot move files to Trash"
+// error:error];
+// }
+// [self syncOperationFinishedWithSuccess:YES];
+// }];
+ for (NSString *subPath in subPaths) {
+ NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
+ error = nil;
+ if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
+ message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
+ error:error];
+ }
}
}
- }
+ }
}
-- (BOOL)moveToTempTrashFile:(NSString *)filePath {
- NSString *trashDirPath = self.tempTrashDirPath;
- if (!tempTrashDirPath)
- return NO;
- NSFileManager *fileManager = [NSFileManager defaultManager];
- BOOL isDirectory;
- BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
- NSError *error = nil;
- NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath
- withString:trashDirPath];
- NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
- if (fileExists && isDirectory) {
- NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
- if (error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
- message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
- error:error];
- return NO;
- }
- if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
- message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
- error:error];
+- (BOOL)moveToTempTrashFile:(NSString *)filePath
+ accountName:(NSString *)accountName
+ pithosContainer:(ASIPithosContainer *)pithosContainer {
+ @autoreleasepool {
+ if (!self.tempTrashDirPath)
return NO;
- }
- if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
- message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
- filePath, newFilePath]
- error:error];
- return NO;
- }
- PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
- if (currentState) {
- currentState.filePath = newFilePath;
- [currentLocalObjectStates setObject:currentState forKey:newFilePath];
- [currentLocalObjectStates removeObjectForKey:filePath];
- } else {
- [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
- blockHash:blockHash
- blockSize:blockSize]
- forKey:newFilePath];
- }
- for (NSString *subPath in subPaths) {
- NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
- NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath
- withString:trashDirPath];
- currentState = [currentLocalObjectStates objectForKey:subFilePath];
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ BOOL isDirectory;
+ BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
+ NSError *error = nil;
+ NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:directoryPath withString:self.tempTrashDirPath];
+ NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
+ if (fileExists && isDirectory) {
+ NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
+ if (error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
+ message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath]
+ error:error];
+ return NO;
+ }
+ if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
+ message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
+ error:error];
+ return NO;
+ }
+ if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
+ message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
+ filePath, newFilePath]
+ error:error];
+ return NO;
+ }
+ PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
if (currentState) {
- currentState.filePath = newSubFilePath;
- [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
- [currentLocalObjectStates removeObjectForKey:subFilePath];
+ currentState.filePath = newFilePath;
+ [currentLocalObjectStates setObject:currentState forKey:newFilePath];
+ [currentLocalObjectStates removeObjectForKey:filePath];
} else {
- [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
- blockHash:blockHash
- blockSize:blockSize]
- forKey:newSubFilePath];
- }
- }
- } else if (fileExists && !isDirectory) {
- if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
- message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
- error:error];
- return NO;
- }
- if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
- message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
- filePath, newFilePath]
- error:error];
- return NO;
- }
- PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
- if (currentState) {
- currentState.filePath = newFilePath;
- [currentLocalObjectStates setObject:currentState forKey:newFilePath];
- [currentLocalObjectStates removeObjectForKey:filePath];
- } else {
- [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
- blockHash:blockHash
- blockSize:blockSize]
- forKey:newFilePath];
+ [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
+ blockHash:pithosContainer.blockHash
+ blockSize:pithosContainer.blockSize]
+ forKey:newFilePath];
+ }
+ for (NSString *subPath in subPaths) {
+ NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
+ NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:directoryPath
+ withString:self.tempTrashDirPath];
+ currentState = [currentLocalObjectStates objectForKey:subFilePath];
+ if (currentState) {
+ currentState.filePath = newSubFilePath;
+ [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
+ [currentLocalObjectStates removeObjectForKey:subFilePath];
+ } else {
+ [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath
+ blockHash:pithosContainer.blockHash
+ blockSize:pithosContainer.blockSize]
+ forKey:newSubFilePath];
+ }
+ }
+ } else if (fileExists && !isDirectory) {
+ if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
+ message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
+ error:error];
+ return NO;
+ }
+ if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
+ message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
+ filePath, newFilePath]
+ error:error];
+ return NO;
+ }
+ PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
+ if (currentState) {
+ currentState.filePath = newFilePath;
+ [currentLocalObjectStates setObject:currentState forKey:newFilePath];
+ [currentLocalObjectStates removeObjectForKey:filePath];
+ } else {
+ [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath
+ blockHash:pithosContainer.blockHash
+ blockSize:pithosContainer.blockSize]
+ forKey:newFilePath];
+ }
}
}
return YES;
}
- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
- NSUInteger hashLength = [hash length];
- if ((hashLength != 32) && (hashLength != 64))
+ if ([hash length] != 64)
return NO;
- PithosLocalObjectState *localState;
- NSFileManager *fileManager = [NSFileManager defaultManager];
- BOOL isDirectory;
- NSError *error = nil;
- for (NSString *localFilePath in [currentLocalObjectStates allKeys]) {
- localState = [currentLocalObjectStates objectForKey:localFilePath];
- if (!localState.isDirectory && ([hash isEqualToString:localState.md5] || [hash isEqualToString:localState.hashMapHash]) &&
- [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
- if ([localFilePath hasPrefix:containerDirectoryPath]) {
- if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
- message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
- localFilePath, filePath]
- error:error];
- } else {
- return YES;
- }
- } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
- if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
- message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
- localFilePath, filePath]
- error:error];
- } else {
- localState.filePath = filePath;
- [currentLocalObjectStates setObject:localState forKey:filePath];
- [currentLocalObjectStates removeObjectForKey:localFilePath];
- return YES;
+ @autoreleasepool {
+ PithosLocalObjectState *localState;
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ BOOL isDirectory;
+ NSError *error = nil;
+ for (NSString *localFilePath in currentLocalObjectStates) {
+ localState = [currentLocalObjectStates objectForKey:localFilePath];
+ if (!localState.isDirectory && [hash isEqualToString:localState.hash] &&
+ [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
+ if ([localFilePath hasPrefix:directoryPath]) {
+ if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
+ message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
+ localFilePath, filePath]
+ error:error];
+ } else {
+ return YES;
+ }
+ } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
+ if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
+ message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
+ localFilePath, filePath]
+ error:error];
+ } else {
+ localState.filePath = filePath;
+ [currentLocalObjectStates setObject:localState forKey:filePath];
+ [currentLocalObjectStates removeObjectForKey:localFilePath];
+ return YES;
+ }
}
}
}
}
- (void)updateLocalStateWithObject:(ASIPithosObject *)object
- localFilePath:(NSString *)filePath {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSError *error;
- BOOL isDirectory;
- BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
- PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
- if (!object || !object.hash) {
- // Delete local object
- NSLog(@"Sync::delete local object: %@", filePath);
- if (!fileExists || [self moveToTempTrashFile:filePath]) {
- [activityFacility startAndEndActivityWithType:PithosActivityOther
- message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]];
- [storedLocalObjectStates removeObjectForKey:object.name];
- [self saveLocalState];
- }
- } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
- // Create local directory object
- NSLog(@"Sync::create local directory object: %@", filePath);
- BOOL directoryCreated = NO;
- if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath])) {
- NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
- error = nil;
- if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
- message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
- error:error];
- } else {
- directoryCreated = YES;
- storedState.isDirectory = YES;
- [self saveLocalState];
+ localFilePath:(NSString *)filePath
+ accountName:(NSString *)accountName
+ pithosContainer:(ASIPithosContainer *)pithosContainer {
+ @autoreleasepool {
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSError *error;
+ BOOL isDirectory;
+ BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
+ NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
+ NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
+ objectForKey:pithosContainer.name];
+ PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
+ // Remote updated info
+ NSError *remoteError;
+ BOOL remoteIsDirectory;
+ BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos
+ containerName:pithosContainer.name
+ objectName:object.name
+ error:&remoteError
+ isDirectory:&remoteIsDirectory
+ sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
+ if (!object || !object.objectHash) {
+ // Delete local object
+ if (![accountName isEqualToString:@""])
+ // If "shared with me" skip
+ return;
+ if (remoteObjectExists) {
+ // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
+ syncIncomplete = YES;
}
- } else {
- NSLog(@"Sync::local directory object exists: %@", filePath);
- directoryCreated = YES;
- }
- if (directoryCreated)
- [activityFacility startAndEndActivityWithType:PithosActivityOther
- message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name]];
- } else if (object.bytes == 0) {
- // Create local object with zero length
- NSLog(@"Sync::create local zero length object: %@", filePath);
- BOOL fileCreated = NO;
- if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath])) {
- NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
- error = nil;
- if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
- message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
- error:error];
- } else {
- fileCreated = YES;
- storedState.hash = object.hash;
- storedState.filePath = nil;
+ DLog(@"Sync::delete local object: %@", filePath);
+ if (!fileExists || [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility startAndEndActivityWithType:PithosActivityOther
+ message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)",
+ pithosContainer.name, object.name]
+ pithosAccount:pithosAccount];
+ });
+ [containerStoredLocalObjectStates removeObjectForKey:object.name];
[self saveLocalState];
}
- } else {
- NSLog(@"Sync::local zero length object exists: %@", filePath);
- fileCreated = YES;
- }
- if (fileCreated)
- [activityFacility startAndEndActivityWithType:PithosActivityOther
- message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
- } else if (storedState.filePath == nil) {
- // Create new local object
- // Check first if a local copy exists
- if ([self findLocalCopyForObjectWithHash:object.hash forFile:filePath]) {
- [activityFacility startAndEndActivityWithType:PithosActivityOther
- message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
- } else {
- [self increaseSyncOperationCount];
- __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
- object:object
- blockIndex:0
- blockSize:blockSize];
- objectRequest.delegate = self;
- objectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
- objectRequest.didFailSelector = @selector(requestFailed:);
- PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
- message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name]
- totalBytes:object.bytes
- currentBytes:0];
- objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- object, @"pithosObject",
- [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(blockSize + 0.0)))], @"missingBlocks",
- [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
- filePath, @"filePath",
- activity, @"activity",
- [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage",
- [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage",
- [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage",
- [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
- [NSNumber numberWithUnsignedInteger:10], @"retries",
- nil];
- [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
- [activityFacility updateActivity:activity
- withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
- [[objectRequest.userInfo valueForKey:@"pithosObject"] name],
- (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
- totalBytes:activity.totalBytes
- currentBytes:(activity.currentBytes + size)];
- }];
- [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
- }
- } else {
- // Resume local object download
- // Check first if a local copy exists
- if ([self findLocalCopyForObjectWithHash:object.hash forFile:filePath]) {
- [activityFacility startAndEndActivityWithType:PithosActivityOther
- message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
- // Delete incomplete temp download
+ } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
+ // Create local directory object
+ if (!remoteObjectExists || !remoteIsDirectory) {
+ // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
+ syncIncomplete = YES;
+ return;
+ }
+ DLog(@"Sync::create local directory object: %@", filePath);
+ BOOL directoryCreated = NO;
+ if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
+ DLog(@"Sync::local directory object doesn't exist: %@", filePath);
+ error = nil;
+ if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
+ message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath]
+ error:error];
+ } else {
+ directoryCreated = YES;
+ storedState.filePath = filePath;
+ storedState.isDirectory = YES;
+ [self saveLocalState];
+ }
+ } else {
+ DLog(@"Sync::local directory object exists: %@", filePath);
+ directoryCreated = YES;
+ }
+ if (directoryCreated)
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility startAndEndActivityWithType:PithosActivityOther
+ message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name],
+ object.name]
+ pithosAccount:pithosAccount];
+ });
+ } else if (object.bytes == 0) {
+ // Create local object with zero length
+ if (!remoteObjectExists || remoteIsDirectory) {
+ // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
+ syncIncomplete = YES;
+ return;
+ }
+ DLog(@"Sync::create local zero length object: %@", filePath);
+ BOOL fileCreated = NO;
+ if (!fileExists ||
+ ((isDirectory || [PithosUtilities bytesOfFile:filePath]) &&
+ [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) {
+ DLog(@"Sync::local zero length object doesn't exist: %@", filePath);
+ // Create directory of the file, if it doesn't exist
+ error = nil;
+ if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
+ message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
+ error:error];
+ });
+ }
+ error = nil;
+ if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
+ message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
+ error:error];
+ } else {
+ fileCreated = YES;
+ storedState.filePath = filePath;
+ storedState.hash = object.objectHash;
+ storedState.tmpFilePath = nil;
+ [self saveLocalState];
+ }
+ } else {
+ DLog(@"Sync::local zero length object exists: %@", filePath);
+ fileCreated = YES;
+ }
+ if (fileCreated)
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility startAndEndActivityWithType:PithosActivityOther
+ message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name],
+ object.name]
+ pithosAccount:pithosAccount];
+ });
+ } else if (storedState.tmpFilePath == nil) {
+ // Create new local object
+ if (!remoteObjectExists || remoteIsDirectory) {
+ // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
+ syncIncomplete = YES;
+ return;
+ }
+ // Create directory of the file, if it doesn't exist
error = nil;
- if (![fileManager removeItemAtPath:storedState.filePath error:&error] || error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
- message:[NSString stringWithFormat:@"Cannot remove file at '%@'", storedState.filePath]
+ if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
+ message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
error:error];
}
+ // Check first if a local copy exists
+ if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
+ storedState.filePath = filePath;
+ storedState.hash = object.objectHash;
+ [self saveLocalState];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility startAndEndActivityWithType:PithosActivityOther
+ message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name],
+ object.name]
+ pithosAccount:pithosAccount];
+ });
+ } else {
+ [self syncOperationStarted];
+ __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ object:object
+ blockIndex:0
+ blockSize:pithosContainer.blockSize];
+ if (![accountName isEqualToString:@""])
+ [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
+ message:[messagePrefix stringByAppendingString:@" (0%%)"]
+ totalBytes:object.bytes
+ currentBytes:0
+ pithosAccount:pithosAccount];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
+ objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ accountName, @"accountName",
+ pithosContainer, @"pithosContainer",
+ object, @"pithosObject",
+ messagePrefix, @"messagePrefix",
+ [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks",
+ [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
+ filePath, @"filePath",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ nil];
+ [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
+ [activityFacility updateActivity:activity
+ withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
+ messagePrefix, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
+ totalBytes:activity.totalBytes
+ currentBytes:(activity.currentBytes + size)];
+ }];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
+ }
} else {
- [self increaseSyncOperationCount];
- ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithContainerName:containerName
- objectName:object.name];
- objectRequest.delegate = self;
- objectRequest.didFinishSelector = @selector(downloadObjectHashMapFinished:);
- // The fail method for block download does exactly what we want
- objectRequest.didFailSelector = @selector(requestFailed:);
- PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
- message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name]
- totalBytes:object.bytes
- currentBytes:0];
- objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- object, @"pithosObject",
- filePath, @"filePath",
- activity, @"activity",
- [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage",
- [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage",
- [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage",
- [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
- [NSNumber numberWithUnsignedInteger:10], @"retries",
- nil];
- [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
+ // Resume local object download
+ if (!remoteObjectExists || remoteIsDirectory) {
+ // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
+ syncIncomplete = YES;
+ return;
+ }
+ // Create directory of the file, if it doesn't exist
+ error = nil;
+ if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
+ message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath]
+ error:error];
+ }
+ // Check first if a local copy exists
+ if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
+ storedState.filePath = filePath;
+ storedState.hash = object.objectHash;
+ // Delete incomplete temp download
+ storedState.tmpFilePath = nil;
+ [self saveLocalState];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility startAndEndActivityWithType:PithosActivityOther
+ message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name],
+ object.name]
+ pithosAccount:pithosAccount];
+ });
+ } else {
+ [self syncOperationStarted];
+ ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ objectName:object.name];
+ if (![accountName isEqualToString:@""])
+ [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
+ message:[messagePrefix stringByAppendingString:@" (0%%)"]
+ totalBytes:object.bytes
+ currentBytes:0
+ pithosAccount:pithosAccount];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
+ objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ accountName, @"accountName",
+ pithosContainer, @"pithosContainer",
+ object, @"pithosObject",
+ messagePrefix, @"messagePrefix",
+ filePath, @"filePath",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ nil];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
+ }
}
}
}
-(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
object:(ASIPithosObject *)object
- localFilePath:(NSString *)filePath {
- [self increaseSyncOperationCount];
- if (currentState.isDirectory) {
- // Create remote directory object
- ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName
- objectName:object.name
- eTag:nil
- contentType:@"application/directory"
- contentEncoding:nil
- contentDisposition:nil
- manifest:nil
- sharing:nil
- isPublic:ASIPithosObjectRequestPublicIgnore
- metadata:nil
- data:[NSData data]];
- objectRequest.delegate = self;
- objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
- objectRequest.didFailSelector = @selector(requestFailed:);
- PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
- message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]];
- objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- object, @"pithosObject",
- activity, @"activity",
- [NSString stringWithFormat:@"Sync: Creating directory '%@' (stopped)", object.name], @"stoppedActivityMessage",
- [NSString stringWithFormat:@"Sync: Creating directory '%@' (failed)", object.name], @"failedActivityMessage",
- [NSString stringWithFormat:@"Sync: Creating directory '%@' (finished)", object.name], @"finishedActivityMessage",
- [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
- [NSNumber numberWithUnsignedInteger:10], @"retries",
- nil];
- [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
- } else if (!currentState.exists) {
- // Delete remote object
- NSString *safeObjectName = [PithosUtilities safeObjectNameForContainerName:@"trash"
- objectName:object.name];
- if (safeObjectName) {
- ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:containerName
- objectName:object.name
- destinationContainerName:@"trash"
- destinationObjectName:safeObjectName
- checkIfExists:NO];
- if (objectRequest) {
+ localFilePath:(NSString *)filePath
+ accountName:(NSString *)accountName
+ pithosContainer:(ASIPithosContainer *)pithosContainer {
+ @autoreleasepool {
+ [self syncOperationStarted];
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ BOOL isDirectory;
+ BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
+ if (currentState.isDirectory) {
+ // Create remote directory object
+ if (![accountName isEqualToString:@""]) {
+ if (!object.allowedTo) {
+ NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
+ NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
+ while ([objectAncestorName length] && !object.allowedTo) {
+ object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
+ objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
+ }
+ }
+ if (![object.allowedTo isEqualToString:@"write"]) {
+ // If read-only "shared with me" skip
+ [self syncOperationFinishedWithSuccess:YES];
+ return;
+ }
+ }
+ if (!fileExists || !isDirectory) {
+ // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+ ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ objectName:object.name
+ eTag:nil
+ contentType:@"application/directory"
+ contentEncoding:nil
+ contentDisposition:nil
+ manifest:nil
+ sharing:nil
+ isPublic:ASIPithosObjectRequestPublicIgnore
+ metadata:nil
+ data:[NSData data]];
+ if (![accountName isEqualToString:@""])
+ [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Creating directory '%@/%@'",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
+ message:messagePrefix
+ pithosAccount:pithosAccount];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
+ objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ accountName, @"accountName",
+ pithosContainer, @"pithosContainer",
+ object, @"pithosObject",
+ messagePrefix, @"messagePrefix",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ nil];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ } else if (![currentState exists]) {
+ // Delete remote object
+ if (![accountName isEqualToString:@""]) {
+ // If "shared with me" skip
+ [self syncOperationFinishedWithSuccess:YES];
+ return;
+ }
+ if (fileExists) {
+ // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
+ syncIncomplete = YES;
+ }
+ if ([pithosContainer.name isEqualToString:@"trash"]) {
+ // Delete
+ ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ objectName:object.name];
objectRequest.delegate = self;
- objectRequest.didFinishSelector = @selector(moveObjectToTrashFinished:);
- objectRequest.didFailSelector = @selector(requestFailed:);
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Deleting '%@/%@'",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
- message:[NSString stringWithFormat:@"Sync: Moving to trash '%@'", object.name]];
+ message:messagePrefix
+ pithosAccount:pithosAccount];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ accountName, @"accountName",
+ pithosContainer, @"pithosContainer",
object, @"pithosObject",
+ messagePrefix, @"messagePrefix",
activity, @"activity",
- [NSString stringWithFormat:@"Sync: Moving to trash '%@' (stopped)", object.name], @"stoppedActivityMessage",
- [NSString stringWithFormat:@"Sync: Moving to trash '%@' (failed)", object.name], @"failedActivityMessage",
- [NSString stringWithFormat:@"Sync: Moving to trash '%@' (finished)", object.name], @"finishedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
[NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
[NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
nil];
- [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
} else {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- }
- } else {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- }
- } else {
- // Upload file to remote object
- NSError *error = nil;
- object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
- if (object.contentType == nil)
- object.contentType = @"application/octet-stream";
- if (error)
- NSLog(@"contentType detection error: %@", error);
- NSArray *hashes = nil;
- ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName
+ // Move to container trash
+ NSString *safeName;
+ if ([PithosUtilities isContentTypeDirectory:object.contentType])
+ safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
+ else
+ safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
+ if (safeName) {
+ ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
+ containerName:pithosContainer.name
objectName:object.name
- contentType:object.contentType
- blockSize:blockSize
- blockHash:blockHash
- forFile:filePath
- checkIfExists:NO
- hashes:&hashes
- sharingAccount:nil];
- if (objectRequest) {
- objectRequest.delegate = self;
- objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
- objectRequest.didFailSelector = @selector(requestFailed:);
- PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
- message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
- totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
- currentBytes:0];
- [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
- [NSDictionary dictionaryWithObjectsAndKeys:
- object, @"pithosObject",
- filePath, @"filePath",
- hashes, @"hashes",
- [NSNumber numberWithUnsignedInteger:10], @"iteration",
- activity, @"activity",
- [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage",
- [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage",
- [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage",
- [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
- [NSNumber numberWithUnsignedInteger:10], @"retries",
- nil]];
- [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
+ destinationContainerName:@"trash"
+ destinationObjectName:safeName
+ checkIfExists:NO];
+ if (objectRequest) {
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
+ message:messagePrefix
+ pithosAccount:pithosAccount];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
+ objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ accountName, @"accountName",
+ pithosContainer, @"pithosContainer",
+ object, @"pithosObject",
+ messagePrefix, @"messagePrefix",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ nil];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ } else {
+ [self syncOperationFinishedWithSuccess:NO];
+ }
+ } else {
+ [self syncOperationFinishedWithSuccess:NO];
+ }
+ }
} else {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
+ // Upload file to remote object
+ if (![accountName isEqualToString:@""]) {
+ if (!object.allowedTo) {
+ NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
+ NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent];
+ while ([objectAncestorName length] && !object.allowedTo) {
+ object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo];
+ objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent];
+ }
+ }
+ if (![object.allowedTo isEqualToString:@"write"]) {
+ // If read-only "shared with me" skip
+ [self syncOperationFinishedWithSuccess:YES];
+ return;
+ }
+ }
+ if (!fileExists || isDirectory) {
+ // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+ NSError *error = nil;
+ object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
+ if (object.contentType == nil)
+ object.contentType = @"application/octet-stream";
+ #if DEBUG_PITHOS
+ if (error)
+ DLog(@"contentType detection error: %@", error);
+ #endif
+ NSArray *hashes = nil;
+ ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ objectName:object.name
+ contentType:object.contentType
+ blockSize:pithosContainer.blockSize
+ blockHash:pithosContainer.blockHash
+ forFile:filePath
+ checkIfExists:NO
+ hashes:&hashes
+ sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
+ if (objectRequest) {
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Uploading '%@/%@'",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
+ message:[messagePrefix stringByAppendingString:@" (0%%)"]
+ totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
+ currentBytes:0
+ pithosAccount:pithosAccount];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
+ [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ accountName, @"accountName",
+ pithosContainer, @"pithosContainer",
+ object, @"pithosObject",
+ messagePrefix, @"messagePrefix",
+ filePath, @"filePath",
+ hashes, @"hashes",
+ [NSNumber numberWithUnsignedInteger:10], @"iteration",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ nil]];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
+ } else {
+ [self syncOperationFinishedWithSuccess:NO];
+ }
}
}
-
}
#pragma mark -
#pragma mark ASIHTTPRequestDelegate
-- (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
- NSLog(@"Sync::list request finished: %@", containerRequest.url);
- if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
- if (containerRequest.responseStatusCode == 200) {
- NSArray *someObjects = [containerRequest objects];
- if (objects == nil) {
- objects = [[NSMutableArray alloc] initWithArray:someObjects];
- } else {
- [objects addObjectsFromArray:someObjects];
+- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
+ // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
+ NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
+ selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
+ object:request];
+ operation.completionBlock = ^{
+ @autoreleasepool {
+ if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
}
- if ([someObjects count] < 10000) {
- self.blockHash = [containerRequest blockHash];
- self.blockSize = [containerRequest blockSize];
- self.lastModified = [containerRequest lastModified];
- self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
- for (ASIPithosObject *object in objects) {
- [remoteObjects setObject:object forKey:object.name];
+ }
+ };
+ [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
+ [callbackQueue addOperation:operation];
+}
+
+- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
+ if (request.isCancelled) {
+ // Request has been cancelled
+ // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
+ [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
+ withObject:request];
+ } else {
+ // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
+ NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
+ selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
+ object:request];
+ operation.completionBlock = ^{
+ @autoreleasepool {
+ if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
}
- [objects release];
- objects = nil;
- } else {
- // Do an additional request to fetch more objects
- ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName
- limit:0
- marker:[[someObjects lastObject] name]
- prefix:nil
- delimiter:nil
- path:nil
- meta:nil
- shared:NO
- until:nil
- ifModifiedSince:lastModified];
+ }
+ };
+ [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
+ [callbackQueue addOperation:operation];
+ }
+}
+
+- (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
+ @autoreleasepool {
+ NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Sync::list request finished: %@", containerRequest.url);
+ if (operation.isCancelled) {
+ [self listRequestFailed:containerRequest];
+ } else if ((containerRequest.responseStatusCode == 200) ||
+ (containerRequest.responseStatusCode == 304) ||
+ (containerRequest.responseStatusCode == 403)) {
+ NSString *accountName = [accountsNames objectAtIndex:accountsIndex];
+ ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
+ if (containerRequest.responseStatusCode == 200) {
+ NSArray *someObjects = [containerRequest objects];
+ if (objects == nil) {
+ objects = [[NSMutableArray alloc] initWithArray:someObjects];
+ } else {
+ [objects addObjectsFromArray:someObjects];
+ }
+ if ([someObjects count] < 10000) {
+ pithosContainer.blockHash = [containerRequest blockHash];
+ pithosContainer.blockSize = [containerRequest blockSize];
+ pithosContainer.lastModified = [containerRequest lastModified];
+ NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
+ for (ASIPithosObject *object in objects) {
+ [containerRemoteObjects setObject:object forKey:object.name];
+ }
+ [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
+ objects = nil;
+ } else {
+ // Do an additional request to fetch more objects
+ ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ limit:0
+ marker:[[someObjects lastObject] name]
+ prefix:nil
+ delimiter:nil
+ path:nil
+ meta:nil
+ shared:NO
+ until:nil
+ ifModifiedSince:pithosContainer.lastModified];
+ if (![accountName isEqualToString:@""])
+ [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
+ newContainerRequest.delegate = self;
+ newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ newContainerRequest.userInfo = containerRequest.userInfo;
+ [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ return;
+ }
+ } else if (containerRequest.responseStatusCode == 304) {
+ NSMutableDictionary *containerRemoteObjects = [[previousRemoteObjects objectForKey:accountName]
+ objectForKey:pithosContainer.name];
+ if (containerRemoteObjects)
+ [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name];
+ } else if (containerRequest.responseStatusCode == 403) {
+ [[remoteObjects objectForKey:accountName] setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
+ }
+
+ containersIndex++;
+ if (containersIndex == [[accountsPithosContainers objectForKey:accountName] count]) {
+ accountsIndex++;
+ containersIndex = 0;
+ }
+ if (accountsIndex < accountsCount) {
+ accountName = [accountsNames objectAtIndex:accountsIndex];
+ pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex];
+ // Do a request for the next container
+ ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ limit:0
+ marker:nil
+ prefix:nil
+ delimiter:nil
+ path:nil
+ meta:nil
+ shared:NO
+ until:nil
+ ifModifiedSince:pithosContainer.lastModified];
+ if (![accountName isEqualToString:@""])
+ [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
newContainerRequest.delegate = self;
- newContainerRequest.didFinishSelector = @selector(listRequestFinished:);
- newContainerRequest.didFailSelector = @selector(listRequestFailed:);
- newContainerRequest.userInfo = newContainerRequest.userInfo;
+ newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ newContainerRequest.userInfo = containerRequest.userInfo;
[(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
- [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
return;
}
- }
- [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
- withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSError *error = nil;
- NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
- if (error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
- message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
- error:error];
- [activityFacility startAndEndActivityWithType:PithosActivityOther message:@"Sync: Failed to read contents of sync directory"];
- @synchronized(self) {
- // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccesfully
- syncOperationCount = 0;
- if (newSyncRequested)
- [self sync];
- }
- return;
- }
- self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:[subPaths count]];
- for (NSString *objectName in subPaths) {
- if (![storedLocalObjectStates objectForKey:objectName]) {
- [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
+ self.previousRemoteObjects = remoteObjects;
+ // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
+
+ if (operation.isCancelled) {
+ [self listRequestFailed:containerRequest];
+ return;
}
- NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
- [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
- blockHash:blockHash
- blockSize:blockSize]
- forKey:filePath];
- }
- [self saveLocalState];
- for (NSString *objectName in remoteObjects) {
- if (![storedLocalObjectStates objectForKey:objectName])
- [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
- }
-
- for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
- NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
- if ([objectName hasSuffix:@"/"])
- filePath = [filePath stringByAppendingString:@":"];
- ASIPithosObject *object = [ASIPithosObject object];
- object.name = objectName;
- NSLog(@"Sync::object name: %@", objectName);
-
- PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
- PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
- if (!currentLocalObjectState)
- currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
- blockHash:blockHash
- blockSize:blockSize];
- if (currentLocalObjectState.isDirectory)
- object.contentType = @"application/directory";
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
+ withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+ });
+ NSFileManager *fileManager = [NSFileManager defaultManager];
- PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
- ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
- if (remoteObject) {
- if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
- remoteObjectState.isDirectory = YES;
- object.contentType = @"application/directory";
- } else {
- remoteObjectState.hash = remoteObject.hash;
+ // Compute current state of legal existing local objects
+ // and add an empty stored state for legal new local objects since last sync
+ self.currentLocalObjectStates = [NSMutableDictionary dictionary];
+ for (NSString *accountName in accountsNames) {
+ for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
+ NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
+ NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
+ BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
+ NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
+ objectForKey:pithosContainer.name];
+ NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath];
+ for (__strong NSString *objectName in dirEnumerator) {
+ objectName = [objectName precomposedStringWithCanonicalMapping];
+ if (operation.isCancelled) {
+ operation.completionBlock = nil;
+ [self saveLocalState];
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+
+ NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
+ NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
+ BOOL isDirectory;
+ BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
+ if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error"
+ message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.",
+ containerDirectoryPath, pithosAccount.name]
+ error:nil];
+ pithosAccount.syncActive = NO;
+ return;
+ } else if (fileExists) {
+ if (skipHidden && [[objectName lastPathComponent] hasPrefix:@"."]) {
+ // Skip hidden directory and its descendants, or hidden file
+ if (isDirectory)
+ [dirEnumerator skipDescendants];
+ // Remove stored state if any
+ [containerStoredLocalObjectStates removeObjectForKey:objectName];
+ continue;
+ } else if ([[objectName pathComponents] count] == 1) {
+ if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) {
+ // Skip excluded directory and its descendants, or root file with same name
+ if (isDirectory)
+ [dirEnumerator skipDescendants];
+ // Remove stored state if any
+ [containerStoredLocalObjectStates removeObjectForKey:objectName];
+ continue;
+ } else if (!isDirectory && containerExcludeRootFiles) {
+ // Skip excluded root file
+ // Remove stored state if any
+ [containerStoredLocalObjectStates removeObjectForKey:objectName];
+ continue;
+ }
+ }
+ // Include local object
+ PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
+ if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
+ // New or modified existing local object, compute current state
+ if (!storedLocalObjectState)
+ // For new local object, also create empty stored state
+ [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
+ [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
+ blockHash:pithosContainer.blockHash
+ blockSize:pithosContainer.blockSize]
+ forKey:filePath];
+ } else {
+ // Local object hasn't changed, set stored state also to current
+ [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
+ }
+ }
+ }
+ [self saveLocalState];
}
}
+
+ if (operation.isCancelled) {
+ operation.completionBlock = nil;
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
- BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
- BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
- NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
- if (!localStateHasChanged) {
- // Local state hasn't changed
- if (serverStateHasChanged) {
- // Server state has changed
- // Update local state to match that of the server
- object.bytes = remoteObject.bytes;
- object.version = remoteObject.version;
- object.contentType = remoteObject.contentType;
- object.hash = remoteObject.hash;
- [self updateLocalStateWithObject:object localFilePath:filePath];
- } else if (!remoteObject && !currentLocalObjectState.exists) {
- // Server state hasn't changed
- // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
- [storedLocalObjectStates removeObjectForKey:objectName];
+ // Add an empty stored state for legal new remote objects since last sync
+ for (NSString *accountName in accountsNames) {
+ for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
+ NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
+ BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
+ NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
+ objectForKey:pithosContainer.name];
+ NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
+ for (NSString *objectName in containerRemoteObjects) {
+ if (operation.isCancelled) {
+ operation.completionBlock = nil;
+ [self saveLocalState];
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+
+ ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName];
+ NSString *localObjectName;
+ if ([object.name hasSuffix:@"/"])
+ localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
+ else
+ localObjectName = [NSString stringWithString:object.name];
+ NSArray *pathComponents = [localObjectName pathComponents];
+ if (skipHidden) {
+ BOOL skipObject = NO;
+ for (NSString *pathComponent in pathComponents) {
+ if ([pathComponent hasPrefix:@"."]) {
+ // Skip hidden directory and its descendants, or hidden file
+ // Remove stored state if any
+ [containerStoredLocalObjectStates removeObjectForKey:objectName];
+ skipObject = YES;
+ break;
+ }
+ }
+ if (skipObject)
+ continue;
+ }
+ if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
+ // Skip excluded directory object and its descendants, or root file object with same name
+ // Remove stored state if any
+ [containerStoredLocalObjectStates removeObjectForKey:object.name];
+ continue;
+ } else if (containerExcludeRootFiles &&
+ ([pathComponents count] == 1) &&
+ ![PithosUtilities isContentTypeDirectory:object.contentType]) {
+ // Skip root file object
+ // Remove stored state if any
+ [containerStoredLocalObjectStates removeObjectForKey:object.name];
+ continue;
+ }
+ if (![containerStoredLocalObjectStates objectForKey:object.name])
+ [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name];
+ }
[self saveLocalState];
}
- } else {
- // Local state has changed
- if (!serverStateHasChanged) {
- // Server state hasn't changed
- [self updateServerStateWithCurrentState:currentLocalObjectState
- object:object
- localFilePath:filePath];
- } else {
- // Server state has also changed
- if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
- // Both did the same change (directory)
- storedLocalObjectState.isDirectory = YES;
- [self saveLocalState];
- } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
- // Both did the same change (object edit or delete)
- if (!remoteObjectState.exists)
- [storedLocalObjectStates removeObjectForKey:object.name];
- else
- storedLocalObjectState.hash = remoteObjectState.hash;
- [self saveLocalState];
+ }
+
+ if (operation.isCancelled) {
+ operation.completionBlock = nil;
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+
+ // For each stored state compare with current and remote state
+ // Stored states of local objects that have been deleted,
+ // haven't been checked for legality (only existing local remote objects)
+ // These should be identified and skipped
+ for (NSString *accountName in accountsNames) {
+ for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
+ NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name];
+ NSSet *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name];
+ BOOL containerExcludeRootFiles = [containerExcludedDirectories containsObject:@""];
+ NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName]
+ objectForKey:pithosContainer.name];
+ NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name];
+ for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
+ if (operation.isCancelled) {
+ operation.completionBlock = nil;
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+
+ NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
+ if ([objectName hasSuffix:@"/"])
+ filePath = [filePath stringByAppendingString:@":"];
+ ASIPithosObject *object = [ASIPithosObject object];
+ object.name = objectName;
+ DLog(@"Sync::object name: %@", object.name);
+
+ PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
+ PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
+ if (!currentLocalObjectState) {
+ // The stored state corresponds to a remote or deleted local object, that's why there is no current state
+ // In the latter case it must be checked for legality, which can be determined by its stored state
+ // If it existed locally, but was deleted, state.exists is true,
+ // else if the stored state is an empty state that was created due to the server object, state.exists is false
+ if (storedLocalObjectState.exists) {
+ NSString *localObjectName;
+ if ([object.name hasSuffix:@"/"])
+ localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
+ else
+ localObjectName = [NSString stringWithString:object.name];
+ NSArray *pathComponents = [localObjectName pathComponents];
+ if (skipHidden) {
+ BOOL skipObject = NO;
+ for (NSString *pathComponent in pathComponents) {
+ if ([pathComponent hasPrefix:@"."]) {
+ // Skip hidden directory and its descendants, or hidden file
+ // Remove stored state if any
+ [containerStoredLocalObjectStates removeObjectForKey:objectName];
+ [self saveLocalState];
+ skipObject = YES;
+ break;
+ }
+ }
+ if (skipObject)
+ continue;
+ }
+ if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
+ // Skip excluded directory object and its descendants, or root file object with same name
+ // Remove stored state
+ [containerStoredLocalObjectStates removeObjectForKey:object.name];
+ [self saveLocalState];
+ continue;
+ } else if (containerExcludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) {
+ // Skip root file object
+ // Remove stored state
+ [containerStoredLocalObjectStates removeObjectForKey:object.name];
+ [self saveLocalState];
+ continue;
+ }
+ }
+ // There is also the off case that a local object has been created in the meantime
+ // This call works in any case, existent or non-existent local object
+ currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
+ blockHash:pithosContainer.blockHash
+ blockSize:pithosContainer.blockSize];
+ }
+
+ PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
+ ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
+ if (remoteObject) {
+ if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
+ remoteObjectState.isDirectory = YES;
+ } else {
+ remoteObjectState.hash = remoteObject.objectHash;
+ }
+ }
+
+ BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
+ BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
+ DLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
+ if (!localStateHasChanged) {
+ // Local state hasn't changed
+ if (serverStateHasChanged) {
+ // Server state has changed
+ // Update local state to match that of the server
+ object.bytes = remoteObject.bytes;
+ object.version = remoteObject.version;
+ object.contentType = remoteObject.contentType;
+ object.objectHash = remoteObject.objectHash;
+ [self updateLocalStateWithObject:object localFilePath:filePath
+ accountName:accountName pithosContainer:pithosContainer];
+ } else if (!remoteObject && ![currentLocalObjectState exists]) {
+ // Server state hasn't changed
+ // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
+ [containerStoredLocalObjectStates removeObjectForKey:objectName];
+ [self saveLocalState];
+ }
} else {
- // Conflict, we ask the user which change to keep
- NSString *informativeText;
- NSString *firstButtonText;
- NSString *secondButtonText;
-
- if (!remoteObjectState.exists) {
- // Remote object has been deleted
- informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
- firstButtonText = @"Delete local file";
- secondButtonText = @"Upload file to server";
- } else if (!currentLocalObjectState.exists) {
- informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
- firstButtonText = @"Download file from server";
- secondButtonText = @"Delete file on server";
+ // Local state has changed
+ if (!serverStateHasChanged) {
+ // Server state hasn't changed
+ if (currentLocalObjectState.isDirectory)
+ object.contentType = @"application/directory";
+ else
+ object.objectHash = currentLocalObjectState.hash;
+ [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
+ accountName:accountName pithosContainer:pithosContainer];
} else {
- informativeText = [NSString stringWithFormat:@"'%@' has been modifed both locally and on the server.", object.name];
- firstButtonText = @"Keep server version";
- secondButtonText = @"Keep local version";
- }
- NSAlert *alert = [[[NSAlert alloc] init] autorelease];
- [alert setMessageText:@"Conflict"];
- [alert setInformativeText:informativeText];
- [alert addButtonWithTitle:firstButtonText];
- [alert addButtonWithTitle:secondButtonText];
- [alert addButtonWithTitle:@"Do nothing"];
- NSInteger choice = [alert runModal];
- if (choice == NSAlertFirstButtonReturn) {
- object.bytes = remoteObject.bytes;
- object.version = remoteObject.version;
- object.contentType = remoteObject.contentType;
- object.hash = remoteObject.hash;
- [self updateLocalStateWithObject:object localFilePath:filePath];
- } if (choice == NSAlertSecondButtonReturn) {
- [self updateServerStateWithCurrentState:currentLocalObjectState
- object:object
- localFilePath:filePath];
+ // Server state has also changed
+ if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
+ // Both did the same change (directory)
+ storedLocalObjectState.filePath = filePath;
+ storedLocalObjectState.isDirectory = YES;
+ [self saveLocalState];
+ } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
+ // Both did the same change (object edit or delete)
+ if (![remoteObjectState exists]) {
+ [containerStoredLocalObjectStates removeObjectForKey:object.name];
+ } else {
+ storedLocalObjectState.filePath = filePath;
+ storedLocalObjectState.hash = remoteObjectState.hash;
+ }
+ [self saveLocalState];
+ } else {
+ // Conflict, we ask the user which change to keep
+ NSString *informativeText;
+ NSString *firstButtonText;
+ NSString *secondButtonText;
+
+ if (![remoteObjectState exists]) {
+ // Remote object has been deleted
+ informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name ];
+ firstButtonText = @"Delete local file";
+ secondButtonText = @"Upload file to server";
+ } else if (![currentLocalObjectState exists]) {
+ informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
+ firstButtonText = @"Download file from server";
+ secondButtonText = @"Delete file on server";
+ } else {
+ informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.",
+ [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name];
+ firstButtonText = @"Keep server version";
+ secondButtonText = @"Keep local version";
+ }
+ __block NSInteger choice;
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:@"Conflict"];
+ [alert setInformativeText:informativeText];
+ [alert addButtonWithTitle:firstButtonText];
+ [alert addButtonWithTitle:secondButtonText];
+ [alert addButtonWithTitle:@"Do nothing"];
+ choice = [alert runModal];
+ });
+ if (choice == NSAlertFirstButtonReturn) {
+ object.bytes = remoteObject.bytes;
+ object.version = remoteObject.version;
+ object.contentType = remoteObject.contentType;
+ object.objectHash = remoteObject.objectHash;
+ [self updateLocalStateWithObject:object localFilePath:filePath
+ accountName:accountName pithosContainer:pithosContainer];
+ } if (choice == NSAlertSecondButtonReturn) {
+ if (currentLocalObjectState.isDirectory)
+ object.contentType = @"application/directory";
+ else
+ object.objectHash = currentLocalObjectState.hash;
+ [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath
+ accountName:accountName pithosContainer:pithosContainer];
+ }
+ }
}
}
}
}
- }
- @synchronized(self) {
- [self decreaseSyncOperationCount];
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
- } else {
- NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
- if (retries > 0) {
- ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
- [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
- [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
- } else {
- [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
- withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
- @synchronized(self) {
- // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
- syncOperationCount = 0;
- if (newSyncRequested)
- [self sync];
}
+ [self syncOperationFinishedWithSuccess:YES];
+ } else {
+ [self listRequestFailed:containerRequest];
}
}
}
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
- if ([containerRequest isCancelled]) {
- [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
- withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
- [objects release];
- objects = nil;
- @synchronized(self) {
- syncOperationCount = 0;
+ @autoreleasepool {
+ NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
+ if (operation.isCancelled) {
+ objects = nil;
+ return;
}
- return;
- }
- // If the server listing fails, the sync should start over, so just retrying is enough
- NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
- if (retries > 0) {
- ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
- [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
- [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
- } else {
- [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
- withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
- [objects release];
- objects = nil;
- @synchronized(self) {
+ if (containerRequest.isCancelled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
+ withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ objects = nil;
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+ // If the server listing fails, the sync should start over, so just retrying is enough
+ NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+ if (retries > 0) {
+ ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ } else {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
+ withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ objects = nil;
// Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
- syncOperationCount = 0;
- if (newSyncRequested)
- [self sync];
+ [self syncOperationFinishedWithSuccess:NO];
}
}
}
- (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
- NSLog(@"Sync::download object block finished: %@", objectRequest.url);
- if (objectRequest.responseStatusCode == 206) {
- ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSError *error;
- PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
-
- NSString *downloadsDirPath = self.tempDownloadsDirPath;
- if (!downloadsDirPath) {
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
- @synchronized(self) {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- if (newSyncRequested && !syncOperationCount)
- [self sync];
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Sync::download object block finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 206) {
+ NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
+ ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
+ ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSError *error;
+ PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+
+ NSString *downloadsDirPath = self.tempDownloadsDirPath;
+ if (!downloadsDirPath) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
}
- return;
- }
-
- PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
- if ((storedState.filePath == nil) || ![fileManager fileExistsAtPath:storedState.filePath]) {
- NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
- const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
- char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
- strcpy(tempFileNameCString, tempFileTemplateCString);
- int fileDescriptor = mkstemp(tempFileNameCString);
- NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
- free(tempFileNameCString);
- if (fileDescriptor == -1) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
- message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", storedState.filePath]
- error:nil];
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
- @synchronized(self) {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- if (newSyncRequested && !syncOperationCount)
- [self sync];
+
+ PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
+ objectForKey:pithosContainer.name]
+ objectForKey:object.name];
+ if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
+ NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
+ const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
+ char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
+ strcpy(tempFileNameCString, tempFileTemplateCString);
+ int fileDescriptor = mkstemp(tempFileNameCString);
+ NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
+ free(tempFileNameCString);
+ if (fileDescriptor == -1) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
+ message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]
+ error:nil];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
}
- return;
+ close(fileDescriptor);
+ storedState.tmpFilePath = tempFilePath;
+ [self saveLocalState];
}
- close(fileDescriptor);
- storedState.filePath = tempFilePath;
- [self saveLocalState];
- }
-
- NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
- NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.filePath];
- [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
- [tempFileHandle writeData:[objectRequest responseData]];
- [tempFileHandle closeFile];
-
- NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
- missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
- if (missingBlockIndex == NSNotFound) {
- NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
- NSString *dirPath = [filePath stringByDeletingLastPathComponent];
- if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath]) {
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
- @synchronized(self) {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- if (newSyncRequested && !syncOperationCount)
- [self sync];
+ NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
+ NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
+ [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
+ [tempFileHandle writeData:[objectRequest responseData]];
+ [tempFileHandle closeFile];
+
+ NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
+ missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
+ if (missingBlockIndex == NSNotFound) {
+ NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
+ NSString *dirPath = [filePath stringByDeletingLastPathComponent];
+ if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath
+ accountName:accountName
+ pithosContainer:pithosContainer]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ } else if (![fileManager fileExistsAtPath:dirPath]) {
+ // File doesn't exist but also the containing directory doesn't exist
+ // In most cases this should have been resolved as an update of the corresponding local object,
+ // but it never hurts to check
+ error = nil;
+ [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
+ if (error != nil) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
+ message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
+ error:error];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
}
- return;
- } else if (![fileManager fileExistsAtPath:dirPath]) {
- // File doesn't exist but also the containing directory doesn't exist
- // In most cases this should have been resolved as an update of the corresponding local object,
- // but it never hurts to check
+ // Move file from tmp download
error = nil;
- [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
+ [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
if (error != nil) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
- message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
+ message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath]
error:error];
- [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
- withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
- @synchronized(self) {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
return;
}
- }
- // Move file from tmp download
- error = nil;
- [fileManager moveItemAtPath:storedState.filePath toPath:filePath error:&error];
- if (error != nil) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
- message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.filePath, filePath]
- error:error];
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
- @synchronized(self) {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
- return;
- }
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
- totalBytes:activity.totalBytes
- currentBytes:activity.totalBytes];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
+ totalBytes:activity.totalBytes
+ currentBytes:activity.totalBytes];
+ });
- storedState.hash = object.hash;
- storedState.filePath = nil;
- [self saveLocalState];
-
- @synchronized(self) {
- [self decreaseSyncOperationCount];
- if (newSyncRequested && !syncOperationCount)
- [self sync];
+ storedState.filePath = filePath;
+ storedState.hash = object.objectHash;
+ storedState.tmpFilePath = nil;
+ [self saveLocalState];
+ [self syncOperationFinishedWithSuccess:YES];
+ return;
+ } else {
+ if (newSyncRequested || syncLate || operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else {
+ __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ object:object
+ blockIndex:missingBlockIndex
+ blockSize:pithosContainer.blockSize];
+ if (![accountName isEqualToString:@""])
+ [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
+ newObjectRequest.delegate = self;
+ newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ newObjectRequest.userInfo = objectRequest.userInfo;
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+ [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
+ [activityFacility updateActivity:activity
+ withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
+ [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
+ (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
+ totalBytes:activity.totalBytes
+ currentBytes:(activity.currentBytes + size)];
+ }];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ }
}
- return;
- } else {
- if (newSyncRequested) {
+ } else if (objectRequest.responseStatusCode == 412) {
+ // The object has changed on the server
+ dispatch_async(dispatch_get_main_queue(), ^{
[activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
- @synchronized(self) {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- if (!syncOperationCount)
- [self sync];
- }
- return;
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ } else {
+ [self requestFailed:objectRequest];
+ }
+ }
+}
+
+- (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 200) {
+ if (newSyncRequested || syncLate || operation.isCancelled) {
+ [self requestFailed:objectRequest];
} else {
- __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
- object:object
- blockIndex:missingBlockIndex
- blockSize:blockSize];
+ NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
+ ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
+ ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
+ PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
+ objectForKey:pithosContainer.name]
+ objectForKey:object.name];
+ if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
+ [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
+ PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+ NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath
+ blockSize:pithosContainer.blockSize
+ blockHash:pithosContainer.blockHash
+ withHashes:[objectRequest hashes]];
+ NSUInteger missingBlockIndex = [missingBlocks firstIndex];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity
+ withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
+ [objectRequest.userInfo objectForKey:@"messagePrefix"],
+ (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))]
+ totalBytes:activity.totalBytes
+ currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
+ });
+
+ __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ object:object
+ blockIndex:missingBlockIndex
+ blockSize:pithosContainer.blockSize];
+ if (![accountName isEqualToString:@""])
+ [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos];
newObjectRequest.delegate = self;
- newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
- newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
+ newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
newObjectRequest.userInfo = objectRequest.userInfo;
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
[(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
[(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
[newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
[activityFacility updateActivity:activity
- withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
- object.name,
+ withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
+ [newObjectRequest.userInfo objectForKey:@"messagePrefix"],
(100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
totalBytes:activity.totalBytes
currentBytes:(activity.currentBytes + size)];
}];
- [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
- }
- }
- } else if (objectRequest.responseStatusCode == 412) {
- // The object has changed on the server
- [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
- withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
- @synchronized(self) {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
- } else {
- [self requestFailed:objectRequest];
- }
-}
-
-- (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
- NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
- if (objectRequest.responseStatusCode == 200) {
- if (newSyncRequested) {
- [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
- withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
- @synchronized(self) {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- if (!syncOperationCount)
- [self sync];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
}
- return;
} else {
- ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
- PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
- if ([PithosUtilities bytesOfFile:storedState.filePath] > object.bytes)
- [[NSFileHandle fileHandleForWritingAtPath:storedState.filePath] truncateFileAtOffset:object.bytes];
- PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
- NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.filePath
- blockSize:blockSize
- blockHash:blockHash
- withHashes:[objectRequest hashes]];
- NSUInteger missingBlockIndex = [missingBlocks firstIndex];
- [activityFacility endActivity:activity
- withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
- object.name,
- (100*(activity.totalBytes - [missingBlocks count]*blockSize + 0.0)/(activity.totalBytes + 0.0))]
- totalBytes:activity.totalBytes
- currentBytes:(activity.totalBytes - [missingBlocks count]*blockSize)];
-
- __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
- object:object
- blockIndex:missingBlockIndex
- blockSize:blockSize];
- newObjectRequest.delegate = self;
- newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
- newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
- newObjectRequest.userInfo = objectRequest.userInfo;
- [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
- [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
- [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
- [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
- [activityFacility updateActivity:activity
- withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)",
- object.name,
- (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
- totalBytes:activity.totalBytes
- currentBytes:(activity.currentBytes + size)];
- }];
- [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ [self requestFailed:objectRequest];
}
- } else {
- [self requestFailed:objectRequest];
}
}
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
- NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
- if (objectRequest.responseStatusCode == 201) {
- PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
- storedState.isDirectory = YES;
- [self saveLocalState];
- [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
- withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
- @synchronized(self) {
- [self decreaseSyncOperationCount];
- if (newSyncRequested && !syncOperationCount)
- [self sync];
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Sync::upload directory object finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 201) {
+ PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
+ objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
+ objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
+ storedState.isDirectory = YES;
+ [self saveLocalState];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:YES];
+ } else {
+ [self requestFailed:objectRequest];
}
- } else {
- [self requestFailed:objectRequest];
}
}
- (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
- NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
- if (objectRequest.responseStatusCode == 201) {
- [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
- [self saveLocalState];
- [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
- withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
- @synchronized(self) {
- [self decreaseSyncOperationCount];
- if (newSyncRequested && !syncOperationCount)
- [self sync];
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Sync::move object to trash finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 201) {
+ [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
+ objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
+ removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
+ [self saveLocalState];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:YES];
+ } else {
+ [self requestFailed:objectRequest];
}
- } else {
- [self requestFailed:objectRequest];
}
}
-- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
- NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
- ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
- PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
- PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
- NSUInteger totalBytes = activity.totalBytes;
- NSUInteger currentBytes = activity.currentBytes;
- if (objectRequest.responseStatusCode == 201) {
- NSLog(@"Sync::object created: %@", objectRequest.url);
- storedState.hash = [objectRequest eTag];
- [self saveLocalState];
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
- totalBytes:totalBytes
- currentBytes:totalBytes];
- @synchronized(self) {
- [self decreaseSyncOperationCount];
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
- } else if (objectRequest.responseStatusCode == 409) {
- if (newSyncRequested) {
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
- @synchronized(self) {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- if (!syncOperationCount)
- [self sync];
- }
- return;
+- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Sync::delete object finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 204) {
+ [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]]
+ objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
+ removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
+ [self saveLocalState];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:YES];
} else {
- NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
- if (iteration == 0) {
- NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- return;
- }
- NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
- NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
- withMissingHashesResponse:[objectRequest responseString]];
- if (totalBytes >= [missingBlocks count]*blockSize)
- currentBytes = totalBytes - [missingBlocks count]*blockSize;
- [activityFacility updateActivity:activity
- withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
- totalBytes:totalBytes
- currentBytes:currentBytes];
- NSUInteger missingBlockIndex = [missingBlocks firstIndex];
- __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
- blockSize:blockSize
- forFile:[objectRequest.userInfo objectForKey:@"filePath"]
- missingBlockIndex:missingBlockIndex
- sharingAccount:nil];
- newContainerRequest.delegate = self;
- newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
- newContainerRequest.didFailSelector = @selector(requestFailed:);
- newContainerRequest.userInfo = objectRequest.userInfo;
- [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
- [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
- [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
- [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
- [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
- [activityFacility updateActivity:activity
- withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
- totalBytes:activity.totalBytes
- currentBytes:(activity.currentBytes + size)];
- }];
- [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ [self requestFailed:objectRequest];
}
- } else {
- [self requestFailed:objectRequest];
}
}
-- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
- NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
- if (containerRequest.responseStatusCode == 202) {
- ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
- PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
- NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
- NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
- missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
- if (missingBlockIndex == NSNotFound) {
- NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
- ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName
- objectName:object.name
- contentType:object.contentType
- blockSize:blockSize
- blockHash:blockHash
- forFile:[containerRequest.userInfo objectForKey:@"filePath"]
- checkIfExists:NO
- hashes:&hashes
- sharingAccount:nil];
- newObjectRequest.delegate = self;
- newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
- newObjectRequest.didFailSelector = @selector(requestFailed:);
- newObjectRequest.userInfo = containerRequest.userInfo;
- [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
- [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
- [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
- [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
- } else {
- if (newSyncRequested) {
+- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
+ NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"];
+ ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
+ ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
+ PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
+ objectForKey:pithosContainer.name]
+ objectForKey:object.name];
+ PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+ NSUInteger totalBytes = activity.totalBytes;
+ NSUInteger currentBytes = activity.currentBytes;
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 201) {
+ DLog(@"Sync::object created: %@", objectRequest.url);
+ storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
+ storedState.hash = object.objectHash;
+ [self saveLocalState];
+ dispatch_async(dispatch_get_main_queue(), ^{
[activityFacility endActivity:activity
- withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
- @synchronized(self) {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- if (!syncOperationCount)
- [self sync];
- }
- return;
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
+ totalBytes:totalBytes
+ currentBytes:totalBytes];
+ });
+ [self syncOperationFinishedWithSuccess:YES];
+ } else if (objectRequest.responseStatusCode == 409) {
+ if (newSyncRequested || syncLate || operation.isCancelled) {
+ [self requestFailed:objectRequest];
} else {
- __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
- blockSize:blockSize
- forFile:[containerRequest.userInfo objectForKey:@"filePath"]
- missingBlockIndex:missingBlockIndex
- sharingAccount:nil];
+ NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
+ if (iteration == 0) {
+ DLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+ DLog(@"Sync::object is missing hashes: %@", objectRequest.url);
+ NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
+ withMissingHashes:[objectRequest hashes]];
+ if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
+ currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity
+ withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
+ [objectRequest.userInfo objectForKey:@"messagePrefix"],
+ (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
+ totalBytes:totalBytes
+ currentBytes:currentBytes];
+ });
+ NSUInteger missingBlockIndex = [missingBlocks firstIndex];
+ __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ blockSize:pithosContainer.blockSize
+ forFile:[objectRequest.userInfo objectForKey:@"filePath"]
+ missingBlockIndex:missingBlockIndex
+ sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
newContainerRequest.delegate = self;
- newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
- newContainerRequest.didFailSelector = @selector(requestFailed:);
- newContainerRequest.userInfo = containerRequest.userInfo;
+ newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ newContainerRequest.userInfo = objectRequest.userInfo;
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
[(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
[(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
[newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
[activityFacility updateActivity:activity
- withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
+ withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
+ [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
+ (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
totalBytes:activity.totalBytes
currentBytes:(activity.currentBytes + size)];
}];
- [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
}
+ } else {
+ [self requestFailed:objectRequest];
}
- } else {
- [self requestFailed:containerRequest];
}
}
-- (void)requestFailed:(ASIPithosRequest *)request {
- if ([request isCancelled]) {
- [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
- withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- return;
- }
- if (newSyncRequested) {
- [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
- withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
- @synchronized(self) {
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
- if (!syncOperationCount)
- [self sync];
+- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
+ @autoreleasepool {
+ NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:containerRequest];
+ } else if (containerRequest.responseStatusCode == 202) {
+ NSString *accountName = [containerRequest.userInfo objectForKey:@"accountName"];
+ ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
+ ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
+ PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
+ NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
+ NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
+ missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
+ if (operation.isCancelled) {
+ [self requestFailed:containerRequest];
+ } else if (missingBlockIndex == NSNotFound) {
+ NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
+ ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ objectName:object.name
+ contentType:object.contentType
+ blockSize:pithosContainer.blockSize
+ blockHash:pithosContainer.blockHash
+ forFile:[containerRequest.userInfo objectForKey:@"filePath"]
+ checkIfExists:NO
+ hashes:&hashes
+ sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
+ newObjectRequest.delegate = self;
+ newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ newObjectRequest.userInfo = containerRequest.userInfo;
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ } else {
+ if (newSyncRequested || syncLate || operation.isCancelled) {
+ [self requestFailed:containerRequest];
+ } else {
+ __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ blockSize:pithosContainer.blockSize
+ forFile:[containerRequest.userInfo objectForKey:@"filePath"]
+ missingBlockIndex:missingBlockIndex
+ sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)];
+ newContainerRequest.delegate = self;
+ newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ newContainerRequest.userInfo = containerRequest.userInfo;
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
+ [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
+ [activityFacility updateActivity:activity
+ withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)",
+ [newContainerRequest.userInfo objectForKey:@"messagePrefix"],
+ (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
+ totalBytes:activity.totalBytes
+ currentBytes:(activity.currentBytes + size)];
+ }];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ }
+ }
+ } else {
+ [self requestFailed:containerRequest];
}
- return;
- }
- NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
- if (retries > 0) {
- ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
- [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
- [queue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
- } else {
- [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
- withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
- syncIncomplete = YES;
- [self decreaseSyncOperationCount];
}
}
+- (void)requestFailed:(ASIPithosRequest *)request {
+ @autoreleasepool {
+ NSOperation *operation = [request.userInfo objectForKey:@"operation"];
+ DLog(@"Sync::request failed: %@", request.url);
+ if (operation.isCancelled)
+ return;
+ if (request.isCancelled || newSyncRequested || syncLate) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+ NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+ if (retries > 0) {
+ ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
+ [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ } else {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ }
+ }
+}
@end