@implementation PithosSyncDaemon
@synthesize directoryPath, accountsDictionary, skipHidden, pithos;
-@synthesize accountsNames, accountsPithosContainers;
+@synthesize userCatalog, accountsNames, accountsPithosContainers;
@synthesize lastCompletedSync, remoteObjects, previousRemoteObjects, storedLocalObjectStates, currentLocalObjectStates;
-@synthesize pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
+@synthesize userCatalogFilePath, pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
#pragma mark -
#pragma Object Lifecycle
else
[self resetLocalStateWithAll:NO];
+ // Load user catalog.
+ if ([[NSFileManager defaultManager] fileExistsAtPath:self.userCatalogFilePath])
+ self.userCatalog = [NSKeyedUnarchiver unarchiveObjectWithFile:self.userCatalogFilePath];
+ else
+ self.userCatalog = [NSMutableDictionary dictionary];
+ if (!userCatalog)
+ self.userCatalog = [NSMutableDictionary dictionary];
+
networkQueue = [[ASINetworkQueue alloc] init];
networkQueue.showAccurateProgress = YES;
networkQueue.shouldCancelAllRequestsOnFailure = NO;
// Remove containers that don't interest us anymore and save
if (!storedLocalObjectStates)
[self loadLocalState];
- for (NSString *accountName in storedLocalObjectStates) {
+ for (NSString *accountName in [storedLocalObjectStates allKeys]) {
NSMutableDictionary *accountStoredLocalObjectStates = [storedLocalObjectStates objectForKey:accountName];
NSDictionary *containersDictionary = [accountsDictionary objectForKey:accountName];
if (!containersDictionary) {
if (self.tempDownloadsDirPath) {
if ([accountName isEqualToString:@""]) {
for (NSString *containerName in accountStoredLocalObjectStates) {
- [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]];
+ [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]
+ andDirectory:YES];
}
} else {
[PithosUtilities removeContentsAtPath:[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared with me"]
- stringByAppendingPathComponent:accountName]];
+ stringByAppendingPathComponent:accountName]
+ andDirectory:YES];
}
}
[storedLocalObjectStates removeObjectForKey:accountName];
} else {
// Check the account's containers
- for (NSString *containerName in accountStoredLocalObjectStates) {
+ for (NSString *containerName in [accountStoredLocalObjectStates allKeys]) {
if (![containersDictionary objectForKey:containerName]) {
if ([accountName isEqualToString:@""])
- [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]];
+ [PithosUtilities removeContentsAtPath:[self.tempDownloadsDirPath stringByAppendingPathComponent:containerName]
+ andDirectory:YES];
else
[PithosUtilities removeContentsAtPath:[[[self.tempDownloadsDirPath stringByAppendingPathComponent:@"shared with me"]
stringByAppendingPathComponent:accountName]
- stringByAppendingPathComponent:containerName]];
+ stringByAppendingPathComponent:containerName]
+ andDirectory:YES];
[accountStoredLocalObjectStates removeObjectForKey:containerName];
}
}
#pragma mark -
#pragma mark Properties
+- (NSString *)userCatalogFilePath {
+ if (!userCatalogFilePath) {
+ userCatalogFilePath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
+ stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-SyncUserCatalog.archive",
+ pithosAccount.uniqueName]];
+ NSString *userCatalogFileDirPath = [userCatalogFilePath stringByDeletingLastPathComponent];
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ BOOL isDirectory;
+ BOOL fileExists = [fileManager fileExistsAtPath:userCatalogFileDirPath isDirectory:&isDirectory];
+ NSError *error = nil;
+ if (fileExists && !isDirectory)
+ [fileManager removeItemAtPath:userCatalogFileDirPath error:&error];
+ if (!error && !fileExists)
+ [fileManager createDirectoryAtPath:userCatalogFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
+ //if (error)
+ // userCatalogFilePath = nil;
+ // XXX create a dir using mktmps?
+ }
+ return [userCatalogFilePath copy];
+}
+
- (NSString *)pithosStateFilePath {
if (!pithosStateFilePath) {
pithosStateFilePath = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
return YES;
}
-- (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
- if ([accountName isEqualToString:@""])
- return [directoryPath stringByAppendingPathComponent:containerName];
- else
- return [[[directoryPath stringByAppendingPathComponent:@"shared with me"]
- stringByAppendingPathComponent:accountName]
- stringByAppendingPathComponent:containerName];
-}
-
- (NSString *)relativeDirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
- if ([accountName isEqualToString:@""])
+ if ([accountName isEqualToString:@""]) {
return containerName;
- else
- return [[@"shared with me" stringByAppendingPathComponent:accountName] stringByAppendingPathComponent:containerName];
+ } else {
+ NSMutableDictionary *displaynameDictionary = [userCatalog objectForKey:accountName];
+ if ([[displaynameDictionary objectForKey:@"suffix"] unsignedIntegerValue] == 0) {
+ return [[@"shared with me" stringByAppendingPathComponent:[displaynameDictionary objectForKey:@"displayname"]]
+ stringByAppendingPathComponent:containerName];
+ } else {
+ return [[@"shared with me" stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ (%@)",
+ [displaynameDictionary objectForKey:@"displayname"],
+ [displaynameDictionary objectForKey:@"suffix"]]]
+ stringByAppendingPathComponent:containerName];
+ }
+ }
+}
+
+- (NSString *)dirPathForAccount:(NSString *)accountName container:(NSString *)containerName {
+ return [directoryPath stringByAppendingPathComponent:[self relativeDirPathForAccount:accountName container:containerName]];
}
#pragma mark -
[self syncOperationFinishedWithSuccess:NO];
return;
}
+ // Update user catalog for accountsNames.
+ ASIPithosRequest *userCatalogRequest = [pithosAccount updateUserCatalogForForDisplaynames:nil UUIDs:accountsNames];
+ if (userCatalogRequest.error || ((userCatalogRequest.responseStatusCode != 200) && (userCatalogRequest.responseStatusCode != 404))) {
+ // Update failed try sync again later.
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+ for (NSString *accountName in accountsNames) {
+ if (![accountName isEqualToString:@""]) {
+ // If 404, displayname will be same as accountName.
+ NSString *displayname = [pithosAccount displaynameForUUID:accountName safe:YES];
+ NSMutableDictionary *displaynameDictionary = [userCatalog objectForKey:accountName];
+ if (!displaynameDictionary) {
+ // New entry in the user catalog. Determine suffix discriminator (0 is for no suffix).
+ NSNumber *suffix = [NSNumber numberWithInteger:0];
+ for (NSMutableDictionary *otherDisplaynameDictionary in [userCatalog objectEnumerator]) {
+ if ([[otherDisplaynameDictionary objectForKey:@"displayname"] isEqualToString:displayname] &&
+ [[otherDisplaynameDictionary objectForKey:@"suffix"] isGreaterThanOrEqualTo:suffix]) {
+ suffix = [NSNumber numberWithUnsignedInteger:([[otherDisplaynameDictionary objectForKey:@"suffix"] unsignedIntegerValue] + 1)];
+ }
+ }
+ [userCatalog setObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
+ displayname, @"displayname",
+ suffix, @"suffix",
+ nil]
+ forKey:accountName];
+ // Save user catalog.
+ [NSKeyedArchiver archiveRootObject:userCatalog toFile:self.userCatalogFilePath];
+ } else if (![[displaynameDictionary objectForKey:@"displayname"] isEqualToString:displayname]) {
+ // First determine new suffix.
+ NSNumber *suffix = [NSNumber numberWithInteger:0];
+ for (NSMutableDictionary *otherDisplaynameDictionary in [userCatalog objectEnumerator]) {
+ if ([[otherDisplaynameDictionary objectForKey:@"displayname"] isEqualToString:displayname] &&
+ [[otherDisplaynameDictionary objectForKey:@"suffix"] isGreaterThanOrEqualTo:suffix]) {
+ suffix = [NSNumber numberWithUnsignedInteger:([[otherDisplaynameDictionary objectForKey:@"suffix"] unsignedIntegerValue] + 1)];
+ }
+ }
+ // Compute old and new sync account dirPaths.
+ NSString *accountDirPath = (([[displaynameDictionary objectForKey:@"suffix"] unsignedIntegerValue] == 0) ?
+ [[directoryPath stringByAppendingPathComponent:@"shared with me"]
+ stringByAppendingPathComponent:[displaynameDictionary objectForKey:@"displayname"]] :
+ [[directoryPath stringByAppendingPathComponent:@"shared with me"]
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ (%@)",
+ [displaynameDictionary objectForKey:@"displayname"],
+ [displaynameDictionary objectForKey:@"suffix"]]]);
+ NSString *newAccountDirPath = (([suffix unsignedIntegerValue] == 0) ?
+ [[directoryPath stringByAppendingPathComponent:@"shared with me"]
+ stringByAppendingPathComponent:displayname] :
+ [[directoryPath stringByAppendingPathComponent:@"shared with me"]
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ (%@)",
+ displayname, suffix]]);
+ // Check if the old account sync directory exists.
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ BOOL isDirectory;
+ NSError *error = nil;
+ if ([fileManager fileExistsAtPath:accountDirPath isDirectory:&isDirectory] && isDirectory &&
+ (![fileManager moveItemAtPath:accountDirPath toPath:newAccountDirPath error:&error] || error)) {
+ // If so try to move it. We know that the containing directory "shared with me" exists.
+ [PithosUtilities fileActionFailedAlertWithTitle:@"Move Directory Error"
+ message:[NSString stringWithFormat:@"Cannot move directory at '%@' to '%@'",
+ accountDirPath, newAccountDirPath]
+ error:error];
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+ // Else the new account sync directory will be created afterwards.
+ // Fix filePaths of the stored local object states of the account.
+ for (NSMutableDictionary *containerStoredLocalObjectStates in [[storedLocalObjectStates objectForKey:accountName] objectEnumerator]) {
+ for (PithosLocalObjectState *storedLocalObjectState in [containerStoredLocalObjectStates objectEnumerator]) {
+ if ([storedLocalObjectState.filePath hasPrefix:accountDirPath]) {
+ storedLocalObjectState.filePath = [newAccountDirPath stringByAppendingString:
+ [storedLocalObjectState.filePath substringFromIndex:accountDirPath.length]];
+ }
+ }
+ }
+ [self saveLocalState];
+ // Finally update user catalog.
+ [userCatalog setObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
+ displayname, @"displayname",
+ suffix, @"suffix",
+ nil]
+ forKey:accountName];
+ // Save user catalog.
+ [NSKeyedArchiver archiveRootObject:userCatalog toFile:self.userCatalogFilePath];
+ }
+ // Else no change is required.
+ }
+ }
+
for (NSString *accountName in accountsNames) {
for (ASIPithosContainer *pithosContainer in [accountsPithosContainers objectForKey:accountName]) {
if (![self createSyncDirectory:[self dirPathForAccount:accountName container:pithosContainer.name]]) {
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDirectory;
NSError *error = nil;
- for (NSString *localFilePath in currentLocalObjectStates) {
+ for (NSString *localFilePath in [currentLocalObjectStates allKeys]) {
localState = [currentLocalObjectStates objectForKey:localFilePath];
if (!localState.isDirectory && [hash isEqualToString:localState.hash] &&
[fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
NSError *error;
PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
- NSString *downloadsDirPath = self.tempDownloadsDirPath;
- if (!downloadsDirPath) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [activityFacility endActivity:activity
- withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
- });
- [self syncOperationFinishedWithSuccess:NO];
- return;
- }
-
- PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
+ PithosLocalObjectState *storedState = [[[storedLocalObjectStates objectForKey:accountName]
objectForKey:pithosContainer.name]
objectForKey:object.name];
if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
+ NSString *downloadsDirPath = self.tempDownloadsDirPath;
+ if (!downloadsDirPath) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+ if ([accountName isEqualToString:@""]) {
+ downloadsDirPath = [downloadsDirPath stringByAppendingPathComponent:pithosContainer.name];
+ } else {
+ downloadsDirPath = [[[downloadsDirPath stringByAppendingPathComponent:@"shared with me"]
+ stringByAppendingPathComponent:accountName]
+ stringByAppendingPathComponent:pithosContainer.name];
+ }
+ BOOL isDirectory;
+ error = nil;
+ if (![fileManager fileExistsAtPath:downloadsDirPath isDirectory:&isDirectory]) {
+ if (![fileManager createDirectoryAtPath:downloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+ } else if (!isDirectory) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ [self syncOperationFinishedWithSuccess:NO];
+ return;
+ }
+
NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);