// 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"
-#import "FileMD5Hash.h"
-#import "HashMapHash.h"
-
-#define DATA_MODEL_FILE @"localstate.archive"
-#define ARCHIVE_KEY @"Data"
@interface PithosSyncDaemon (Private)
-- (NSString *)pithosStateFilePath;
+- (void)loadLocalState;
+- (void)resetLocalStateWithAll:(BOOL)all;
- (void)saveLocalState;
-- (BOOL)localStateHasChanged:(PithosLocalObjectState *)storedState currentState:(PithosLocalObjectState *)currentState;
-- (BOOL)serverStateHasChanged:(PithosLocalObjectState *)storedState
- remoteObjectHash:(NSString *)remoteObjectHash
- remoteObjectIsDirectory:(BOOL)remoteObjectIsDirectory;
-- (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath;
--(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
- object:(ASIPithosObject *)object
- localFilePath:(NSString *)filePath;
+
+- (BOOL)moveToTempTrashFile:(NSString *)filePath pithosContainer:(ASIPithosContainer *)pithosContainer;
+- (void)emptyTempTrash;
+- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
+
+- (void)updateLocalStateWithObject:(ASIPithosObject *)object
+ localFilePath:(NSString *)filePath
+ pithosContainer:(ASIPithosContainer *)pithosContainer;
+- (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
+ object:(ASIPithosObject *)object
+ localFilePath:(NSString *)filePath
+ pithosContainer:(ASIPithosContainer *)pithosContainer;
+- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest;
- (void)requestFailed:(ASIPithosRequest *)request;
+
+- (void)syncOperationStarted;
+- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull;
+
@end
@implementation PithosSyncDaemon
-@synthesize blockHash, blockSize, lastModified, remoteObjects, storedLocalObjectStates;
-@synthesize pithosStateFilePath, tempDownloadsDirPath;
+@synthesize directoryPath, pithosAccount, containersDictionary, pithos;
+@synthesize pithosContainers;
+@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
+ containersDictionary:(NSMutableDictionary *)aContainersDictionary
+ resetLocalState:(BOOL)resetLocalState {
if ((self = [super init])) {
directoryPath = [aDirectoryPath copy];
- containerName = [aContainerName copy];
- timeInterval = aTimeInterval;
+ pithosAccount = [aPithosAccount retain];
+ containersDictionary = [aContainersDictionary retain];
+ self.pithos = pithosAccount.pithos;
- syncOperationCount = 0;
- newSyncRequested = NO;
+ containersCount = [containersDictionary count];
+ self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount];
+ for (NSString *containerName in [containersDictionary allKeys]) {
+ ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
+ pithosContainer.name = containerName;
+ [pithosContainers addObject:pithosContainer];
+ }
activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
- if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath]) {
- NSData *data = [NSData dataWithContentsOfFile:self.pithosStateFilePath];
- NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
- self.storedLocalObjectStates = [unarchiver decodeObjectForKey:ARCHIVE_KEY];
- [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.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 {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath])
+ self.storedLocalObjectStates = [NSKeyedUnarchiver unarchiveObjectWithFile:self.pithosStateFilePath];
+ else
+ self.storedLocalObjectStates = [NSMutableDictionary dictionary];
+ if (!storedLocalObjectStates)
+ self.storedLocalObjectStates = [NSMutableDictionary dictionary];
+ for (ASIPithosContainer *pithosContainer in pithosContainers) {
+ if (![storedLocalObjectStates objectForKey:pithosContainer.name]) {
+ [storedLocalObjectStates setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
+ }
+ }
+ [pool drain];
+}
+
+- (void)resetLocalStateWithAll:(BOOL)all {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ self.lastCompletedSync = nil;
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSError *error;
+ 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) {
+ 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;
+ }
+ }
+ } else {
+ // Remove containers that don't interest us anymore and save
+ if (!storedLocalObjectStates)
+ [self loadLocalState];
+ for (NSString *containerName in [storedLocalObjectStates allKeys]) {
+ if (![containersDictionary objectForKey:containerName]) {
+ [storedLocalObjectStates removeObjectForKey:containerName];
+ if (self.tempDownloadsDirPath) {
+ NSString *containerTempDownloadsDirPath = [self.tempDownloadsDirPath stringByAppendingPathComponent:containerName];
+ BOOL isDirectory;
+ BOOL fileExists = [fileManager fileExistsAtPath:containerTempDownloadsDirPath isDirectory:&isDirectory];
+ if (fileExists && isDirectory) {
+ error = nil;
+ for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:containerTempDownloadsDirPath error:&error]) {
+ if (error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
+ message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'",
+ containerTempDownloadsDirPath]
+ error:error];
+ break;
+ }
+ NSString *subFilePath = [containerTempDownloadsDirPath 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;
+ }
+ } else if (fileExists && !isDirectory) {
+ error = nil;
+ if (![fileManager removeItemAtPath:containerTempDownloadsDirPath error:&error] || error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
+ message:[NSString stringWithFormat:@"Cannot remove file at '%@'",
+ containerTempDownloadsDirPath]
+ error:error];
+ }
+ }
+ }
+ }
+ }
+ [self saveLocalState];
+ }
+ [pool drain];
+}
+
+- (void)saveLocalState {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [NSKeyedArchiver archiveRootObject:storedLocalObjectStates toFile:self.pithosStateFilePath];
+ [pool drain];
+}
+
+- (void)resetDaemon {
+ @synchronized(self) {
+ if (!daemonActive)
+ return;
+ }
+
+ [networkQueue reset];
+ [callbackQueue cancelAllOperations];
+ [callbackQueue setSuspended:YES];
+ [self emptyTempTrash];
- timer = [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(sync) userInfo:nil repeats:YES] retain];
- [timer fire];
+ syncOperationCount = 0;
+
+ @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];
+ [self resetDaemon];
+ [callbackQueue release];
+ [networkQueue release];
+ [tempTrashDirPath release];
[tempDownloadsDirPath release];
[pithosStateFilePath release];
+ [currentLocalObjectStates release];
[storedLocalObjectStates release];
+ [previousRemoteObjects release];
[remoteObjects release];
[objects release];
- [lastModified release];
- [blockHash release];
- [containerName release];
+ [lastCompletedSync release];
+ [pithosContainers release];
+ [pithos release];
+ [containersDictionary release];
+ [pithosAccount release];
[directoryPath release];
[super dealloc];
}
- (NSString *)pithosStateFilePath {
if (!pithosStateFilePath) {
- pithosStateFilePath = [[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:DATA_MODEL_FILE] retain];
+ pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
+ stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive",
+ pithosAccount.uniqueName]] retain];
+ 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];
+ return [[pithosStateFilePath copy] autorelease];
}
- (NSString *)tempDownloadsDirPath {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- if (!tempDownloadsDirPath || ![fileManager fileExistsAtPath:tempDownloadsDirPath]) {
- // Get the path from user defaults
- tempDownloadsDirPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"PithosTempDownloadsDirPath"];
- 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) {
+ tempDownloadsDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
+ stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads",
+ pithosAccount.uniqueName]] retain];
+ 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;
+}
+
+- (NSString *)tempTrashDirPath {
+ if (!tempTrashDirPath) {
+ tempTrashDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
+ stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash",
+ pithosAccount.uniqueName]] retain];
+ 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 release];
+ directoryPath = [aDirectoryPath copy];
+ }
+}
+
+- (void)setContainersDictionary:(NSDictionary *)aContainersDictionary {
+ if (aContainersDictionary && ![aContainersDictionary isEqualToDictionary:containersDictionary]) {
+ [self resetDaemon];
+ [containersDictionary release];
+ containersDictionary = [aContainersDictionary retain];
+ containersCount = [containersDictionary count];
+ self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount];
+ for (NSString *containerName in [containersDictionary allKeys]) {
+ ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
+ pithosContainer.name = containerName;
+ [pithosContainers addObject:pithosContainer];
}
- if (!tempDownloadsDirPath)
- [[NSUserDefaults standardUserDefaults] setObject:tempDownloadsDirPath forKey:@"PithosTempDownloadsDirPath"];
- [tempDownloadsDirPath retain];
+ [self resetLocalStateWithAll:NO];
+ }
+}
+
+- (void)setPithos:(ASIPithos *)aPithos {
+ if (!pithos) {
+ pithos = [[ASIPithos pithos] retain];
+ pithos.authUser = [[aPithos.authUser copy] autorelease];
+ pithos.authToken = [[aPithos.authToken copy] autorelease];
+ pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
+ pithos.authURL = [[aPithos.authURL copy] autorelease];
+ pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
}
- return [tempDownloadsDirPath 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] autorelease];
+ pithos.authToken = [[aPithos.authToken copy] autorelease];
+ pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
+ pithos.authURL = [[aPithos.authURL copy] autorelease];
+ pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
+ }
}
#pragma mark -
#pragma mark Sync
-- (void)saveLocalState {
- NSMutableData *data = [NSMutableData data];
- NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
- [archiver encodeObject:storedLocalObjectStates forKey:ARCHIVE_KEY];
- [archiver finishEncoding];
- [data writeToFile:self.pithosStateFilePath atomically:YES];
+- (void)syncOperationStarted {
+ @synchronized(self) {
+ syncOperationCount++;
+ }
+}
+
+- (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
+ NSLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
+ return;
+ }
+ syncOperationCount--;
+ if (syncOperationCount == 0) {
+ if (!syncIncomplete) {
+ self.lastCompletedSync = [NSDate date];
+ 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 && containersCount) {
// The first operation is the server listing
- syncOperationCount = 1;
+ [self syncOperationStarted];
newSyncRequested = NO;
+ syncIncomplete = NO;
+ syncLate = NO;
+ } else {
+ return;
}
}
- NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:containerName];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDirectory;
NSError *error = nil;
- if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory] &&
- (![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 (![fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory]) {
+ if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error] ||
+ error) {
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error"
+ message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", directoryPath]
+ error:error];
+ [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]
+ message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", directoryPath]
error:nil];
- @synchronized(self) {
- syncOperationCount = 0;
- }
+ [self syncOperationFinishedWithSuccess:NO];
return;
}
-
- ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName
- limit:0
- marker:nil
- prefix:nil
- delimiter:nil
- path:nil
- meta:nil
- shared:NO
- until:nil
- ifModifiedSince:lastModified];
+ NSString *containerDirectoryPath;
+ for (ASIPithosContainer *pithosContainer in pithosContainers) {
+ containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+ 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];
+ [self syncOperationFinishedWithSuccess:NO];
+ 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];
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+ }
+ containersIndex = 0;
+ self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:containersCount];
+ ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
+ containerName:[[pithosContainers objectAtIndex:containersIndex] name]
+ limit:0
+ marker:nil
+ prefix:nil
+ delimiter:nil
+ path:nil
+ meta:nil
+ shared:NO
+ until:nil
+ ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
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:NSOperationQueuePriorityHigh], @"priority",
[NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector",
nil];
-// [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh]];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
}
-- (BOOL)localStateHasChanged:(PithosLocalObjectState *)storedState currentState:(PithosLocalObjectState *)currentState {
- if (currentState.isDirectory)
- // Currently a directory, check previous state
- return (!storedState.isDirectory);
- if (storedState.isDirectory)
- // Previously a directory, currently a file or doesn't exist, state has changed
- return YES;
- if ([currentState.md5 isEqualToString:@" "] && [currentState.hashMapHash isEqualToString:@" "] &&
- (![storedState.md5 isEqualToString:@" "] || ![storedState.hashMapHash isEqualToString:@" "]))
- // Currently doesn't exist, previously a file, state has changed
- return YES;
- if (![storedState.md5 isEqualToString:currentState.md5] && ![storedState.hashMapHash isEqualToString:currentState.hashMapHash])
- // Neither hash remained the same, different files, state has changed
- return YES;
- else
- // At least one hash remained the same (the other is either the same or emmpty), state hasn't changed
- return NO;
+- (void)emptyTempTrash {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ 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) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
+ message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath]
+ error:error];
+ });
+ [pool drain];
+ 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) {
+// dispatch_async(dispatch_get_main_queue(), ^{
+// [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)
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
+ message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath]
+ error:error];
+ });
+ }
+ }
+ }
+ [pool drain];
}
-- (BOOL)serverStateHasChanged:(PithosLocalObjectState *)storedState
- remoteObjectHash:(NSString *)remoteObjectHash
- remoteObjectIsDirectory:(BOOL)remoteObjectIsDirectory {
- if (remoteObjectIsDirectory)
- // Remotely a directory, check previous state
- return (!storedState.isDirectory);
- if (storedState.isDirectory)
- // Previously a directory, remotely a file or doesn't exist, state has changed
- return YES;
- if ([remoteObjectHash length] == 32)
- // Remotely a file, check previous state
- return ![remoteObjectHash isEqualToString:storedState.md5];
- else if ([remoteObjectHash length] == 64)
- // Remotely a file, check previous state
- return ![remoteObjectHash isEqualToString:storedState.hashMapHash];
- else if ([remoteObjectHash isEqualToString:@" "]) {
- // Remotely doesn't exist
- if ([storedState.md5 isEqualToString:@" "] && [storedState.hashMapHash isEqualToString:@" "])
- // Previously didn't exist, state hasn't changed
+- (BOOL)moveToTempTrashFile:(NSString *)filePath pithosContainer:(ASIPithosContainer *)pithosContainer {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ if (!self.tempTrashDirPath) {
+ [pool drain];
+ return NO;
+ }
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ BOOL isDirectory;
+ BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
+ NSError *error = nil;
+ NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+ NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath withString:self.tempTrashDirPath];
+ NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
+ if (fileExists && isDirectory) {
+ NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
+ if (error) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
+ message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath]
+ error:error];
+ });
+ [pool drain];
return NO;
- else
- // Previously did exist, state has changed
- return YES;
+ }
+ if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
+ message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
+ error:error];
+ });
+ [pool drain];
+ return NO;
+ }
+ if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
+ message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
+ filePath, newFilePath]
+ error:error];
+ });
+ [pool drain];
+ 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];
+ }
+ for (NSString *subPath in subPaths) {
+ NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
+ NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath
+ 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) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
+ message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath]
+ error:error];
+ });
+ [pool drain];
+ return NO;
+ }
+ if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
+ message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'",
+ filePath, newFilePath]
+ error:error];
+ });
+ [pool drain];
+ 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];
+ }
}
- // Only if the server doesn't respond properly this will be reached, leave as is for now
+ [pool drain];
+ return YES;
+}
+
+- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
+ if ([hash length] != 64)
+ return NO;
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ 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.hash] &&
+ [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
+ if ([localFilePath hasPrefix:directoryPath]) {
+ if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error"
+ message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'",
+ localFilePath, filePath]
+ error:error];
+ });
+ } else {
+ [pool drain];
+ return YES;
+ }
+ } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
+ if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [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];
+ [pool drain];
+ return YES;
+ }
+ }
+ }
+ }
+ [pool drain];
return NO;
}
- (void)updateLocalStateWithObject:(ASIPithosObject *)object
- localFilePath:(NSString *)filePath {
+ localFilePath:(NSString *)filePath
+ pithosContainer:(ASIPithosContainer *)pithosContainer {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
BOOL isDirectory;
BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
- PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
- if ([object.hash isEqualToString:@" "]) {
+ NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
+ NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates 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:nil];
+ if (!object || !object.objectHash) {
// Delete local object
- // XXX move to local trash instead
- NSLog(@"Sync::delete local object: %@", filePath);
- BOOL deleteFailed = NO;
- if (fileExists) {
- error = nil;
- if (![fileManager removeItemAtPath:filePath error:&error] || error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
- message:[NSString stringWithFormat:@"Cannot remove file at '%@'", filePath]
- error:error];
- deleteFailed = YES;
- }
+ if (remoteObjectExists) {
+ // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
+ syncIncomplete = YES;
}
- if (!deleteFailed) {
- [activityFacility startAndEndActivityWithType:PithosActivityOther
- message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]];
- [storedLocalObjectStates removeObjectForKey:object.name];
+ NSLog(@"Sync::delete local object: %@", filePath);
+ if (!fileExists || [self moveToTempTrashFile:filePath 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 if ([object.contentType isEqualToString:@"application/directory"]) {
+ } 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;
+ [pool drain];
+ return;
+ }
NSLog(@"Sync::create local directory object: %@", filePath);
BOOL directoryCreated = NO;
- if (fileExists && !isDirectory) {
- NSLog(@"Sync::delete local file object: %@", filePath);
- error = nil;
- if (![fileManager removeItemAtPath:filePath error:&error] || error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
- message:[NSString stringWithFormat:@"Cannot remove file at '%@'", filePath]
- error:error];
- }
- }
- if (![fileManager fileExistsAtPath:filePath]) {
+ if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath pithosContainer:pithosContainer])) {
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];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [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 if (isDirectory) {
+ } 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]];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility startAndEndActivityWithType:PithosActivityOther
+ message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)",
+ 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;
+ [pool drain];
+ return;
+ }
NSLog(@"Sync::create local zero length object: %@", filePath);
BOOL fileCreated = NO;
- if (fileExists && (isDirectory || [PithosUtilities bytesOfFile:filePath])) {
- NSLog(@"Sync::delete local object: %@", filePath);
+ if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath pithosContainer:pithosContainer])) {
+ NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
+ // Create directory of the file, if it doesn't exist
error = nil;
- if (![fileManager removeItemAtPath:filePath error:&error] || error) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
- message:[NSString stringWithFormat:@"Cannot remove file at '%@'", filePath]
- error:error];
+ 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];
+ });
}
- }
- if (![fileManager fileExistsAtPath: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];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error"
+ message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath]
+ error:error];
+ });
} else {
fileCreated = YES;
- if ([object.hash length] == 32) {
- storedState.md5 = object.hash;
- storedState.hashMapHash = @" ";
- } else if([object.hash length] == 64) {
- storedState.md5 = @" ";
- storedState.hashMapHash = object.hash;
- } else {
- storedState.md5 = @" ";
- storedState.hashMapHash = @" ";
- }
- storedState.tmpDownloadFile = nil;
+ storedState.filePath = filePath;
+ storedState.hash = object.objectHash;
+ storedState.tmpFilePath = nil;
[self saveLocalState];
}
- } else if (!isDirectory && ![PithosUtilities bytesOfFile:filePath]) {
+ } 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.tmpDownloadFile == nil) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility startAndEndActivityWithType:PithosActivityOther
+ message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)",
+ pithosContainer.name, object.name]
+ pithosAccount:pithosAccount];
+ });
+ } else if (storedState.tmpFilePath == nil) {
// Create new local object
- @synchronized(self) {
- syncOperationCount++;
+ 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;
+ [pool drain];
+ return;
+ }
+ // 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];
+ });
+ }
+ // 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%%)",
+ 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];
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
+ message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (0%%)",
+ pithosContainer.name, object.name]
+ totalBytes:object.bytes
+ currentBytes:0
+ pithosAccount:pithosAccount];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
+ objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ pithosContainer, @"pithosContainer",
+ object, @"pithosObject",
+ [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks",
+ [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex",
+ filePath, @"filePath",
+ activity, @"activity",
+ [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage",
+ [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage",
+ [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", pithosContainer.name, object.name], @"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:@"Sync: Downloading '%@/%@' (%.0f%%)",
+ objectRequest.containerName,
+ [[objectRequest.userInfo objectForKey:@"pithosObject"] name],
+ (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
+ totalBytes:activity.totalBytes
+ currentBytes:(activity.currentBytes + size)];
+ }];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
}
- __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:NSOperationQueuePriorityHigh], @"priority",
- [NSNumber numberWithUnsignedInteger:10], @"retries",
- nil];
- [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
- [activityFacility updateActivity:activity
- withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)",
- [[objectRequest.userInfo valueForKey:@"pithosObject"] name],
- (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
- totalBytes:activity.totalBytes
- currentBytes:(activity.currentBytes + size)];
- }];
-// [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
} else {
// Resume local object download
- @synchronized(self) {
- syncOperationCount++;
+ 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;
+ [pool drain];
+ return;
+ }
+ // 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];
+ });
+ }
+ // 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%%)",
+ pithosContainer.name, object.name]
+ pithosAccount:pithosAccount];
+ });
+ } else {
+ [self syncOperationStarted];
+ ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ objectName:object.name];
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
+ message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (0%%)",
+ pithosContainer.name, object.name]
+ totalBytes:object.bytes
+ currentBytes:0
+ pithosAccount:pithosAccount];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
+ objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ pithosContainer, @"pithosContainer",
+ object, @"pithosObject",
+ filePath, @"filePath",
+ activity, @"activity",
+ [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage",
+ [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage",
+ [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", pithosContainer.name, object.name], @"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]];
}
- 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:NSOperationQueuePriorityHigh], @"priority",
- [NSNumber numberWithUnsignedInteger:10], @"retries",
- nil];
-// [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
}
+ [pool drain];
}
-(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState
object:(ASIPithosObject *)object
- localFilePath:(NSString *)filePath {
+ localFilePath:(NSString *)filePath
+ pithosContainer:(ASIPithosContainer *)pithosContainer {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [self syncOperationStarted];
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ BOOL isDirectory;
+ BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
if (currentState.isDirectory) {
// Create remote directory object
- @synchronized(self) {
- syncOperationCount++;
+ 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];
+ [pool drain];
+ return;
}
- 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]];
+ 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]];
objectRequest.delegate = self;
- objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
- objectRequest.didFailSelector = @selector(requestFailed:);
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
- message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]];
+ message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@'",
+ pithosContainer.name, object.name]
+ pithosAccount:pithosAccount];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ pithosContainer, @"pithosContainer",
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",
+ [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage",
+ [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage",
+ [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage",
[NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
[NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
nil];
-// [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
- } else if ([currentState.md5 isEqualToString:@" "] && [currentState.hashMapHash isEqualToString:@" "]) {
+ [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ } else if (![currentState exists]) {
// Delete remote object
- @synchronized(self) {
- syncOperationCount++;
+ 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(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
+ message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@'",
+ pithosContainer.name, object.name]
+ pithosAccount:pithosAccount];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
+ objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ pithosContainer, @"pithosContainer",
+ object, @"pithosObject",
+ activity, @"activity",
+ [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage",
+ [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage",
+ [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ nil];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ } else {
+ // 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
+ destinationContainerName:@"trash"
+ destinationObjectName:safeName
+ checkIfExists:NO];
+ if (objectRequest) {
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
+ message:[NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'",
+ pithosContainer.name, object.name]
+ pithosAccount:pithosAccount];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
+ objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ pithosContainer, @"pithosContainer",
+ object, @"pithosObject",
+ activity, @"activity",
+ [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage",
+ [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage",
+ [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (finished)", pithosContainer.name, object.name], @"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];
+ }
}
- ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:containerName
- objectName:object.name];
- objectRequest.delegate = self;
- objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
- objectRequest.didFailSelector = @selector(requestFailed:);
- PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
- message:[NSString stringWithFormat:@"Sync: Deleting '%@'", object.name]];
- objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- object, @"pithosObject",
- activity, @"activity",
- [NSString stringWithFormat:@"Sync: Deleting '%@' (stopped)", object.name], @"stoppedActivityMessage",
- [NSString stringWithFormat:@"Sync: Deleting '%@' (failed)", object.name], @"failedActivityMessage",
- [NSString stringWithFormat:@"Sync: Deleting '%@' (finished)", object.name], @"finishedActivityMessage",
- [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
- [NSNumber numberWithUnsignedInteger:10], @"retries",
- nil];
-// [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
} else {
// Upload file to remote object
+ 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];
+ [pool drain];
+ return;
+ }
NSError *error = nil;
object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
if (object.contentType == nil)
if (error)
NSLog(@"contentType detection error: %@", error);
NSArray *hashes = nil;
- ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName
- objectName:object.name
- contentType:object.contentType
- blockSize:blockSize
- blockHash:blockHash
- forFile:filePath
- checkIfExists:NO
- hashes:&hashes
- sharingAccount: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:nil];
if (objectRequest) {
- @synchronized(self) {
- syncOperationCount++;
- }
objectRequest.delegate = self;
- objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
- objectRequest.didFailSelector = @selector(requestFailed:);
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
- message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
- totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
- currentBytes:0];
+ message:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (0%%)",
+ pithosContainer.name, object.name]
+ 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:
+ pithosContainer, @"pithosContainer",
object, @"pithosObject",
filePath, @"filePath",
hashes, @"hashes",
[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:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
[NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
nil]];
-// [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
+ } else {
+ [self syncOperationFinishedWithSuccess:NO];
}
}
-
+ [pool drain];
}
#pragma mark -
#pragma mark ASIHTTPRequestDelegate
+- (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] autorelease];
+ operation.completionBlock = ^{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ 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];
+ }
+ [pool drain];
+ };
+ [(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] autorelease];
+ operation.completionBlock = ^{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ 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];
+ }
+ [pool drain];
+ };
+ [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
+ [callbackQueue addOperation:operation];
+ }
+}
+
- (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
NSLog(@"Sync::list request finished: %@", containerRequest.url);
- if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
+ if (operation.isCancelled) {
+ [self listRequestFailed:containerRequest];
+ } else if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
if (containerRequest.responseStatusCode == 200) {
NSArray *someObjects = [containerRequest objects];
if (objects == nil) {
[objects addObjectsFromArray:someObjects];
}
if ([someObjects count] < 10000) {
- self.blockHash = [containerRequest blockHash];
- self.blockSize = [containerRequest blockSize];
- self.lastModified = [containerRequest lastModified];
- self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
+ ASIPithosContainer *pithosContainer = [pithosContainers objectAtIndex:containersIndex];
+ pithosContainer.blockHash = [containerRequest blockHash];
+ pithosContainer.blockSize = [containerRequest blockSize];
+ pithosContainer.lastModified = [containerRequest lastModified];
+ NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
for (ASIPithosObject *object in objects) {
- [remoteObjects setObject:object forKey:object.name];
+ [containerRemoteObjects setObject:object forKey:object.name];
}
+ [remoteObjects setObject:containerRemoteObjects forKey:pithosContainer.name];
[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];
+ ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
+ containerName:[[pithosContainers objectAtIndex:containersIndex] name]
+ limit:0
+ marker:[[someObjects lastObject] name]
+ prefix:nil
+ delimiter:nil
+ path:nil
+ meta:nil
+ shared:NO
+ until:nil
+ ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
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"];
-// [[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityHigh]];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ [pool drain];
return;
}
+ } else {
+ ASIPithosContainer *pithosContainer = [pithosContainers objectAtIndex:containersIndex];
+ NSMutableDictionary *containerRemoteObjects = [previousRemoteObjects objectForKey:pithosContainer.name];
+ if (containerRemoteObjects)
+ [remoteObjects setObject:containerRemoteObjects forKey:pithosContainer.name];
}
- [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
- withMessage:@"Sync: Getting server listing (finished)"];
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:containerName];
- 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];
- }
+ containersIndex++;
+ if (containersIndex < containersCount) {
+ // Do a request for the next container
+ ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
+ containerName:[[pithosContainers objectAtIndex:containersIndex] name]
+ limit:0
+ marker:nil
+ prefix:nil
+ delimiter:nil
+ path:nil
+ meta:nil
+ shared:NO
+ until:nil
+ ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
+ 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]]];
+ [pool drain];
return;
}
- for (NSString *objectName in subPaths) {
- if (![storedLocalObjectStates objectForKey:objectName]) {
- [storedLocalObjectStates setObject:[PithosLocalObjectState nullObjectState] forKey:objectName];
- }
+ self.previousRemoteObjects = remoteObjects;
+
+ if (operation.isCancelled) {
+ [self listRequestFailed:containerRequest];
+ [pool drain];
+ return;
}
- [self saveLocalState];
- for (NSString *objectName in remoteObjects) {
- if (![storedLocalObjectStates objectForKey:objectName])
- [storedLocalObjectStates setObject:[PithosLocalObjectState nullObjectState] forKey:objectName];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
+ withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+ });
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSError *error;
+ NSMutableDictionary *subPaths = [NSMutableDictionary dictionaryWithCapacity:containersCount];
+ NSUInteger subPathsCount = 0;
+ NSString *containerDirectoryPath;
+ NSArray *containerSubPaths;
+ for (ASIPithosContainer *pithosContainer in pithosContainers) {
+ error = nil;
+ containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+ containerSubPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
+ if (error) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [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"
+ pithosAccount:pithosAccount];
+ });
+ // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccessfully
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
+ return;
+ }
+ [subPaths setObject:containerSubPaths forKey:pithosContainer.name];
+ subPathsCount += [containerSubPaths count];
+ }
+
+ if (operation.isCancelled) {
+ operation.completionBlock = nil;
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
+ return;
}
- 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 = [PithosLocalObjectState nullObjectState];
- BOOL isDirectory;
- if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
- if (isDirectory) {
- currentLocalObjectState.isDirectory = YES;
+ self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:subPathsCount];
+ NSMutableDictionary *containerStoredLocalObjectStates;
+ for (ASIPithosContainer *pithosContainer in pithosContainers) {
+ containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+ containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
+ for (NSString *objectName in [subPaths objectForKey:pithosContainer.name]) {
+ if (operation.isCancelled) {
+ operation.completionBlock = nil;
+ [self saveLocalState];
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
+ return;
+ }
+
+ PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
+ NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent: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 {
- currentLocalObjectState.md5 = (NSString *)FileMD5HashCreateWithPath((CFStringRef)filePath,
- FileHashDefaultChunkSizeForReadingData);
- currentLocalObjectState.hashMapHash = [HashMapHash calculateHashMapHash:[HashMapHash calculateObjectHashMap:filePath
- withBlockHash:blockHash
- andBlockSize:blockSize]];
+ // Local object hasn't changed, set stored state also to current
+ [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
}
}
- if (currentLocalObjectState.isDirectory)
- object.contentType = @"application/directory";
-
- NSString *remoteObjectHash = @" ";
- BOOL remoteObjectIsDirectory = NO;
- ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
- if (remoteObject) {
- remoteObjectHash = remoteObject.hash;
- if ([remoteObject.contentType isEqualToString:@"application/directory"]) {
- remoteObjectIsDirectory = YES;
- object.contentType = @"application/directory";
+ [self saveLocalState];
+ }
+
+ if (operation.isCancelled) {
+ operation.completionBlock = nil;
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
+ return;
+ }
+
+ for (ASIPithosContainer *pithosContainer in pithosContainers) {
+ containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
+ for (NSString *objectName in [remoteObjects objectForKey:pithosContainer.name]) {
+ if (operation.isCancelled) {
+ operation.completionBlock = nil;
+ [self saveLocalState];
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
+ return;
}
+
+ if (![containerStoredLocalObjectStates objectForKey:objectName])
+ [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
}
- NSLog(@"Sync::remote object is directory: %d", remoteObjectIsDirectory);
+ [self saveLocalState];
+ }
+
+ if (operation.isCancelled) {
+ operation.completionBlock = nil;
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
+ return;
+ }
+
+ NSMutableDictionary *containerRemoteObjects;
+ for (ASIPithosContainer *pithosContainer in pithosContainers) {
+ containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+ containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
+ containerRemoteObjects = [remoteObjects objectForKey:pithosContainer.name];
+ for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
+ if (operation.isCancelled) {
+ operation.completionBlock = nil;
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
+ return;
+ }
+
+ NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
+ if ([objectName hasSuffix:@"/"])
+ filePath = [filePath stringByAppendingString:@":"];
+ ASIPithosObject *object = [ASIPithosObject object];
+ object.name = objectName;
+ NSLog(@"Sync::object name: %@", object.name);
+
+ PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
+ PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
+ if (!currentLocalObjectState)
+ currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath
+ blockHash:pithosContainer.blockHash
+ blockSize:pithosContainer.blockSize];
+// if (currentLocalObjectState.isDirectory)
+// object.contentType = @"application/directory";
- BOOL localStateHasChanged = [self localStateHasChanged:storedLocalObjectState currentState:currentLocalObjectState];
- BOOL serverStateHasChanged = [self serverStateHasChanged:storedLocalObjectState
- remoteObjectHash:remoteObjectHash
- remoteObjectIsDirectory:remoteObjectIsDirectory];
- NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
- // XXX shouldn't we first do all the deletes? in order not to face a dir that becomes a file and vice versa
- 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 = remoteObjectHash;
- [self updateLocalStateWithObject:object localFilePath:filePath];
+ PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
+ ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
+ if (remoteObject) {
+ if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
+ remoteObjectState.isDirectory = YES;
+// object.contentType = @"application/directory";
+ } else {
+ remoteObjectState.hash = remoteObject.objectHash;
+ }
}
- } 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 (remoteObjectIsDirectory && currentLocalObjectState.isDirectory) {
- // Both did the same change (directory)
- storedLocalObjectState.isDirectory = YES;
- [self saveLocalState];
- } else if ([remoteObjectHash isEqualToString:currentLocalObjectState.md5] ||
- [remoteObjectHash isEqualToString:currentLocalObjectState.hashMapHash]) {
- // Both did the same change (object edit or delete)
- if ([remoteObjectHash length] == 32)
- storedLocalObjectState.md5 = remoteObjectHash;
- else if ([remoteObjectHash length] == 64)
- storedLocalObjectState.hashMapHash = remoteObjectHash;
- else if ([remoteObjectHash isEqualToString:@" "])
- [storedLocalObjectStates removeObjectForKey:object.name];
+
+ 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.objectHash = remoteObject.objectHash;
+ [self updateLocalStateWithObject:object localFilePath:filePath 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 {
+ // 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
+ pithosContainer:pithosContainer];
} else {
- // Conflict, we ask the user which change to keep
- NSString *informativeText;
- NSString *firstButtonText;
- NSString *secondButtonText;
-
- if ([remoteObjectHash isEqualToString:@" "]) {
- // 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.md5 isEqualToString:@" "] && [currentLocalObjectState.hashMapHash isEqualToString:@" "]) {
- 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";
+ // 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 {
- 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 = remoteObjectHash;
- [self updateLocalStateWithObject:object localFilePath:filePath];
- } if (choice == NSAlertSecondButtonReturn) {
- [self updateServerStateWithCurrentState:currentLocalObjectState
- object:object
- localFilePath:filePath];
+ // 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.",
+ 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.",
+ 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.",
+ pithosContainer.name, 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.objectHash = remoteObject.objectHash;
+ [self updateLocalStateWithObject:object localFilePath:filePath pithosContainer:pithosContainer];
+ } if (choice == NSAlertSecondButtonReturn) {
+ if (currentLocalObjectState.isDirectory)
+ object.contentType = @"application/directory";
+ else
+ object.objectHash = currentLocalObjectState.hash;
+ [self updateServerStateWithCurrentState:currentLocalObjectState
+ object:object
+ localFilePath:filePath
+ pithosContainer:pithosContainer];
+ }
}
}
}
}
}
- @synchronized(self) {
- syncOperationCount--;
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
+ [self syncOperationFinishedWithSuccess:YES];
} 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"];
-// [[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityHigh]];
- } else {
- [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
- withMessage:@"Sync: Getting server listing (failed)"];
- @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 listRequestFailed:containerRequest];
}
+ [pool drain];
}
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
- if ([containerRequest isCancelled]) {
- [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
- withMessage:@"Sync: Getting server listing (stopped)"];
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
+ if (operation.isCancelled) {
[objects release];
objects = nil;
- @synchronized(self) {
- syncOperationCount = 0;
- }
+ [pool drain];
+ return;
+ }
+ if (containerRequest.isCancelled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
+ withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ [objects release];
+ objects = nil;
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
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];
+ ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
[(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
-// [[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:NSOperationQueuePriorityVeryHigh]];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
} else {
- [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
- withMessage:@"Sync: Getting server listing (failed)"];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
+ withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
[objects release];
objects = nil;
- @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];
- }
+ // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
+ [self syncOperationFinishedWithSuccess:NO];
}
+ [pool drain];
}
- (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
NSLog(@"Sync::download object block finished: %@", objectRequest.url);
- if (objectRequest.responseStatusCode == 206) {
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 206) {
+ ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSString *downloadsDirPath = self.tempDownloadsDirPath;
if (!downloadsDirPath) {
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
return;
}
- PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
- if ((storedState.tmpDownloadFile == nil) || ![fileManager fileExistsAtPath:storedState.tmpDownloadFile]) {
+ PithosLocalObjectState *storedState = [[storedLocalObjectStates 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);
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.tmpDownloadFile]
- error:nil];
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error"
+ message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]
+ error:nil];
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
return;
}
close(fileDescriptor);
- storedState.tmpDownloadFile = tempFilePath;
+ storedState.tmpFilePath = tempFilePath;
[self saveLocalState];
}
NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
- NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpDownloadFile];
- [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
+ NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
+ [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
[tempFileHandle writeData:[objectRequest responseData]];
[tempFileHandle closeFile];
if (missingBlockIndex == NSNotFound) {
NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
NSString *dirPath = [filePath stringByDeletingLastPathComponent];
- if ([fileManager fileExistsAtPath:filePath]) {
- error = nil;
- // XXX don't delete but move to local trash instead
- [fileManager removeItemAtPath:filePath error:&error];
- if (error != nil) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error"
- message:[NSString stringWithFormat:@"Cannot remove file at '%@'", filePath]
- error:error];
+ if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath pithosContainer:pithosContainer]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
[activityFacility endActivity:activity
withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
- return;
- }
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
+ 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,
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];
- [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
- withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error"
+ message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath]
+ error:error];
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
return;
}
}
// Move file from tmp download
error = nil;
- [fileManager moveItemAtPath:storedState.tmpDownloadFile toPath:filePath error:&error];
+ [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
if (error != nil) {
- [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
- message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpDownloadFile, filePath]
- error:error];
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error"
+ message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath]
+ error:error];
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
return;
}
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
- totalBytes:activity.totalBytes
- currentBytes:activity.totalBytes];
-
- if ([object.hash length] == 32) {
- storedState.md5 = object.hash;
- storedState.hashMapHash = @" ";
- } else if([object.hash length] == 64) {
- storedState.md5 = @" ";
- storedState.hashMapHash = object.hash;
- } else {
- storedState.md5 = @" ";
- storedState.hashMapHash = @" ";
- }
-
- storedState.tmpDownloadFile = nil;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
+ totalBytes:activity.totalBytes
+ currentBytes:activity.totalBytes];
+ });
+
+ storedState.filePath = filePath;
+ storedState.hash = object.objectHash;
+ storedState.tmpFilePath = nil;
[self saveLocalState];
-
- @synchronized(self) {
- syncOperationCount--;
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
+ [self syncOperationFinishedWithSuccess:YES];
+ [pool drain];
return;
} else {
- if (newSyncRequested) {
- [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
- withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- if (!syncOperationCount)
- [self sync];
- }
- return;
+ if (newSyncRequested || syncLate || operation.isCancelled) {
+ [self requestFailed:objectRequest];
} else {
- __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName
- object:object
- blockIndex:missingBlockIndex
- blockSize:blockSize];
+ __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ object:object
+ blockIndex:missingBlockIndex
+ blockSize:pithosContainer.blockSize];
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:[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:@"Downloading '%@' (%.0f%%)",
- object.name,
+ withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)",
+ newObjectRequest.containerName, object.name,
(100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
totalBytes:activity.totalBytes
currentBytes:(activity.currentBytes + size)];
}];
-// [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityHigh]];
+ [networkQueue 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) {
- syncOperationCount--;
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
} else {
[self requestFailed:objectRequest];
}
+ [pool drain];
}
- (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
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) {
- syncOperationCount--;
- if (!syncOperationCount)
- [self sync];
- }
- return;
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 200) {
+ if (newSyncRequested || syncLate || operation.isCancelled) {
+ [self requestFailed:objectRequest];
} else {
+ ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
- PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
- if ([PithosUtilities bytesOfFile:storedState.tmpDownloadFile] > object.bytes)
- [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpDownloadFile] truncateFileAtOffset:object.bytes];
+ PithosLocalObjectState *storedState = [[storedLocalObjectStates 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.tmpDownloadFile
- blockSize:blockSize
- blockHash:blockHash
+ NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath
+ blockSize:pithosContainer.blockSize
+ blockHash:pithosContainer.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];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity
+ withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)",
+ pithosContainer.name, object.name,
+ (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];
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:@"Downloading '%@' (%.0f%%)",
- object.name,
+ withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)",
+ newObjectRequest.containerName, object.name,
(100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
totalBytes:activity.totalBytes
currentBytes:(activity.currentBytes + size)];
}];
-// [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityHigh]];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
}
} else {
[self requestFailed:objectRequest];
}
+ [pool drain];
}
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
- if (objectRequest.responseStatusCode == 201) {
- PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 201) {
+ PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
+ objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
storedState.isDirectory = YES;
[self saveLocalState];
- [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
- withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:YES];
+ } else {
+ [self requestFailed:objectRequest];
+ }
+ [pool drain];
+}
+
+- (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 201) {
+ [[storedLocalObjectStates 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];
}
+ [pool drain];
}
- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
NSLog(@"Sync::delete object finished: %@", objectRequest.url);
- if (objectRequest.responseStatusCode == 204) {
- [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 204) {
+ [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]]
+ removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
[self saveLocalState];
- [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
- withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:YES];
} else {
[self requestFailed:objectRequest];
}
+ [pool drain];
}
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
+ ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
- PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
+ PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
NSUInteger totalBytes = activity.totalBytes;
NSUInteger currentBytes = activity.currentBytes;
- if (objectRequest.responseStatusCode == 201) {
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 201) {
NSLog(@"Sync::object created: %@", objectRequest.url);
- NSString *eTag = [objectRequest eTag];
- if ([eTag length] == 32)
- storedState.md5 = eTag;
- else if([eTag length] == 64)
- storedState.hashMapHash = eTag;
+ storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
+ storedState.hash = object.objectHash;
[self saveLocalState];
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
- totalBytes:totalBytes
- currentBytes:totalBytes];
- @synchronized(self) {
- syncOperationCount--;
- if (newSyncRequested && !syncOperationCount)
- [self sync];
- }
- } else if (objectRequest.responseStatusCode == 409) {
- if (newSyncRequested) {
+ dispatch_async(dispatch_get_main_queue(), ^{
[activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- 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 {
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"]];
- @synchronized(self) {
- syncOperationCount--;
- }
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ [pool drain];
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:@"Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
- totalBytes:totalBytes
- currentBytes:currentBytes];
+ 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:@"Sync: Uploading '%@/%@' (%.0f%%)",
+ pithosContainer.name, 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];
+ __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ blockSize:pithosContainer.blockSize
+ forFile:[objectRequest.userInfo objectForKey:@"filePath"]
+ missingBlockIndex:missingBlockIndex
+ sharingAccount:nil];
newContainerRequest.delegate = self;
- newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
- newContainerRequest.didFailSelector = @selector(requestFailed:);
+ 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:@"Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
+ withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)",
+ newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
totalBytes:activity.totalBytes
currentBytes:(activity.currentBytes + size)];
}];
-// [[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
- [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];
}
+ [pool drain];
}
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
- if (containerRequest.responseStatusCode == 202) {
+ if (operation.isCancelled) {
+ [self requestFailed:containerRequest];
+ } else if (containerRequest.responseStatusCode == 202) {
+ ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
- NSUInteger totalBytes = activity.totalBytes;
- NSUInteger currentBytes = activity.currentBytes + blockSize;
- if (currentBytes > totalBytes)
- currentBytes = totalBytes;
- [activityFacility updateActivity:activity
- withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
- totalBytes:totalBytes
- currentBytes:currentBytes];
NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
- if (missingBlockIndex == NSNotFound) {
+ if (operation.isCancelled) {
+ [self requestFailed:containerRequest];
+ } else 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];
+ 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:nil];
newObjectRequest.delegate = self;
- newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
- newObjectRequest.didFailSelector = @selector(requestFailed:);
+ 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"];
-// [[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
} else {
- if (newSyncRequested) {
- [activityFacility endActivity:activity
- withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- if (!syncOperationCount)
- [self sync];
- }
- return;
+ if (newSyncRequested || syncLate || operation.isCancelled) {
+ [self requestFailed:containerRequest];
} else {
- __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
- blockSize:blockSize
- forFile:[containerRequest.userInfo objectForKey:@"filePath"]
- missingBlockIndex:missingBlockIndex
- sharingAccount:nil];
+ __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
+ containerName:pithosContainer.name
+ blockSize:pithosContainer.blockSize
+ forFile:[containerRequest.userInfo objectForKey:@"filePath"]
+ missingBlockIndex:missingBlockIndex
+ sharingAccount:nil];
newContainerRequest.delegate = self;
- newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
- newContainerRequest.didFailSelector = @selector(requestFailed:);
+ 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:@"Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
+ withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)",
+ newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
totalBytes:activity.totalBytes
currentBytes:(activity.currentBytes + size)];
}];
-// [[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
- [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:containerRequest];
}
+ [pool drain];
}
- (void)requestFailed:(ASIPithosRequest *)request {
- if ([request isCancelled]) {
- [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
- withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- }
- return;
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSOperation *operation = [request.userInfo objectForKey:@"operation"];
+ NSLog(@"Sync::request failed: %@", request.url);
+ if (operation.isCancelled) {
+ [pool drain];
+ return;
}
- if (newSyncRequested) {
- [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
- withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- if (!syncOperationCount)
- [self sync];
- }
+ 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];
+ [pool drain];
return;
}
NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
if (retries > 0) {
- ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
+ ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
[(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
-// [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
- [queue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
} else {
- [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
- withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
- @synchronized(self) {
- syncOperationCount--;
- }
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
}
+ [pool drain];
}
-
@end