From: Miltiadis Vasilakis Date: Thu, 5 Apr 2012 16:14:25 +0000 (+0300) Subject: Sync with "shared to me" selected accounts. X-Git-Tag: v0.9~9^2~1 X-Git-Url: https://code.grnet.gr/git/pithos-macos/commitdiff_plain/62e7fb925b34ba4189df0a8cee2f7951e8f0520e Sync with "shared to me" selected accounts. --- diff --git a/pithos-macos/PithosSyncDaemon.h b/pithos-macos/PithosSyncDaemon.h index 04f5f71..49a2a2d 100644 --- a/pithos-macos/PithosSyncDaemon.h +++ b/pithos-macos/PithosSyncDaemon.h @@ -46,10 +46,11 @@ PithosAccount *pithosAccount; NSDictionary *accountsDictionary; ASIPithos *pithos; - - NSDictionary *containersDictionary; - NSUInteger containersCount; - NSMutableArray *pithosContainers; + + NSUInteger accountsCount; + NSMutableArray *accountsNames; + NSUInteger accountsIndex; + NSMutableDictionary *accountsPithosContainers; NSUInteger containersIndex; NSMutableArray *objects; @@ -80,8 +81,8 @@ @property (nonatomic, retain) NSDictionary *accountsDictionary; @property (nonatomic, retain) ASIPithos *pithos; -@property (nonatomic, retain) NSDictionary *containersDictionary; -@property (nonatomic, retain) NSMutableArray *pithosContainers; +@property (nonatomic, retain) NSMutableArray *accountsNames; +@property (nonatomic, retain) NSMutableDictionary *accountsPithosContainers; @property (nonatomic, retain) NSMutableDictionary *remoteObjects; @property (nonatomic, retain) NSMutableDictionary *previousRemoteObjects; diff --git a/pithos-macos/PithosSyncDaemon.m b/pithos-macos/PithosSyncDaemon.m index 073d61c..a61b81d 100644 --- a/pithos-macos/PithosSyncDaemon.m +++ b/pithos-macos/PithosSyncDaemon.m @@ -53,16 +53,24 @@ - (void)resetLocalStateWithAll:(BOOL)all; - (void)saveLocalState; -- (BOOL)moveToTempTrashFile:(NSString *)filePath pithosContainer:(ASIPithosContainer *)pithosContainer; +- (BOOL)createSyncDirectory:(NSString *)dirPath; +- (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName; +- (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName; + +- (BOOL)moveToTempTrashFile:(NSString *)filePath + accountName:(NSString *)accountName + pithosContainer:(ASIPithosContainer *)pithosContainer; - (void)emptyTempTrash; - (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath; - (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath + accountName:(NSString *)accountName pithosContainer:(ASIPithosContainer *)pithosContainer; - (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState object:(ASIPithosObject *)object localFilePath:(NSString *)filePath + accountName:(NSString *)accountName pithosContainer:(ASIPithosContainer *)pithosContainer; - (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest; - (void)requestFailed:(ASIPithosRequest *)request; @@ -74,7 +82,7 @@ @implementation PithosSyncDaemon @synthesize directoryPath, accountsDictionary, pithos; -@synthesize containersDictionary, pithosContainers; +@synthesize accountsNames, accountsPithosContainers; @synthesize lastCompletedSync, remoteObjects, previousRemoteObjects, storedLocalObjectStates, currentLocalObjectStates; @synthesize pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath; @@ -88,19 +96,8 @@ if ((self = [super init])) { directoryPath = [aDirectoryPath copy]; pithosAccount = [aPithosAccount retain]; - accountsDictionary = [anAccountsDictionary copy]; - self.pithos = pithosAccount.pithos; - - self.containersDictionary = [accountsDictionary objectForKey:@""]; - if (!containersDictionary) - self.containersDictionary = [NSDictionary dictionary]; - containersCount = [containersDictionary count]; - self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount]; - for (NSString *containerName in containersDictionary) { - ASIPithosContainer *pithosContainer = [ASIPithosContainer container]; - pithosContainer.name = containerName; - [pithosContainers addObject:pithosContainer]; - } + self.accountsDictionary = anAccountsDictionary; + self.pithos = pithosAccount.pithos; activityFacility = [PithosActivityFacility defaultPithosActivityFacility]; @@ -135,9 +132,15 @@ 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]; + for (NSString *accountName in accountsNames) { + NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName]; + if (!accountStoredLocalObjectStates) { + accountStoredLocalObjectStates = [NSMutableDictionary dictionary]; + [storedLocalObjectStates setObject:accountStoredLocalObjectStates forKey:accountName]; + } + for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) { + if (![accountStoredLocalObjectStates objectForKey:pithosContainer.name]) + [accountStoredLocalObjectStates setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name]; } } [pool drain]; @@ -146,69 +149,43 @@ - (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; - } - } + if (self.tempDownloadsDirPath) + [PithosUtilities removeContentsAtPath:self.tempDownloadsDirPath]; } else { // Remove containers that don't interest us anymore and save if (!storedLocalObjectStates) [self loadLocalState]; - for (NSString *containerName in storedLocalObjectStates) { - if (![containersDictionary objectForKey:containerName]) { - [storedLocalObjectStates removeObjectForKey:containerName]; + for (NSString *accountName in storedLocalObjectStates) { + NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName]; + NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName]; + if (!containersDictionary) { 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]; + if ([accountName isEqualToString:@""]) { + for (NSString *containerName in accountStoredLocalObjectStates) { + [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]]; } + } else { + [PithosUtilities removeContentsAtPath:[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared to me"] + stringByAppendingPathComponent:accountName]]; + } + } + [storedLocalObjectStates removeObjectForKey:accountName]; + } else { + // Check the account's containers + for (NSString *containerName in accountStoredLocalObjectStates) { + if (![containersDictionary objectForKey:containerName]) { + if ([accountName isEqualToString:@""]) + [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]]; + else + [PithosUtilities removeContentsAtPath:[[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared to me"] + stringByAppendingPathComponent:accountName] + stringByAppendingPathComponent:containerName]]; + [accountStoredLocalObjectStates removeObjectForKey:containerName]; } } } @@ -281,8 +258,8 @@ [remoteObjects release]; [objects release]; [lastCompletedSync release]; - [pithosContainers release]; - [containersDictionary release]; + [accountsPithosContainers release]; + [accountsNames release]; [pithos release]; [accountsDictionary release]; [pithosAccount release]; @@ -375,21 +352,43 @@ - (void)setAccountsDictionary:(NSDictionary *)anAccountsDictionary { if (anAccountsDictionary && ![anAccountsDictionary isEqualToDictionary:accountsDictionary]) { - [self resetDaemon]; + BOOL reset = (accountsDictionary != nil); + if (reset) + [self resetDaemon]; + [accountsDictionary release]; accountsDictionary = [anAccountsDictionary copy]; - self.containersDictionary = [accountsDictionary objectForKey:@""]; - if (!containersDictionary) - self.containersDictionary = [NSDictionary dictionary]; - containersCount = [containersDictionary count]; - self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount]; - for (NSString *containerName in containersDictionary) { - ASIPithosContainer *pithosContainer = [ASIPithosContainer container]; - pithosContainer.name = containerName; - [pithosContainers addObject:pithosContainer]; + accountsCount = [accountsDictionary count]; + self.accountsNames = [NSMutableArray arrayWithCapacity:accountsCount]; + self.accountsPithosContainers = [NSMutableDictionary dictionaryWithCapacity:accountsCount]; + NSDictionary *containersDictionary = [accountsDictionary objectForKey:@""]; + if (containersDictionary && [containersDictionary count]) { + NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]]; + for (NSString *containerName in containersDictionary) { + ASIPithosContainer *pithosContainer = [ASIPithosContainer container]; + pithosContainer.name = containerName; + [pithosContainers addObject:pithosContainer]; + } + [accountsNames addObject:@""]; + [accountsPithosContainers setObject:pithosContainers forKey:@""]; + } + for (NSString *accountName in accountsDictionary) { + NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName]; + if (![accountsNames containsObject:accountName] && [containersDictionary count]) { + NSMutableArray *pithosContainers = [NSMutableArray arrayWithCapacity:[containersDictionary count]]; + for (NSString *containerName in containersDictionary) { + ASIPithosContainer *pithosContainer = [ASIPithosContainer container]; + pithosContainer.name = containerName; + [pithosContainers addObject:pithosContainer]; + } + [accountsNames addObject:accountName]; + [accountsPithosContainers setObject:pithosContainers forKey:accountName]; + } } - [self resetLocalStateWithAll:NO]; + + if (reset) + [self resetLocalStateWithAll:NO]; } } @@ -419,6 +418,49 @@ } #pragma mark - +#pragma mark Helper Methods + +- (BOOL)createSyncDirectory:(NSString *)dirPath { + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isDirectory; + NSError *error = nil; + if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory]) { + if (![fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) { + [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" + message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", + dirPath] + error:error]; + return NO; + } + } else if (!isDirectory) { + [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" + message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", + dirPath] + error:nil]; + return NO; + } + return YES; +} + +- (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName { + if ([accountName isEqualToString:@""]) + return [directoryPath stringByAppendingPathComponent:containerName]; + else + return [[[directoryPath stringByAppendingPathComponent:@"shared to me"] + stringByAppendingPathComponent:accountName] + stringByAppendingPathComponent:containerName]; +} + +- (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName { + if ([accountName isEqualToString:@""]) + return containerName; + else + return [[[NSString stringWithString:@"shared to me"] + stringByAppendingPathComponent:accountName] + stringByAppendingPathComponent:containerName]; +} + +#pragma mark - #pragma mark Sync - (void)syncOperationStarted { @@ -473,7 +515,7 @@ // If at least one operation is running return newSyncRequested = YES; return; - } else if (daemonActive && containersCount) { + } else if (daemonActive && accountsCount) { // The first operation is the server listing [self syncOperationStarted]; newSyncRequested = NO; @@ -484,51 +526,29 @@ } } - NSFileManager *fileManager = [NSFileManager defaultManager]; - BOOL isDirectory; - NSError *error = nil; - 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; - } - } else if (!isDirectory) { - [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" - message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", directoryPath] - error:nil]; + if (![self createSyncDirectory:directoryPath]) { [self syncOperationFinishedWithSuccess:NO]; return; } - for (ASIPithosContainer *pithosContainer in pithosContainers) { - NSString *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]; + for (NSString *accountName in accountsNames) { + for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) { + if (![self createSyncDirectory:[self dirPathForAccount:accountName container:pithosContainer.name]]) { [self syncOperationFinishedWithSuccess:NO]; return; } - } 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; } } + + self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:accountsCount]; + for (NSString *accountName in accountsNames) { + [remoteObjects setObject:[NSMutableDictionary dictionary] forKey:accountName]; + } + accountsIndex = 0; containersIndex = 0; - self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:containersCount]; + NSString *accountName = [accountsNames objectAtIndex:accountsIndex]; + ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex]; ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos - containerName:[[pithosContainers objectAtIndex:containersIndex] name] + containerName:pithosContainer.name limit:0 marker:nil prefix:nil @@ -537,7 +557,9 @@ meta:nil shared:NO until:nil - ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]]; + ifModifiedSince:pithosContainer.lastModified]; + if (![accountName isEqualToString:@""]) + [containerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos]; containerRequest.delegate = self; containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); @@ -605,7 +627,9 @@ [pool drain]; } -- (BOOL)moveToTempTrashFile:(NSString *)filePath pithosContainer:(ASIPithosContainer *)pithosContainer { +- (BOOL)moveToTempTrashFile:(NSString *)filePath + accountName:(NSString *)accountName + pithosContainer:(ASIPithosContainer *)pithosContainer { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (!self.tempTrashDirPath) { [pool drain]; @@ -615,7 +639,7 @@ BOOL isDirectory; BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]; NSError *error = nil; - NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name]; + NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name]; NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath withString:self.tempTrashDirPath]; NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent]; if (fileExists && isDirectory) { @@ -759,6 +783,7 @@ - (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath + accountName:(NSString *)accountName pithosContainer:(ASIPithosContainer *)pithosContainer { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSFileManager *fileManager = [NSFileManager defaultManager]; @@ -766,7 +791,8 @@ BOOL isDirectory; BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]; NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent]; - NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name]; + NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName] + objectForKey:pithosContainer.name]; PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name]; // Remote updated info NSError *remoteError; @@ -776,15 +802,20 @@ objectName:object.name error:&remoteError isDirectory:&remoteIsDirectory - sharingAccount:nil]; + sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)]; if (!object || !object.objectHash) { // Delete local object + if (![accountName isEqualToString:@""]) { + // If "shared to me" skip + [pool drain]; + return; + } if (remoteObjectExists) { // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object syncIncomplete = YES; } NSLog(@"Sync::delete local object: %@", filePath); - if (!fileExists || [self moveToTempTrashFile:filePath pithosContainer:pithosContainer]) { + if (!fileExists || [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer]) { dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility startAndEndActivityWithType:PithosActivityOther message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)", @@ -804,7 +835,7 @@ } NSLog(@"Sync::create local directory object: %@", filePath); BOOL directoryCreated = NO; - if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath pithosContainer:pithosContainer])) { + if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) { NSLog(@"Sync::local directory object doesn't exist: %@", filePath); error = nil; if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) { @@ -827,7 +858,8 @@ dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility startAndEndActivityWithType:PithosActivityOther message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)", - pithosContainer.name, object.name] + [self relativeDirPathForAccount:accountName container:pithosContainer.name], + object.name] pithosAccount:pithosAccount]; }); } else if (object.bytes == 0) { @@ -840,7 +872,9 @@ } NSLog(@"Sync::create local zero length object: %@", filePath); BOOL fileCreated = NO; - if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath pithosContainer:pithosContainer])) { + if (!fileExists || + ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && + [self moveToTempTrashFile:filePath accountName:accountName pithosContainer:pithosContainer])) { NSLog(@"Sync::local zero length object doesn't exist: %@", filePath); // Create directory of the file, if it doesn't exist error = nil; @@ -873,7 +907,8 @@ dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility startAndEndActivityWithType:PithosActivityOther message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", - pithosContainer.name, object.name] + [self relativeDirPathForAccount:accountName container:pithosContainer.name], + object.name] pithosAccount:pithosAccount]; }); } else if (storedState.tmpFilePath == nil) { @@ -901,7 +936,8 @@ dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility startAndEndActivityWithType:PithosActivityOther message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", - pithosContainer.name, object.name] + [self relativeDirPathForAccount:accountName container:pithosContainer.name], + object.name] pithosAccount:pithosAccount]; }); } else { @@ -911,12 +947,15 @@ object:object blockIndex:0 blockSize:pithosContainer.blockSize]; + if (![accountName isEqualToString:@""]) + [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos]; objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); + NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'", + [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name]; PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload - message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (0%%)", - pithosContainer.name, object.name] + message:[messagePrefix stringByAppendingString:@" (0%%)"] totalBytes:object.bytes currentBytes:0 pithosAccount:pithosAccount]; @@ -924,15 +963,17 @@ [activityFacility updateActivity:activity withMessage:activity.message]; }); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + accountName, @"accountName", pithosContainer, @"pithosContainer", object, @"pithosObject", + messagePrefix, @"messagePrefix", [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks", [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex", filePath, @"filePath", activity, @"activity", - [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", + [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", + [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", + [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage", [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector", @@ -940,10 +981,8 @@ 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))] + withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", + messagePrefix, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] totalBytes:activity.totalBytes currentBytes:(activity.currentBytes + size)]; }]; @@ -976,7 +1015,8 @@ dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility startAndEndActivityWithType:PithosActivityOther message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", - pithosContainer.name, object.name] + [self relativeDirPathForAccount:accountName container:pithosContainer.name], + object.name] pithosAccount:pithosAccount]; }); } else { @@ -984,12 +1024,15 @@ ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos containerName:pithosContainer.name objectName:object.name]; + if (![accountName isEqualToString:@""]) + [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos]; objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); + NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Downloading '%@/%@'", + [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name]; PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload - message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (0%%)", - pithosContainer.name, object.name] + message:[messagePrefix stringByAppendingString:@" (0%%)"] totalBytes:object.bytes currentBytes:0 pithosAccount:pithosAccount]; @@ -997,13 +1040,15 @@ [activityFacility updateActivity:activity withMessage:activity.message]; }); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + accountName, @"accountName", pithosContainer, @"pithosContainer", object, @"pithosObject", + messagePrefix, @"messagePrefix", 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", + [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", + [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", + [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage", [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector", @@ -1018,6 +1063,7 @@ -(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState object:(ASIPithosObject *)object localFilePath:(NSString *)filePath + accountName:(NSString *)accountName pithosContainer:(ASIPithosContainer *)pithosContainer { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [self syncOperationStarted]; @@ -1026,6 +1072,22 @@ BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]; if (currentState.isDirectory) { // Create remote directory object + if (![accountName isEqualToString:@""]) { + if (!object.allowedTo) { + NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name]; + NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent]; + while ([objectAncestorName length] && !object.allowedTo) { + object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo]; + objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent]; + } + } + if (![object.allowedTo isEqualToString:@"write"]) { + // If read-only "shared to me" skip + [self syncOperationFinishedWithSuccess:YES]; + [pool drain]; + return; + } + } if (!fileExists || !isDirectory) { // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip [self syncOperationFinishedWithSuccess:NO]; @@ -1044,23 +1106,28 @@ isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil data:[NSData data]]; + if (![accountName isEqualToString:@""]) + [objectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos]; objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); + NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Creating directory '%@/%@'", + [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name]; PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory - message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@'", - pithosContainer.name, object.name] + message:messagePrefix pithosAccount:pithosAccount]; dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility updateActivity:activity withMessage:activity.message]; }); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + accountName, @"accountName", pithosContainer, @"pithosContainer", object, @"pithosObject", + messagePrefix, @"messagePrefix", activity, @"activity", - [NSString stringWithFormat:@"Sync: 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", + [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", + [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", + [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", @@ -1069,6 +1136,12 @@ [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]]; } else if (![currentState exists]) { // Delete remote object + if (![accountName isEqualToString:@""]) { + // If "shared to me" skip + [self syncOperationFinishedWithSuccess:YES]; + [pool drain]; + return; + } if (fileExists) { // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object syncIncomplete = YES; @@ -1081,20 +1154,23 @@ objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); + NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Deleting '%@/%@'", + [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name]; PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete - message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@'", - pithosContainer.name, object.name] + message:messagePrefix pithosAccount:pithosAccount]; dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility updateActivity:activity withMessage:activity.message]; }); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + accountName, @"accountName", pithosContainer, @"pithosContainer", object, @"pithosObject", + messagePrefix, @"messagePrefix", activity, @"activity", - [NSString stringWithFormat:@"Sync: 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", + [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", + [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", + [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", @@ -1119,20 +1195,23 @@ objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); + NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'", + [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name]; PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete - message:[NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'", - pithosContainer.name, object.name] + message:messagePrefix pithosAccount:pithosAccount]; dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility updateActivity:activity withMessage:activity.message]; }); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + accountName, @"accountName", pithosContainer, @"pithosContainer", object, @"pithosObject", + messagePrefix, @"messagePrefix", activity, @"activity", - [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (stopped)", 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", + [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", + [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", + [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage", [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector", @@ -1148,6 +1227,22 @@ } } else { // Upload file to remote object + if (![accountName isEqualToString:@""]) { + if (!object.allowedTo) { + NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name]; + NSString *objectAncestorName = [object.name stringByDeletingLastPathComponent]; + while ([objectAncestorName length] && !object.allowedTo) { + object.allowedTo = [[containerRemoteObjects objectForKey:objectAncestorName] allowedTo]; + objectAncestorName = [objectAncestorName stringByDeletingLastPathComponent]; + } + } + if (![object.allowedTo isEqualToString:@"write"]) { + // If read-only "shared to me" skip + [self syncOperationFinishedWithSuccess:YES]; + [pool drain]; + return; + } + } if (!fileExists || isDirectory) { // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip [self syncOperationFinishedWithSuccess:NO]; @@ -1170,14 +1265,15 @@ forFile:filePath checkIfExists:NO hashes:&hashes - sharingAccount:nil]; + sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)]; if (objectRequest) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); + NSString *messagePrefix = [NSString stringWithFormat:@"Sync: Uploading '%@/%@'", + [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name]; PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload - message:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (0%%)", - pithosContainer.name, object.name] + message:[messagePrefix stringByAppendingString:@" (0%%)"] totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue] currentBytes:0 pithosAccount:pithosAccount]; @@ -1186,15 +1282,17 @@ }); [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary: [NSDictionary dictionaryWithObjectsAndKeys: + accountName, @"accountName", pithosContainer, @"pithosContainer", object, @"pithosObject", + messagePrefix, @"messagePrefix", filePath, @"filePath", hashes, @"hashes", [NSNumber numberWithUnsignedInteger:10], @"iteration", activity, @"activity", - [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage", - [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage", - [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage", + [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage", + [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage", + [messagePrefix stringByAppendingString:@" (100%%)"], @"finishedActivityMessage", [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", @@ -1264,7 +1362,11 @@ NSLog(@"Sync::list request finished: %@", containerRequest.url); if (operation.isCancelled) { [self listRequestFailed:containerRequest]; - } else if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) { + } else if ((containerRequest.responseStatusCode == 200) || + (containerRequest.responseStatusCode == 304) || + (containerRequest.responseStatusCode == 403)) { + NSString *accountName = [accountsNames objectAtIndex:accountsIndex]; + ASIPithosContainer *pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex]; if (containerRequest.responseStatusCode == 200) { NSArray *someObjects = [containerRequest objects]; if (objects == nil) { @@ -1273,7 +1375,6 @@ [objects addObjectsFromArray:someObjects]; } if ([someObjects count] < 10000) { - ASIPithosContainer *pithosContainer = [pithosContainers objectAtIndex:containersIndex]; pithosContainer.blockHash = [containerRequest blockHash]; pithosContainer.blockSize = [containerRequest blockSize]; pithosContainer.lastModified = [containerRequest lastModified]; @@ -1281,13 +1382,13 @@ for (ASIPithosObject *object in objects) { [containerRemoteObjects setObject:object forKey:object.name]; } - [remoteObjects setObject:containerRemoteObjects forKey:pithosContainer.name]; + [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name]; [objects release]; objects = nil; } else { // Do an additional request to fetch more objects ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos - containerName:[[pithosContainers objectAtIndex:containersIndex] name] + containerName:pithosContainer.name limit:0 marker:[[someObjects lastObject] name] prefix:nil @@ -1296,7 +1397,9 @@ meta:nil shared:NO until:nil - ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]]; + ifModifiedSince:pithosContainer.lastModified]; + if (![accountName isEqualToString:@""]) + [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos]; newContainerRequest.delegate = self; newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); @@ -1306,17 +1409,26 @@ [pool drain]; return; } - } else { - ASIPithosContainer *pithosContainer = [pithosContainers objectAtIndex:containersIndex]; - NSMutableDictionary *containerRemoteObjects = [previousRemoteObjects objectForKey:pithosContainer.name]; + } else if (containerRequest.responseStatusCode == 304) { + NSMutableDictionary *containerRemoteObjects = [[previousRemoteObjects objectForKey:accountName] + objectForKey:pithosContainer.name]; if (containerRemoteObjects) - [remoteObjects setObject:containerRemoteObjects forKey:pithosContainer.name]; + [[remoteObjects objectForKey:accountName] setObject:containerRemoteObjects forKey:pithosContainer.name]; + } else if (containerRequest.responseStatusCode == 403) { + [[remoteObjects objectForKey:accountName] setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name]; } + containersIndex++; - if (containersIndex < containersCount) { + if (containersIndex == [[accountsPithosContainers objectForKey:accountName] count]) { + accountsIndex++; + containersIndex = 0; + } + if (accountsIndex < accountsCount) { + accountName = [accountsNames objectAtIndex:accountsIndex]; + pithosContainer = [[accountsPithosContainers objectForKey:accountName] objectAtIndex:containersIndex]; // Do a request for the next container ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos - containerName:[[pithosContainers objectAtIndex:containersIndex] name] + containerName:pithosContainer.name limit:0 marker:nil prefix:nil @@ -1325,7 +1437,9 @@ meta:nil shared:NO until:nil - ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]]; + ifModifiedSince:pithosContainer.lastModified]; + if (![accountName isEqualToString:@""]) + [newContainerRequest setRequestUserFromDefaultTo:accountName withPithos:pithos]; newContainerRequest.delegate = self; newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); @@ -1353,70 +1467,73 @@ // Compute current state of legal existing local objects // and add an empty stored state for legal new local objects since last sync self.currentLocalObjectStates = [NSMutableDictionary dictionary]; - for (ASIPithosContainer *pithosContainer in pithosContainers) { - NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name]; - NSArray *containerExcludedDirectories = [containersDictionary objectForKey:pithosContainer.name]; - BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""]; - NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name]; - NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath]; - for (NSString *objectName in dirEnumerator) { - if (operation.isCancelled) { - operation.completionBlock = nil; - [self saveLocalState]; - [self syncOperationFinishedWithSuccess:NO]; - [pool drain]; - return; - } + for (NSString *accountName in accountsNames) { + for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) { + NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name]; + NSArray *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name]; + BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""]; + NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName] + objectForKey:pithosContainer.name]; + NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath]; + for (NSString *objectName in dirEnumerator) { + if (operation.isCancelled) { + operation.completionBlock = nil; + [self saveLocalState]; + [self syncOperationFinishedWithSuccess:NO]; + [pool drain]; + return; + } - NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName]; - NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; - BOOL isDirectory; - BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]; - if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error" - message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.", - containerDirectoryPath, pithosAccount.name] - error:nil]; - }); - pithosAccount.syncActive = NO; - return; - } else if (fileExists) { - NSArray *pathComponents = [objectName pathComponents]; - if ([pathComponents count] == 1) { - if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) { - // Skip excluded directory and its descendants, or root file with same name - if (isDirectory) - [dirEnumerator skipDescendants]; - // Remove stored state if any - [containerStoredLocalObjectStates removeObjectForKey:objectName]; - continue; - } else if (!isDirectory && containerExludeRootFiles) { - // Skip excluded root file - // Remove stored state if any - [containerStoredLocalObjectStates removeObjectForKey:objectName]; - continue; + NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName]; + NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; + BOOL isDirectory; + BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]; + if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error" + message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.", + containerDirectoryPath, pithosAccount.name] + error:nil]; + }); + pithosAccount.syncActive = NO; + return; + } else if (fileExists) { + NSArray *pathComponents = [objectName pathComponents]; + if ([pathComponents count] == 1) { + if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) { + // Skip excluded directory and its descendants, or root file with same name + if (isDirectory) + [dirEnumerator skipDescendants]; + // Remove stored state if any + [containerStoredLocalObjectStates removeObjectForKey:objectName]; + continue; + } else if (!isDirectory && containerExludeRootFiles) { + // Skip excluded root file + // Remove stored state if any + [containerStoredLocalObjectStates removeObjectForKey:objectName]; + continue; + } + } + // Include local object + PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName]; + if (!storedLocalObjectState || [storedLocalObjectState isModified]) { + // New or modified existing local object, compute current state + if (!storedLocalObjectState) + // For new local object, also create empty stored state + [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName]; + [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath + blockHash:pithosContainer.blockHash + blockSize:pithosContainer.blockSize] + forKey:filePath]; + } else { + // Local object hasn't changed, set stored state also to current + [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath]; } - } - // Include local object - PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName]; - if (!storedLocalObjectState || [storedLocalObjectState isModified]) { - // New or modified existing local object, compute current state - if (!storedLocalObjectState) - // For new local object, also create empty stored state - [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName]; - [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath - blockHash:pithosContainer.blockHash - blockSize:pithosContainer.blockSize] - forKey:filePath]; - } else { - // Local object hasn't changed, set stored state also to current - [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath]; } } + [self saveLocalState]; } - [self saveLocalState]; - } + } if (operation.isCancelled) { operation.completionBlock = nil; @@ -1426,44 +1543,47 @@ } // Add an empty stored state for legal new remote objects since last sync - for (ASIPithosContainer *pithosContainer in pithosContainers) { - NSArray *containerExcludedDirectories = [containersDictionary objectForKey:pithosContainer.name]; - BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""]; - NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name]; - NSMutableDictionary *containerRemoteObjects = [remoteObjects objectForKey:pithosContainer.name]; - for (NSString *objectName in containerRemoteObjects) { - if (operation.isCancelled) { - operation.completionBlock = nil; - [self saveLocalState]; - [self syncOperationFinishedWithSuccess:NO]; - [pool drain]; - return; - } + for (NSString *accountName in accountsNames) { + for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) { + NSArray *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name]; + BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""]; + NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName] + objectForKey:pithosContainer.name]; + NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name]; + for (NSString *objectName in containerRemoteObjects) { + if (operation.isCancelled) { + operation.completionBlock = nil; + [self saveLocalState]; + [self syncOperationFinishedWithSuccess:NO]; + [pool drain]; + return; + } - ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName]; - NSString *localObjectName; - if ([object.name hasSuffix:@"/"]) - localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]]; - else - localObjectName = [NSString stringWithString:object.name]; - NSArray *pathComponents = [localObjectName pathComponents]; - if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) { - // Skip excluded directory object and its descendants, or root file object with same name - // Remove stored state if any - [containerStoredLocalObjectStates removeObjectForKey:object.name]; - continue; - } else if (containerExludeRootFiles && - ([pathComponents count] == 1) && - ![PithosUtilities isContentTypeDirectory:object.contentType]) { - // Skip root file object - // Remove stored state if any - [containerStoredLocalObjectStates removeObjectForKey:object.name]; - continue; + ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName]; + NSString *localObjectName; + if ([object.name hasSuffix:@"/"]) + localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]]; + else + localObjectName = [NSString stringWithString:object.name]; + NSArray *pathComponents = [localObjectName pathComponents]; + if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) { + // Skip excluded directory object and its descendants, or root file object with same name + // Remove stored state if any + [containerStoredLocalObjectStates removeObjectForKey:object.name]; + continue; + } else if (containerExludeRootFiles && + ([pathComponents count] == 1) && + ![PithosUtilities isContentTypeDirectory:object.contentType]) { + // Skip root file object + // Remove stored state if any + [containerStoredLocalObjectStates removeObjectForKey:object.name]; + continue; + } + if (![containerStoredLocalObjectStates objectForKey:object.name]) + [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name]; } - if (![containerStoredLocalObjectStates objectForKey:object.name]) - [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name]; + [self saveLocalState]; } - [self saveLocalState]; } if (operation.isCancelled) { @@ -1477,91 +1597,94 @@ // Stored states of local objects that have been deleted, // haven't been checked for legality (only existing local remote objects) // These should be identified and skipped - for (ASIPithosContainer *pithosContainer in pithosContainers) { - NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name]; - NSArray *containerExcludedDirectories = [containersDictionary objectForKey:pithosContainer.name]; - BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""]; - NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name]; - NSMutableDictionary *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; - } + for (NSString *accountName in accountsNames) { + for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) { + NSString *containerDirectoryPath = [self dirPathForAccount:accountName container:pithosContainer.name]; + NSArray *containerExcludedDirectories = [[accountsDictionary objectForKey:accountName] objectForKey:pithosContainer.name]; + BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""]; + NSMutableDictionary *containerStoredLocalObjectStates = [[storedLocalObjectStates objectForKey:accountName] + objectForKey:pithosContainer.name]; + NSMutableDictionary *containerRemoteObjects = [[remoteObjects objectForKey:accountName] objectForKey:pithosContainer.name]; + for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) { + if (operation.isCancelled) { + operation.completionBlock = nil; + [self syncOperationFinishedWithSuccess:NO]; + [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) { - // The stored state corresponds to a remote or deleted local object, that's why there is no current state - // In the latter case it must be checked for legality, which can be determined by its stored state - // If it existed locally, but was deleted, state.exists is true, - // else if the stored state is an empty state that was created due to the server object, state.exists is false - if (storedLocalObjectState.exists) { - NSString *localObjectName; - if ([object.name hasSuffix:@"/"]) - localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]]; - else - localObjectName = [NSString stringWithString:object.name]; - NSArray *pathComponents = [localObjectName pathComponents]; - if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) { - // Skip excluded directory object and its descendants, or root file object with same name - // Remove stored state - [containerStoredLocalObjectStates removeObjectForKey:object.name]; - [self saveLocalState]; - continue; - } else if (containerExludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) { - // Skip root file object - // Remove stored state - [containerStoredLocalObjectStates removeObjectForKey:object.name]; - [self saveLocalState]; - continue; + 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) { + // The stored state corresponds to a remote or deleted local object, that's why there is no current state + // In the latter case it must be checked for legality, which can be determined by its stored state + // If it existed locally, but was deleted, state.exists is true, + // else if the stored state is an empty state that was created due to the server object, state.exists is false + if (storedLocalObjectState.exists) { + NSString *localObjectName; + if ([object.name hasSuffix:@"/"]) + localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]]; + else + localObjectName = [NSString stringWithString:object.name]; + NSArray *pathComponents = [localObjectName pathComponents]; + if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) { + // Skip excluded directory object and its descendants, or root file object with same name + // Remove stored state + [containerStoredLocalObjectStates removeObjectForKey:object.name]; + [self saveLocalState]; + continue; + } else if (containerExludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) { + // Skip root file object + // Remove stored state + [containerStoredLocalObjectStates removeObjectForKey:object.name]; + [self saveLocalState]; + continue; + } } + // There is also the off case that a local object has been created in the meantime + // This call works in any case, existent or non-existent local object + currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath + blockHash:pithosContainer.blockHash + blockSize:pithosContainer.blockSize]; } - // There is also the off case that a local object has been created in the meantime - // This call works in any case, existent or non-existent local object - currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath - blockHash:pithosContainer.blockHash - blockSize:pithosContainer.blockSize]; - } - PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState]; - ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name]; - if (remoteObject) { - if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) { - remoteObjectState.isDirectory = YES; - } else { - remoteObjectState.hash = remoteObject.objectHash; + PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState]; + ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name]; + if (remoteObject) { + if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) { + remoteObjectState.isDirectory = YES; + } else { + remoteObjectState.hash = remoteObject.objectHash; + } } - } - BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState]; - BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState]; - 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]; - } + 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 + accountName:accountName pithosContainer:pithosContainer]; + } else if (!remoteObject && ![currentLocalObjectState exists]) { + // Server state hasn't changed + // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects + [containerStoredLocalObjectStates removeObjectForKey:objectName]; + [self saveLocalState]; + } } else { // Local state has changed if (!serverStateHasChanged) { @@ -1570,10 +1693,8 @@ object.contentType = @"application/directory"; else object.objectHash = currentLocalObjectState.hash; - [self updateServerStateWithCurrentState:currentLocalObjectState - object:object - localFilePath:filePath - pithosContainer:pithosContainer]; + [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath + accountName:accountName pithosContainer:pithosContainer]; } else { // Server state has also changed if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) { @@ -1599,17 +1720,17 @@ 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 ]; + [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name ]; firstButtonText = @"Delete local file"; secondButtonText = @"Upload file to server"; } else if (![currentLocalObjectState exists]) { informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.", - pithosContainer.name, object.name]; + [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name]; firstButtonText = @"Download file from server"; secondButtonText = @"Delete file on server"; } else { informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.", - pithosContainer.name, object.name]; + [self relativeDirPathForAccount:accountName container:pithosContainer.name], object.name]; firstButtonText = @"Keep server version"; secondButtonText = @"Keep local version"; } @@ -1625,22 +1746,22 @@ object.version = remoteObject.version; object.contentType = remoteObject.contentType; object.objectHash = remoteObject.objectHash; - [self updateLocalStateWithObject:object localFilePath:filePath pithosContainer:pithosContainer]; + [self updateLocalStateWithObject:object localFilePath:filePath + accountName:accountName pithosContainer:pithosContainer]; } if (choice == NSAlertSecondButtonReturn) { if (currentLocalObjectState.isDirectory) object.contentType = @"application/directory"; else object.objectHash = currentLocalObjectState.hash; - [self updateServerStateWithCurrentState:currentLocalObjectState - object:object - localFilePath:filePath - pithosContainer:pithosContainer]; + [self updateServerStateWithCurrentState:currentLocalObjectState object:object localFilePath:filePath + accountName:accountName pithosContainer:pithosContainer]; } } } } } } + } [self syncOperationFinishedWithSuccess:YES]; } else { [self listRequestFailed:containerRequest]; @@ -1694,6 +1815,7 @@ if (operation.isCancelled) { [self requestFailed:objectRequest]; } else if (objectRequest.responseStatusCode == 206) { + NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"]; ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"]; ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"]; NSFileManager *fileManager = [NSFileManager defaultManager]; @@ -1711,7 +1833,9 @@ return; } - PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name]; + PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName] + objectForKey:pithosContainer.name] + objectForKey:object.name]; if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) { NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"]; const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation]; @@ -1736,7 +1860,6 @@ storedState.tmpFilePath = tempFilePath; [self saveLocalState]; } - NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue]; NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath]; @@ -1749,7 +1872,9 @@ if (missingBlockIndex == NSNotFound) { NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"]; NSString *dirPath = [filePath stringByDeletingLastPathComponent]; - if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath pithosContainer:pithosContainer]) { + if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath + accountName:accountName + pithosContainer:pithosContainer]) { dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]]; @@ -1814,6 +1939,8 @@ object:object blockIndex:missingBlockIndex blockSize:pithosContainer.blockSize]; + if (![accountName isEqualToString:@""]) + [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos]; newObjectRequest.delegate = self; newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); @@ -1822,8 +1949,8 @@ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"]; [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){ [activityFacility updateActivity:activity - withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", - newObjectRequest.containerName, object.name, + withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", + [newObjectRequest.userInfo objectForKey:@"messagePrefix"], (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] totalBytes:activity.totalBytes currentBytes:(activity.currentBytes + size)]; @@ -1854,9 +1981,12 @@ if (newSyncRequested || syncLate || operation.isCancelled) { [self requestFailed:objectRequest]; } else { + NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"]; ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"]; ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"]; - PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name]; + PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName] + objectForKey:pithosContainer.name] + objectForKey:object.name]; if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes) [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes]; PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"]; @@ -1867,8 +1997,8 @@ NSUInteger missingBlockIndex = [missingBlocks firstIndex]; dispatch_async(dispatch_get_main_queue(), ^{ [activityFacility updateActivity:activity - withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", - pithosContainer.name, object.name, + withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", + [objectRequest.userInfo objectForKey:@"messagePrefix"], (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))] totalBytes:activity.totalBytes currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)]; @@ -1879,6 +2009,8 @@ object:object blockIndex:missingBlockIndex blockSize:pithosContainer.blockSize]; + if (![accountName isEqualToString:@""]) + [newObjectRequest setRequestUserFromDefaultTo:accountName withPithos:pithos]; newObjectRequest.delegate = self; newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); @@ -1889,8 +2021,8 @@ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"]; [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){ [activityFacility updateActivity:activity - withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", - newObjectRequest.containerName, object.name, + withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", + [newObjectRequest.userInfo objectForKey:@"messagePrefix"], (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] totalBytes:activity.totalBytes currentBytes:(activity.currentBytes + size)]; @@ -1910,7 +2042,8 @@ if (operation.isCancelled) { [self requestFailed:objectRequest]; } else if (objectRequest.responseStatusCode == 201) { - PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] + PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]] + objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]]; storedState.isDirectory = YES; [self saveLocalState]; @@ -1932,7 +2065,8 @@ if (operation.isCancelled) { [self requestFailed:objectRequest]; } else if (objectRequest.responseStatusCode == 201) { - [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] + [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]] + objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]]; [self saveLocalState]; dispatch_async(dispatch_get_main_queue(), ^{ @@ -1953,7 +2087,8 @@ if (operation.isCancelled) { [self requestFailed:objectRequest]; } else if (objectRequest.responseStatusCode == 204) { - [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] + [[[storedLocalObjectStates objectForKey:[objectRequest.userInfo objectForKey:@"accountName"]] + objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]]; [self saveLocalState]; dispatch_async(dispatch_get_main_queue(), ^{ @@ -1971,9 +2106,12 @@ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"]; NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url); + NSString *accountName = [objectRequest.userInfo objectForKey:@"accountName"]; ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"]; ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"]; - PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name]; + PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName] + objectForKey:pithosContainer.name] + objectForKey:object.name]; PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"]; NSUInteger totalBytes = activity.totalBytes; NSUInteger currentBytes = activity.currentBytes; @@ -2012,8 +2150,9 @@ 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))] + withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", + [objectRequest.userInfo objectForKey:@"messagePrefix"], + (100*(currentBytes + 0.0)/(totalBytes + 0.0))] totalBytes:totalBytes currentBytes:currentBytes]; }); @@ -2023,7 +2162,7 @@ blockSize:pithosContainer.blockSize forFile:[objectRequest.userInfo objectForKey:@"filePath"] missingBlockIndex:missingBlockIndex - sharingAccount:nil]; + sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)]; newContainerRequest.delegate = self; newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); @@ -2035,8 +2174,9 @@ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"]; [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){ [activityFacility updateActivity:activity - withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", - newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] + withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", + [newContainerRequest.userInfo objectForKey:@"messagePrefix"], + (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] totalBytes:activity.totalBytes currentBytes:(activity.currentBytes + size)]; }]; @@ -2055,6 +2195,7 @@ if (operation.isCancelled) { [self requestFailed:containerRequest]; } else if (containerRequest.responseStatusCode == 202) { + NSString *accountName = [containerRequest.userInfo objectForKey:@"accountName"]; ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"]; ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"]; PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"]; @@ -2074,7 +2215,7 @@ forFile:[containerRequest.userInfo objectForKey:@"filePath"] checkIfExists:NO hashes:&hashes - sharingAccount:nil]; + sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)]; newObjectRequest.delegate = self; newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); @@ -2093,7 +2234,7 @@ blockSize:pithosContainer.blockSize forFile:[containerRequest.userInfo objectForKey:@"filePath"] missingBlockIndex:missingBlockIndex - sharingAccount:nil]; + sharingAccount:([accountName isEqualToString:@""] ? nil : accountName)]; newContainerRequest.delegate = self; newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); @@ -2102,8 +2243,9 @@ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"]; [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){ [activityFacility updateActivity:activity - withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", - newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] + withMessage:[NSString stringWithFormat:@"%@ (%.0f%%)", + [newContainerRequest.userInfo objectForKey:@"messagePrefix"], + (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] totalBytes:activity.totalBytes currentBytes:(activity.currentBytes + size)]; }]; diff --git a/pithos-macos/PithosUtilities.h b/pithos-macos/PithosUtilities.h index 8f3de0e..efa2ed4 100644 --- a/pithos-macos/PithosUtilities.h +++ b/pithos-macos/PithosUtilities.h @@ -141,6 +141,7 @@ + (NSUInteger)bytesOfFile:(NSString *)filePath; + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error; + (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error; ++ (void)removeContentsAtPath:(NSString *)dirPath; + (BOOL)isContentTypeDirectory:(NSString *)contentType; + (BOOL)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount; diff --git a/pithos-macos/PithosUtilities.m b/pithos-macos/PithosUtilities.m index b3a3161..af97bb2 100644 --- a/pithos-macos/PithosUtilities.m +++ b/pithos-macos/PithosUtilities.m @@ -154,7 +154,7 @@ } for (ASIPithosObject *object in objects) { - if ([PithosUtilities isContentTypeDirectory:object.contentType]) { + if ([self isContentTypeDirectory:object.contentType]) { NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName]; subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]]; @@ -902,6 +902,36 @@ return YES; } +// Removes contents of a directory ++ (void)removeContentsAtPath:(NSString *)dirPath { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error = nil; + BOOL isDirectory; + if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory]) + return; + if (isDirectory) { + for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:dirPath error:&error]) { + if (error) { + [self fileActionFailedAlertWithTitle:@"Directory Contents Error" + message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", dirPath] + error:error]; + break; + } + NSString *subFilePath = [dirPath stringByAppendingPathComponent:subPath]; + if (![fileManager removeItemAtPath:subFilePath error:&error] || error) { + [self fileActionFailedAlertWithTitle:@"Remove File Error" + message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] + error:error]; + } + error = nil; + } + } else if (![fileManager removeItemAtPath:dirPath error:&error] || error) { + [self fileActionFailedAlertWithTitle:@"Remove File Error" + message:[NSString stringWithFormat:@"Cannot remove file at '%@'", dirPath] + error:error]; + } +} + // Returns if an object is a directory based on its content type + (BOOL)isContentTypeDirectory:(NSString *)contentType { return ([contentType isEqualToString:@"application/directory"] || @@ -921,7 +951,7 @@ [objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; [networkQueue go]; - [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:objectRequest]] waitUntilFinished:YES]; + [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:objectRequest]] waitUntilFinished:YES]; *error = [objectRequest error]; if (*error) { [self httpRequestErrorAlertWithRequest:objectRequest]; @@ -987,7 +1017,7 @@ [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; [networkQueue go]; - [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES]; + [networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:containerRequest]] waitUntilFinished:YES]; if ([containerRequest error]) { [self httpRequestErrorAlertWithRequest:containerRequest]; return nil;