[self syncOperationFinishedWithSuccess:NO];
return;
}
- NSString *containerDirectoryPath;
for (ASIPithosContainer *pithosContainer in pithosContainers) {
- containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+ NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
error = nil;
if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] ||
return;
}
self.previousRemoteObjects = remoteObjects;
+ // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
if (operation.isCancelled) {
[self listRequestFailed:containerRequest];
withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
});
NSFileManager *fileManager = [NSFileManager defaultManager];
- NSError *error;
- NSMutableDictionary *subPaths = [NSMutableDictionary dictionaryWithCapacity:containersCount];
- NSUInteger subPathsCount = 0;
- NSString *containerDirectoryPath;
- NSArray *containerSubPaths;
- for (ASIPithosContainer *pithosContainer in pithosContainers) {
- error = nil;
- containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
- containerSubPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
- if (error) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error"
- message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath]
- error:error];
- [activityFacility startAndEndActivityWithType:PithosActivityOther
- message:@"Sync: Failed to read contents of sync directory"
- pithosAccount:pithosAccount];
- });
- // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccessfully
- [self syncOperationFinishedWithSuccess:NO];
- [pool drain];
- return;
- }
- [subPaths setObject:containerSubPaths forKey:pithosContainer.name];
- subPathsCount += [containerSubPaths count];
- }
- if (operation.isCancelled) {
- operation.completionBlock = nil;
- [self syncOperationFinishedWithSuccess:NO];
- [pool drain];
- return;
- }
-
- self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:subPathsCount];
- NSMutableDictionary *containerStoredLocalObjectStates;
+ // 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) {
- containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
- containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
- for (NSString *objectName in [subPaths objectForKey:pithosContainer.name]) {
+ NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+ NSMutableArray *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];
return;
}
- PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
- if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
- // New or modified existing local object, compute current state
- if (!storedLocalObjectState)
- // For new local object, also create empty stored state
- [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
- [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath
- blockHash:pithosContainer.blockHash
- blockSize:pithosContainer.blockSize]
- forKey:filePath];
- } else {
- // Local object hasn't changed, set stored state also to current
- [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
+ 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];
+ }
}
}
[self saveLocalState];
- }
+ }
if (operation.isCancelled) {
operation.completionBlock = nil;
return;
}
+ // Add an empty stored state for legal new remote objects since last sync
for (ASIPithosContainer *pithosContainer in pithosContainers) {
- containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
- for (NSString *objectName in [remoteObjects objectForKey:pithosContainer.name]) {
+ NSMutableArray *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];
return;
}
- if (![containerStoredLocalObjectStates objectForKey:objectName])
- [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
+ 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];
}
[self saveLocalState];
}
return;
}
- NSMutableDictionary *containerRemoteObjects;
+ // For each stored state compare with current and remote state
+ // Stored states of local objects that have been deleted,
+ // haven't been checked for legality (only existing local remote objects)
+ // These should be identified and skipped
for (ASIPithosContainer *pithosContainer in pithosContainers) {
- containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
- containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
- containerRemoteObjects = [remoteObjects objectForKey:pithosContainer.name];
+ NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
+ NSMutableArray *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;
PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
- if (!currentLocalObjectState)
+ 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];
-// if (currentLocalObjectState.isDirectory)
-// object.contentType = @"application/directory";
+ }
PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
if (remoteObject) {
if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
remoteObjectState.isDirectory = YES;
-// object.contentType = @"application/directory";
} else {
remoteObjectState.hash = remoteObject.objectHash;
}