1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-04 10:27:46 +01:00
Telegram/thirdparty/GDFileManager/GDFileManager.m
2015-10-01 19:19:52 +03:00

732 lines
32 KiB
Objective-C
Executable File

//
// GDFileManager.m
// GDFileManagerExample
//
// Created by Graham Dennis on 10/01/13.
// Copyright (c) 2013 Graham Dennis. All rights reserved.
//
#import "GDFileManager.h"
#import "GDFileManager_Private.h"
#import "GDDispatchUtilities.h"
#import "GDURLMetadata.h"
#import "GDURLMetadata_Private.h"
#import "GDFileManagerAlias_Private.h"
#import "GDRetainingMetadataCache.h"
#import "GDFileService.h"
#import "GDFileServiceManager.h"
#import "GDFileServiceSession.h"
#import "GDMultiMap.h"
NSString *const GDFileManagerErrorDomain = @"GDFileManagerErrorDomain";
static NSString *const GDFileManagerSharedCacheDidChange = @"GDFileManagerSharedCacheDidChange";
// Only expecting recent URLs to need caching
#define MAXIMUM_CANONICAL_URL_CACHE_SIZE 20
__attribute__((overloadable)) NSError *GDFileManagerError(NSInteger code, NSError *underlyingError)
{
NSDictionary *userInfo = nil;
if (underlyingError) {
userInfo = @{NSUnderlyingErrorKey: underlyingError};
}
return [[NSError alloc] initWithDomain:GDFileManagerErrorDomain code:code userInfo:userInfo];
}
__attribute__((overloadable)) NSError *GDFileManagerError(NSInteger code)
{
return GDFileManagerError(code, nil);
}
@interface GDFileManager ()
@property (nonatomic, strong) NSCache *directoryResultsCache;
@property (nonatomic, strong) NSCache *multimapDirectoryResultsCache;
@end
static NSOperationQueue *GDFileManagerLowPriorityOperationQueue = nil;
@implementation GDFileManager
+ (void)initialize
{
if (self == [GDFileManager class]) {
GDFileManagerLowPriorityOperationQueue = [NSOperationQueue new];
GDFileManagerLowPriorityOperationQueue.maxConcurrentOperationCount = 1;
}
}
+ (GDFileManager *)sharedManager
{
static GDFileManager *_sharedManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedManager = [GDFileManager new];
_sharedManager.sessionCache = nil;
_sharedManager.directoryResultsCache = nil;
_sharedManager.multimapDirectoryResultsCache = nil;
});
return _sharedManager;
}
+ (void)enqueueLowPriorityFileManagerOperation:(NSOperation *)operation
{
return [GDFileManagerLowPriorityOperationQueue addOperation:operation];
}
#pragma mark - Instance methods
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (id)init
{
return [self initWithFileServiceManager:nil];
}
- (id)initWithFileServiceManager:(GDFileServiceManager *)fileServiceManager
{
if ((self = [super init])) {
self.sessionCache = [GDRetainingMetadataCache new];
self.directoryResultsCache = [NSCache new];
self.multimapDirectoryResultsCache = [NSCache new];
_fileServiceManager = fileServiceManager ?: [GDFileServiceManager sharedManager];
self.defaultCachePolicy = GDFileManagerReloadRevalidatingCacheDataButReturnCacheIfOffline;
_operationQueue = [NSOperationQueue new];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(sharedMetadataCacheDidChange:)
name:GDFileManagerSharedCacheDidChange
object:nil];
}
return self;
}
- (GDFileService *)fileServiceForURLScheme:(NSString *)urlScheme
{
return [self.fileServiceManager fileServiceForURLScheme:urlScheme];
}
- (GDFileServiceSession *)fileServiceSessionForURL:(NSURL *)url
{
return [self.fileServiceManager fileServiceSessionForURL:url];
}
- (NSURL *)uniqueRootURLForURLScheme:(NSString *)scheme error:(NSError *__autoreleasing *)error
{
GDFileService *fileService = [self fileServiceForURLScheme:scheme];
NSArray *sessions = [fileService fileServiceSessions];
if ([sessions count] != 1) {
if (error) *error = GDFileManagerError(GDFileManagerRootNotUniqueError);
return nil;
}
NSURL *baseURL = [(GDFileServiceSession *)[sessions lastObject] baseURL];
return [baseURL URLByAppendingPathComponent:@"/"];
}
- (NSString *)sessionNameForURL:(NSURL *)url
{
GDFileService *service = [self fileServiceForURLScheme:[url scheme]];
return service.name;
}
- (BOOL)handleOpenURL:(NSURL *)url
{
for (GDFileService *fileService in [self.fileServiceManager allFileServices]) {
if ([fileService handleOpenURL:url])
return YES;
}
return NO;
}
#pragma mark - Client methods
- (void)getContentsOfDirectoryAtURL:(NSURL *)url success:(void (^)(NSArray *))success failure:(void (^)(NSError *))failure
{
return [self getContentsOfDirectoryAtURL:url cachePolicy:self.defaultCachePolicy success:success failure:failure];
}
- (void)getContentsOfDirectoryAtURL:(NSURL *)url cachePolicy:(GDFileManagerCachePolicy)cachePolicy
success:(void (^)(NSArray *contents))success failure:(void (^)(NSError *error))failure
{
if (!success) success = ^(__unused NSArray *contents){};
if (!failure) failure = ^(__unused NSError *error){};
GDFileServiceSession *session = [self.fileServiceManager fileServiceSessionForURL:url];
NSURL *canonicalURL = [session canonicalURLForURL:url];
if (!canonicalURL) {
return failure(GDFileManagerError(GDFileManagerNoCanonicalURLError));
}
id <GDURLMetadata> metadata = [self.sessionCache metadataForURL:canonicalURL];
if (metadata && ![metadata isDirectory]) {
return failure(GDFileManagerError(GDFileManagerNoCanonicalURLError));
}
NSArray *directoryContents = [self.directoryResultsCache objectForKey:url];
if (directoryContents && cachePolicy != GDFileManagerReloadRevalidatingCacheDataButReturnCacheIfOfflineAndIgnoreSessionCache) {
if ([directoryContents isKindOfClass:[NSArray class]])
return success(directoryContents);
else if ([directoryContents isKindOfClass:[NSError class]])
return failure((NSError *)directoryContents);
}
directoryContents = [self.sessionCache directoryContentsMetadataArrayForURL:canonicalURL];
if (directoryContents && cachePolicy != GDFileManagerReloadRevalidatingCacheDataButReturnCacheIfOfflineAndIgnoreSessionCache) {
directoryContents = [session clientMetadataArrayWithCachedMetadataArray:directoryContents parentURL:url cache:nil];
[self.directoryResultsCache setObject:directoryContents forKey:url];
return success(directoryContents);
}
if (directoryContents
&& (cachePolicy == GDFileManagerReturnCacheDataElseDontLoad || cachePolicy == GDFileManagerReturnCacheDataElseLoad)) {
directoryContents = [session clientMetadataArrayWithCachedMetadataArray:directoryContents parentURL:url cache:self.sessionCache];
[self.directoryResultsCache setObject:directoryContents forKey:url];
return success(directoryContents);
}
if (cachePolicy == GDFileManagerReturnCacheDataElseDontLoad) {
return failure(GDFileManagerError(GDFileManagerNoResultInCacheError));
}
NSLog(@"Getting contents of directory: %@", url);
[session getContentsOfDirectoryAtURL:url metadataCache:self.layeredCache
cachedMetadata:metadata cachedContents:directoryContents
success:^(NSArray *contents) {
[self.directoryResultsCache setObject:contents forKey:url];
success(contents);
} failure:^(NSError *error) {
if ([[error domain] isEqualToString:NSURLErrorDomain] && ![session isAvailable]) {
error = GDFileManagerError(GDFileManagerNetworkUnreachableError, error);
if (directoryContents
&& (cachePolicy == GDFileManagerReloadRevalidatingCacheDataButReturnCacheIfOffline
|| cachePolicy == GDFileManagerReloadRevalidatingCacheDataButReturnCacheIfOfflineAndIgnoreSessionCache)) {
NSArray *directoryContentsClientMetadata = [session clientMetadataArrayWithCachedMetadataArray:directoryContents parentURL:url cache:nil];
[self.directoryResultsCache setObject:directoryContentsClientMetadata forKey:url];
return success(directoryContentsClientMetadata);
}
} else {
[self.directoryResultsCache setObject:error forKey:url];
}
return failure(error);
}];
}
- (void)getMetadataForURL:(NSURL *)url
success:(void (^)(GDURLMetadata *metadata))success
failure:(void (^)(NSError *error))failure
{
[self getMetadataForURL:url cachePolicy:self.defaultCachePolicy success:success failure:failure];
}
- (void)getMetadataForURL:(NSURL *)url
cachePolicy:(GDFileManagerCachePolicy)cachePolicy
success:(void (^)(GDURLMetadata *))success
failure:(void (^)(NSError *))failure
{
if (!success) success = ^(__unused GDURLMetadata *metadata){};
if (!failure) failure = ^(__unused NSError *error){};
GDFileServiceSession *session = [self.fileServiceManager fileServiceSessionForURL:url];
NSURL *canonicalURL = [session canonicalURLForURL:url];
if (!canonicalURL) {
return failure(GDFileManagerError(GDFileManagerNoCanonicalURLError));
}
id <GDURLMetadata> metadata = [self.sessionCache metadataForURL:canonicalURL];
if (metadata && cachePolicy != GDFileManagerReloadRevalidatingCacheDataButReturnCacheIfOfflineAndIgnoreSessionCache) {
GDURLMetadata *urlMetadata = [self clientMetadataForURLMetadata:metadata clientURL:url fileServiceSession:session cache:nil];
return success(urlMetadata);
}
if (metadata
&& (cachePolicy == GDFileManagerReturnCacheDataElseDontLoad || cachePolicy == GDFileManagerReturnCacheDataElseLoad)) {
GDURLMetadata *urlMetadata = [self clientMetadataForURLMetadata:metadata clientURL:url fileServiceSession:session cache:self.sessionCache];
return success(urlMetadata);
}
if (cachePolicy == GDFileManagerReturnCacheDataElseDontLoad)
return failure(GDFileManagerError(GDFileManagerNoResultInCacheError));
NSLog(@"Getting metadata for URL: %@", url);
[session getMetadataForURL:url metadataCache:self.layeredCache cachedMetadata:metadata
success:success
failure:^(NSError *error) {
if ([[error domain] isEqualToString:NSURLErrorDomain] && ![session isAvailable]) {
error = GDFileManagerError(GDFileManagerNetworkUnreachableError, error);
if (metadata
&& (cachePolicy == GDFileManagerReloadRevalidatingCacheDataButReturnCacheIfOffline
|| cachePolicy == GDFileManagerReloadRevalidatingCacheDataButReturnCacheIfOfflineAndIgnoreSessionCache)) {
GDURLMetadata *urlMetadata = [self clientMetadataForURLMetadata:metadata clientURL:url fileServiceSession:session cache:nil];
return success(urlMetadata);
}
}
return failure(error);
}];
}
- (void)getLatestVersionIdentifierForURL:(NSURL *)url
success:(void (^)(NSString *versionIdentifier))success
failure:(void (^)(NSError *error))failure
{
[self getLatestVersionIdentifierForURL:url cachePolicy:self.defaultCachePolicy success:success failure:failure];
}
- (void)getLatestVersionIdentifierForURL:(NSURL *)url
cachePolicy:(GDFileManagerCachePolicy)cachePolicy
success:(void (^)(NSString *versionIdentifier))success
failure:(void (^)(NSError *error))failure
{
if (!success) success = ^(__unused NSString *versionIdentifier){};
if (!failure) failure = ^(__unused NSError *error){};
GDFileServiceSession *session = [self.fileServiceManager fileServiceSessionForURL:url];
NSURL *canonicalURL = [session canonicalURLForURL:url];
if (!canonicalURL) {
return failure(GDFileManagerError(GDFileManagerNoCanonicalURLError));
}
id <GDURLMetadata> metadata = [self.sessionCache metadataForURL:canonicalURL];
if (metadata.fileVersionIdentifier && cachePolicy != GDFileManagerReloadRevalidatingCacheDataButReturnCacheIfOfflineAndIgnoreSessionCache) {
return success(metadata.fileVersionIdentifier);
}
if (metadata.fileVersionIdentifier
&& (cachePolicy == GDFileManagerReturnCacheDataElseDontLoad || cachePolicy == GDFileManagerReturnCacheDataElseLoad)) {
return success(metadata.fileVersionIdentifier);
}
if (cachePolicy == GDFileManagerReturnCacheDataElseDontLoad)
return failure(GDFileManagerError(GDFileManagerNoResultInCacheError));
[session getLatestVersionIdentifierForURL:canonicalURL metadataCache:self.layeredCache cachedMetadata:metadata
success:success
failure:^(NSError *error) {
if ([[error domain] isEqualToString:NSURLErrorDomain] && ![session isAvailable]) {
error = GDFileManagerError(GDFileManagerNetworkUnreachableError, error);
if (metadata.fileVersionIdentifier
&& (cachePolicy == GDFileManagerReloadRevalidatingCacheDataButReturnCacheIfOffline
|| cachePolicy == GDFileManagerReloadRevalidatingCacheDataButReturnCacheIfOfflineAndIgnoreSessionCache)) {
return success(metadata.fileVersionIdentifier);
}
}
return failure(error);
}];
}
#pragma mark File Operations
- (void)deleteURL:(NSURL *)url success:(void (^)())success failure:(void (^)(NSError *error))failure
{
GDFileServiceSession *session = [self.fileServiceManager fileServiceSessionForURL:url];
if ([session isReadOnly]) {
if (failure) failure(GDFileManagerError(GDFileManagerServiceIsReadOnlyError));
return;
}
[session deleteURL:url success:^{
NSURL *parentURL = [session canonicalURLForURL:[url URLByDeletingLastPathComponent]];
[self.layeredCache removeMetadataForURL:url removeFromParent:parentURL];
[self.directoryResultsCache removeAllObjects];
if (success) success();
} failure:failure];
}
- (void)copyFileAtURL:(NSURL *)sourceURL toParentURL:(NSURL *)destinationParentURL name:(NSString *)name success:(void (^)(GDURLMetadata *metadata))success failure:(void (^)(NSError *))failure
{
NSParameterAssert(name);
GDFileServiceSession *sourceSession = [self.fileServiceManager fileServiceSessionForURL:sourceURL];
GDFileServiceSession *destinationSession = [self.fileServiceManager fileServiceSessionForURL:destinationParentURL];
if (![sourceSession isEqual:destinationSession]) {
if (failure) failure(GDFileManagerError(GDFileManagerFileSessionsNotIdenticalError));
return;
}
if ([sourceSession isReadOnly]) {
if (failure) failure(GDFileManagerError(GDFileManagerServiceIsReadOnlyError));
return;
}
[self getContentsOfDirectoryAtURL:destinationParentURL
success:^(NSArray *contents) {
NSString *nonConflictingFilename = [sourceSession filenameAvoidingConflictsWithExistingContents:contents preferredFilename:name];
[sourceSession copyFileAtURL:sourceURL toParentURL:destinationParentURL name:nonConflictingFilename success:^(GDURLMetadata *metadata){
[self cacheClientMetadata:metadata addToParent:YES];
if (success) success(metadata);
} failure:failure];
} failure:failure];
}
- (void)moveFileAtURL:(NSURL *)sourceURL toParentURL:(NSURL *)destinationParentURL name:(NSString *)name success:(void (^)(GDURLMetadata *metadata))success failure:(void (^)(NSError *))failure
{
NSParameterAssert(name);
GDFileServiceSession *sourceSession = [self.fileServiceManager fileServiceSessionForURL:sourceURL];
if ([sourceSession isReadOnly]) {
if (failure) failure(GDFileManagerError(GDFileManagerServiceIsReadOnlyError));
return;
}
if (!destinationParentURL) {
destinationParentURL = [sourceURL URLByDeletingLastPathComponent];
}
GDFileServiceSession *destinationSession = [self.fileServiceManager fileServiceSessionForURL:destinationParentURL];
if (![sourceSession isEqual:destinationSession]) {
if (failure) failure(GDFileManagerError(GDFileManagerFileSessionsNotIdenticalError));
return;
}
[self getContentsOfDirectoryAtURL:destinationParentURL
success:^(NSArray *contents) {
NSString *nonConflictingFilename = [sourceSession filenameAvoidingConflictsWithExistingContents:contents preferredFilename:name];
[sourceSession moveFileAtURL:sourceURL toParentURL:destinationParentURL name:nonConflictingFilename success:^(GDURLMetadata *metadata) {
NSURL *sourceParentURL = [sourceSession canonicalURLForURL:[sourceURL URLByDeletingLastPathComponent]];
[self.layeredCache removeMetadataForURL:sourceURL removeFromParent:sourceParentURL];
[self cacheClientMetadata:metadata addToParent:YES];
if (success) success(metadata);
} failure:failure];
} failure:failure];
}
- (GDURLMetadata *)clientMetadataForURLMetadata:(id <GDURLMetadata>)metadata clientURL:(NSURL *)clientURL
fileServiceSession:(GDFileServiceSession *)session cache:(id <GDMetadataCache>)cache
{
NSURL *canonicalURL = [session canonicalURLForURL:clientURL];
GDURLMetadata *clientMetadata = [[GDURLMetadata alloc] initWithURLMetadata:metadata clientURL:clientURL canonicalURL:canonicalURL];
if (![session shouldCacheResults])
[cache setMetadata:clientMetadata forURL:canonicalURL];
return clientMetadata;
}
- (void)cacheClientMetadata:(GDURLMetadata *)metadata
{
[self cacheClientMetadata:metadata addToParent:NO];
}
- (void)cacheClientMetadata:(GDURLMetadata *)metadata addToParent:(BOOL)addToParent
{
GDFileServiceSession *session = [self fileServiceSessionForURL:metadata.canonicalURL];
if (![session shouldCacheResults]) return;
NSURL *parentURL = nil;
if (addToParent) {
NSURL *nonCanonicalParentURL = [metadata.url URLByDeletingLastPathComponent];
parentURL = [session canonicalURLForURL:nonCanonicalParentURL];
[self.directoryResultsCache removeAllObjects];
}
[self.layeredCache setMetadata:metadata forURL:metadata.canonicalURL addToParent:parentURL];
}
- (void)cacheClientMetadataContents:(NSArray *)contents forURL:(NSURL *)url
{
if (!url) return;
GDFileServiceSession *session = [self fileServiceSessionForURL:url];
if (![session shouldCacheResults]) return;
NSMutableArray *childURLs = [NSMutableArray arrayWithCapacity:[contents count]];
for (GDURLMetadata *childMetadata in contents) {
[childURLs addObject:childMetadata.canonicalURL];
[self.layeredCache setMetadata:childMetadata forURL:childMetadata.canonicalURL];
}
[self.layeredCache setDirectoryContents:[childURLs copy] forURL:url];
}
#pragma mark - Item finding by path
- (void)findItemsMatchingPath:(NSString *)path relativeToURL:(NSURL *)baseURL
success:(void (^)(NSArray *matchingMetadata))success
failure:(void (^)(NSError *error))failure
{
GDFileServiceSession *session = [self.fileServiceManager fileServiceSessionForURL:baseURL];
NSArray *pathComponents = [[session normalisedPathForPath:path] pathComponents];
if ([pathComponents count] == 0) {
[self getMetadataForURL:baseURL
success:^(GDURLMetadata *metadata) {
if (success) {
success(@[metadata]);
}
} failure:failure];
return;
}
GDMultiMap *multimap = [self.multimapDirectoryResultsCache objectForKey:baseURL];
if (multimap) {
[self _findItemsMatchingPath:path relativeToURL:baseURL multimap:multimap success:success failure:failure];
} else {
[self getContentsOfDirectoryAtURL:baseURL
success:^(NSArray *contents) {
GDMultiMap *multimap = [GDMultiMap new];
for (GDURLMetadata *metadata in contents) {
NSString *normalisedFilename = [session normalisedPathForPath:metadata.filename];
[multimap addObject:metadata forKey:normalisedFilename];
}
[self.multimapDirectoryResultsCache setObject:multimap forKey:baseURL];
[self _findItemsMatchingPath:path relativeToURL:baseURL multimap:multimap success:success failure:failure];
} failure:failure];
}
}
- (void)_findItemsMatchingPath:(NSString *)path relativeToURL:(NSURL *)baseURL multimap:(GDMultiMap *)multimap
success:(void (^)(NSArray *matchingMetadata))success
failure:(void (^)(NSError *error))__unused failure
{
__block NSError *lastError = nil;
GDFileServiceSession *session = [self.fileServiceManager fileServiceSessionForURL:baseURL];
NSArray *pathComponents = [[session normalisedPathForPath:path] pathComponents];
NSString *firstPathComponent = pathComponents[0];
NSMutableArray *results = [NSMutableArray new];
BOOL isLastPathComponent = ([pathComponents count] == 1);
if (isLastPathComponent) {
for (GDURLMetadata *metadata in [multimap objectsForKey:firstPathComponent]) {
NSString *normalisedMetadataFilename = [session normalisedPathForPath:metadata.filename];
if ([normalisedMetadataFilename isEqualToString:firstPathComponent])
[results addObject:metadata];
}
if (success) success([results copy]);
} else {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t results_queue = dispatch_queue_create("me.grahamdennis.GDFileManager.findMatchingItems", DISPATCH_QUEUE_SERIAL);
for (GDURLMetadata *metadata in [multimap objectsForKey:firstPathComponent]) {
if ([metadata isDirectory]) {
// This wasn't the last element, but we found a matching directory. Recurse.
NSString *newPath = [NSString pathWithComponents:[pathComponents subarrayWithRange:NSMakeRange(1, [pathComponents count]-1)]];
dispatch_group_enter(group);
[self findItemsMatchingPath:newPath relativeToURL:metadata.url success:^(NSArray *matchingMetadata) {
dispatch_async(results_queue, ^{
[results addObjectsFromArray:matchingMetadata];
dispatch_group_leave(group);
});
} failure:^(NSError *error) {
dispatch_async(results_queue, ^{
lastError = error;
dispatch_group_leave(group);
});
}];
}
}
dispatch_group_notify(group, results_queue, ^{
if (success) success([results copy]);
#if !OS_OBJECT_USE_OBJC
dispatch_release(group);
dispatch_release(results_queue);
#endif
});
}
}
#pragma mark - Aliases
- (void)createAliasForURL:(NSURL *)url
success:(void (^)(GDFileManagerAlias *alias))success
failure:(void (^)(NSError *error))failure
{
if (!url) {
return failure(nil);
}
NSArray *pathComponents = [url pathComponents];
NSMutableArray *urlsNeedingMetadata = [NSMutableArray arrayWithObject:url];
for (NSInteger i=[pathComponents count]-1; i>0; i--) {
url = [url URLByDeletingLastPathComponent];
NSString *path = [url path];
if ([path isEqualToString:@""] || [path isEqualToString:@"/"]) {
} else
[urlsNeedingMetadata addObject:url];
}
NSMutableArray *metadataHeirarchy = [NSMutableArray arrayWithCapacity:[urlsNeedingMetadata count]];
__block NSError *lastError = nil;
AsyncSequentialEnumeration([urlsNeedingMetadata reverseObjectEnumerator], ^(NSURL *url, AsyncSequentialEnumerationContinuationBlock continuationBlock) {
[self getMetadataForURL:url success:^(GDURLMetadata *metadata) {
[metadataHeirarchy addObject:metadata];
continuationBlock(YES);
} failure:^(NSError *error) {
lastError = error;
[metadataHeirarchy removeAllObjects];
continuationBlock(YES);
}];
}, ^(__unused BOOL completed) {
if ([metadataHeirarchy count] == 0) {
if (failure) dispatch_async(dispatch_get_main_queue(),^{failure(lastError);});
return;
}
GDFileManagerAlias *alias = [[GDFileManagerAlias alloc] initWithMetadataHeirarchy:metadataHeirarchy];
if (success) dispatch_async(dispatch_get_main_queue(), ^{success(alias);});
});
}
- (void)resolveAlias:(GDFileManagerAlias *)alias
success:(void (^)(GDURLMetadata *metadata, GDFileManagerAlias *updatedAlias))success
failure:(void (^)(NSError *error))failure
{
if (!alias) {
if (failure) failure(nil);
return;
}
// The resolution scheme is that we prefer following the pathname over following the URL.
// That means we should start at the root and work our way up using pathname
// But when we fail, we need to try resolving based on the canonical URL, the trick there is then working out how to get parent information from the canonical URL.
// We might be able to get that information from the metadata, it might be obvious to the client, or we might have some information in our persistent metadata cache.
// If we need to resolve based on something else, then some services provide an objectID which will be valid if the file was moved, and may provide information about the
// parent folders. Let's first try to solve the problem
NSString *originalPath = [alias originalFilenamePath];
NSURL *originalURL = [alias originalURL];
GDFileServiceSession *session = [self.fileServiceManager fileServiceSessionForURL:originalURL];
if (!session) {
if (failure) failure(nil);
return;
}
NSURL *baseURL = [session baseURL];
NSURL *originalCanonicalURL = [session canonicalURLForURL:originalURL];
[self findItemsMatchingPath:originalPath relativeToURL:baseURL
success:^(NSArray *matchingMetadata) {
GDURLMetadata *bestMatch = nil;
// If we found a match, we just need to find the best
if ([matchingMetadata count] > 1) {
// Prefer the one that matches the canonical URL if possible.
for (GDURLMetadata *metadata in matchingMetadata) {
if ([metadata.canonicalURL isEqual:originalCanonicalURL]) {
bestMatch = metadata;
break;
}
}
}
// If none match the canonical URL, (or there's only one), take one
if (!bestMatch && [matchingMetadata count]) {
bestMatch = [matchingMetadata lastObject];
}
// We found a match!
if (bestMatch) {
if (success) success(bestMatch, nil);
return;
}
// The files have been renamed, try to locate based on canonical URLs which may be object IDs.
NSMutableArray *urlsToTry = [NSMutableArray arrayWithObject:originalURL];
// If the canonical URL is different to the original URL, try to create an alias for it if we can't
// create one for the original URL.
if (![originalCanonicalURL isEqual:originalURL])
[urlsToTry addObject:originalCanonicalURL];
__block NSError *lastError = nil;
AsyncSequentialEnumeration([urlsToTry objectEnumerator], ^(NSURL *url, AsyncSequentialEnumerationContinuationBlock continuationBlock) {
[self createAliasForURL:url
success:^(GDFileManagerAlias *alias) {
if (success) success(alias.originalMetadata, alias);
continuationBlock(NO);
} failure:^(NSError *error) {
lastError = error;
continuationBlock(YES);
}];
}, ^(BOOL completed) {
if (completed) {
if (failure) failure(lastError);
}
});
} failure:failure];
}
#pragma mark - Downloading
- (GDFileManagerDownloadOperation *)downloadOperationFromSourceURL:(NSURL *)url toLocalFileURL:(NSURL *)localURL
success:(void (^)(NSURL *, GDURLMetadata *))success
failure:(void (^)(NSError *))failure
{
GDFileManagerDownloadOperation *downloadOperation = [[GDFileManagerDownloadOperation alloc] initWithFileManager:self sourceURL:url success:success failure:failure];
downloadOperation.localDestinationFileURL = localURL;
return downloadOperation;
}
#pragma mark - Operation enqueueing
- (void)enqueueFileManagerOperation:(NSOperation *)operation
{
[self.operationQueue addOperation:operation];
}
- (void)enqueueLowPriorityFileManagerOperation:(NSOperation *)operation
{
[GDFileManager enqueueLowPriorityFileManagerOperation:operation];
}
#pragma mark - Cache management
- (void)resetSessionCache
{
[self.sessionCache reset];
}
- (void)sharedMetadataCacheDidChange:(NSNotification *)__unused notification
{
[self _updateLayeredCache];
}
- (void)setSessionCache:(id<GDMetadataCache>)sessionCache
{
_sessionCache = sessionCache;
[self _updateLayeredCache];
}
- (void)_updateLayeredCache
{
id <GDMetadataCache> sessionCache = self.sessionCache;
if (sessionCache) {
self.layeredCache = sessionCache;
} else {
self.layeredCache = nil;
}
}
@end