diff --git a/external/PocketSVG b/external/PocketSVG index fc4c0afe7..039399f26 160000 --- a/external/PocketSVG +++ b/external/PocketSVG @@ -1 +1 @@ -Subproject commit fc4c0afe7a6e151a2c08f4f888b0dc5a3424a5d7 +Subproject commit 039399f26c416a8bf80e9b650445eefc85280c36 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 4141ca8b1..6a802090e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -36,7 +36,9 @@ platform :ios do export_options: { method: "ad-hoc", provisioningProfiles: { - "com.owncloud.ios-app" => "match AdHoc com.owncloud.ios-app" + "com.owncloud.ios-app" => "match AdHoc com.owncloud.ios-app", + "com.owncloud.ios-app.ownCloud-File-Provider" => "match AdHoc com.owncloud.ios-app.ownCloud-File-Provider", + "com.owncloud.ios-app.ownCloud-File-ProviderUI" => "match AdHoc com.owncloud.ios-app.ownCloud-File-ProviderUI" #Add more Provisioning Profiles when extensions are added } } diff --git a/ios-sdk b/ios-sdk index 1a82aa086..b31f81384 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 1a82aa08669827584d3a4b3b3dc160f7a7cc1106 +Subproject commit b31f813843203fbaf1db5928a553e7989d0db923 diff --git a/ownCloud File Provider/FileProviderEnumerator.h b/ownCloud File Provider/FileProviderEnumerator.h new file mode 100644 index 000000000..adb54f7a8 --- /dev/null +++ b/ownCloud File Provider/FileProviderEnumerator.h @@ -0,0 +1,48 @@ +// +// FileProviderEnumerator.h +// ownCloud File Provider +// +// Created by Felix Schwarz on 07.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import +#import +#import "FileProviderEnumeratorObserver.h" + +@class FileProviderExtension; + +@interface FileProviderEnumerator : NSObject +{ + __weak FileProviderExtension *_fileProviderExtension; + + OCCore *_core; + OCBookmark *_bookmark; + NSFileProviderItemIdentifier _enumeratedItemIdentifier; + + OCQuery *_query; + + NSMutableArray *_enumerationObservers; + NSMutableArray *_changeObservers; + + BOOL _invalidated; +} + +@property(weak) FileProviderExtension *fileProviderExtension; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithBookmark:(OCBookmark *)bookmark enumeratedItemIdentifier:(NSFileProviderItemIdentifier)enumeratedItemIdentifier; + +@property (nonatomic, readonly, strong) NSFileProviderItemIdentifier enumeratedItemIdentifier; + +@end diff --git a/ownCloud File Provider/FileProviderEnumerator.m b/ownCloud File Provider/FileProviderEnumerator.m new file mode 100644 index 000000000..1e0c0d197 --- /dev/null +++ b/ownCloud File Provider/FileProviderEnumerator.m @@ -0,0 +1,379 @@ +// +// FileProviderEnumerator.m +// ownCloud File Provider +// +// Created by Felix Schwarz on 07.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "FileProviderEnumerator.h" +#import "FileProviderExtension.h" +#import "OCCore+FileProviderTools.h" +#import "OCItem+FileProviderItem.h" +#import "NSNumber+OCSyncAnchorData.h" + +@implementation FileProviderEnumerator + +@synthesize fileProviderExtension = _fileProviderExtension; + +- (instancetype)initWithBookmark:(OCBookmark *)bookmark enumeratedItemIdentifier:(NSFileProviderItemIdentifier)enumeratedItemIdentifier +{ + if ((self = [super init]) != nil) + { + _bookmark = bookmark; + _enumeratedItemIdentifier = enumeratedItemIdentifier; + + _enumerationObservers = [NSMutableArray new]; + _changeObservers = [NSMutableArray new]; + } + + return (self); +} + +- (void)invalidate +{ + OCLogDebug(@"##### INVALIDATE %@", _query.queryPath); + + if (_core != nil) + { + [_core stopQuery:_query]; + + [[OCCoreManager sharedCoreManager] returnCoreForBookmark:_bookmark completionHandler:nil]; + + _core = nil; + } + + _query.delegate = nil; + _query = nil; +} + +- (void)enumerateItemsForObserver:(id)observer startingAtPage:(NSFileProviderPage)page +{ + FileProviderEnumeratorObserver *enumerationObserver = [FileProviderEnumeratorObserver new]; + + OCLogDebug(@"##### Enumerate ITEMS for observer: %@ fromPage: %@", observer, page); + + enumerationObserver.enumerationObserver = observer; + enumerationObserver.enumerationStartPage = page; + enumerationObserver.didProvideInitialItems = NO; + + @synchronized(self) + { + [_enumerationObservers addObject:enumerationObserver]; + } + + [self _startQuery]; +} + +- (void)_finishAllEnumeratorsWithError:(NSError *)error +{ + @synchronized(self) + { + for (FileProviderEnumeratorObserver *observer in _enumerationObservers) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [observer.enumerationObserver finishEnumeratingWithError:error]; + }); + } + [_enumerationObservers removeAllObjects]; + + for (FileProviderEnumeratorObserver *observer in _changeObservers) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [observer.changeObserver finishEnumeratingWithError:error]; + }); + } + [_changeObservers removeAllObjects]; + } +} + +- (void)_startQuery +{ + OCLogDebug(@"##### Starting query.."); + + if ((_core == nil) && (_query == nil)) + { + _core = [[OCCoreManager sharedCoreManager] requestCoreForBookmark:_bookmark completionHandler:^(OCCore *core, NSError *error) { + self->_core = core; + + if (error != nil) + { + // TODO: Report error as NSFileProviderErrorServerUnreachable or NSFileProviderErrorNotAuthenticated, depending on what the underlying error is + [self _finishAllEnumeratorsWithError:error]; + } + else + { + // Create and add query + __block OCPath queryPath = nil; + + if ([self->_enumeratedItemIdentifier isEqualToString:NSFileProviderRootContainerItemIdentifier]) + { + queryPath = @"/"; + } + else + { + NSError *error = nil; + OCItem *item; + + if ((item = [core synchronousRetrieveItemFromDatabaseForFileID:self->_enumeratedItemIdentifier syncAnchor:NULL error:&error]) != nil) + { + if (item.type == OCItemTypeCollection) + { + queryPath = item.path; + } +// +// if (item.type == OCItemTypeFile) +// { +// OCLogDebug(@"Observe item: %@", item); +// +// [observer didEnumerateItems:@[ item ]]; +// [observer finishEnumeratingUpToPage:nil]; +// return; +// } + } + } + + if (queryPath == nil) + { + // Item not found or not a directory + NSError *enumerationError = [NSError errorWithDomain:NSFileProviderErrorDomain code:NSFileProviderErrorNoSuchItem userInfo:nil]; + + [self _finishAllEnumeratorsWithError:enumerationError]; + return; + } + else + { + // Start query + self->_query = [OCQuery queryForPath:queryPath]; + self->_query.delegate = self; + + @synchronized(self) + { + if ([self->_enumerationObservers.lastObject.enumerationStartPage isEqual:NSFileProviderInitialPageSortedByDate]) + { + self->_query.sortComparator = ^NSComparisonResult(OCItem *item1, OCItem *item2) { + return ([item1.lastModified compare:item2.lastModified]); + }; + } + + if ([self->_enumerationObservers.lastObject.enumerationStartPage isEqual:NSFileProviderInitialPageSortedByName]) + { + self->_query.sortComparator = ^NSComparisonResult(OCItem *item1, OCItem *item2) { + return ([item1.name compare:item2.name]); + }; + } + } + + OCLogDebug(@"##### START QUERY FOR %@", self->_query.queryPath); + + [core startQuery:self->_query]; + } + } + }]; + } + else + { + OCLogDebug(@"Query already running.."); + + if (_query != nil) + { + @synchronized(self) + { + if (_enumerationObservers.count!=0) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self provideItemsForEnumerationObserverFromQuery:self->_query]; + }); + } + + if (_changeObservers.count!=0) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self provideItemsForChangeObserverFromQuery:self->_query]; + }); + } + } + } + } + + /* TODO: + - inspect the page to determine whether this is an initial or a follow-up request + + If this is an enumerator for a directory, the root container or all directories: + - perform a server request to fetch directory contents + If this is an enumerator for the active set: + - perform a server request to update your local database + - fetch the active set from your local database + + - inform the observer about the items returned by the server (possibly multiple times) + - inform the observer that you are finished with this page + */ +} + +- (void)provideItemsForEnumerationObserverFromQuery:(OCQuery *)query +{ + if ((query.state == OCQueryStateContentsFromCache) || ((query.state == OCQueryStateWaitingForServerReply) && (query.queryResults.count > 0)) || (query.state == OCQueryStateIdle)) + { + @synchronized(self) + { + NSMutableArray *removeObservers = [NSMutableArray new]; + + for (FileProviderEnumeratorObserver *observer in _enumerationObservers) + { + if (observer.enumerationObserver != nil) + { + if (!observer.didProvideInitialItems) + { + OCLogDebug(@"##### PROVIDE ITEMS TO %ld --ENUMERATION-- OBSERVER %@ FOR %@: %@", _enumerationObservers.count, observer.enumerationObserver, query.queryPath, query.queryResults); + + observer.didProvideInitialItems = YES; + + if (query.queryResults != nil) + { + [observer.enumerationObserver didEnumerateItems:query.queryResults]; + } + + [observer.enumerationObserver finishEnumeratingUpToPage:nil]; + + [removeObservers addObject:observer]; + } + } + } + + [_enumerationObservers removeObjectsInArray:removeObservers]; + } + } +} + +- (void)provideItemsForChangeObserverFromQuery:(OCQuery *)query +{ + @synchronized(self) + { + if (_changeObservers.count > 0) + { + OCLogDebug(@"##### PROVIDE ITEMS TO %d --CHANGE-- OBSERVER FOR %@: %@", _changeObservers.count, query.queryPath, query.queryResults); + + for (FileProviderEnumeratorObserver *observer in _changeObservers) + { + [observer.changeObserver didUpdateItems:query.queryResults]; + [observer.changeObserver finishEnumeratingChangesUpToSyncAnchor:[_core.latestSyncAnchor syncAnchorData] moreComing:NO]; + } + + [_changeObservers removeAllObjects]; + } + } +} + +- (void)queryHasChangesAvailable:(OCQuery *)query +{ + OCLogDebug(@"##### Query for %@ has changes. Query state: %lu", query.queryPath, (unsigned long)query.state); + + if ((query.state == OCQueryStateContentsFromCache) || ((query.state == OCQueryStateWaitingForServerReply) && (query.queryResults.count > 0)) || (query.state == OCQueryStateIdle)) + { + dispatch_async(dispatch_get_main_queue(), ^{ + @synchronized(self) + { + if (self->_enumerationObservers.count > 0) + { + [self provideItemsForEnumerationObserverFromQuery:query]; + } + + if (self->_changeObservers.count > 0) + { + [self provideItemsForChangeObserverFromQuery:query]; + } + } + }); + } +} + +- (void)query:(OCQuery *)query failedWithError:(NSError *)error +{ + OCLogDebug(@"### Query failed with error: %@", error); +} + +- (void)enumerateChangesForObserver:(id)observer fromSyncAnchor:(NSFileProviderSyncAnchor)syncAnchor +{ + OCLogDebug(@"##### Enumerate CHANGES for observer: %@ fromSyncAnchor: %@", observer, syncAnchor); + + if (syncAnchor != nil) + { + /** Apple: + If the enumeration fails with NSFileProviderErrorSyncAnchorExpired, we will + drop all cached data and start the enumeration over starting with sync anchor + nil. + */ + dispatch_async(dispatch_get_main_queue(), ^{ + if ([syncAnchor isEqual:[self->_core.latestSyncAnchor syncAnchorData]]) + { + OCLogDebug(@"##### END(LATEST) Enumerate CHANGES for observer: %@ fromSyncAnchor: %@", observer, syncAnchor); + [observer finishEnumeratingChangesUpToSyncAnchor:syncAnchor moreComing:NO]; + } + else + { + OCLogDebug(@"##### END(EXPIRED) Enumerate CHANGES for observer: %@ fromSyncAnchor: %@", observer, syncAnchor); + [observer finishEnumeratingWithError:[NSError errorWithDomain:NSFileProviderErrorDomain code:NSFileProviderErrorSyncAnchorExpired userInfo:nil]]; + } + }); + } + else + { + /** Apple: + "If anchor is nil, then the system is enumerating from scratch: the system wants + to receives changes to reconstruct the list of items in this enumeration as if + starting from an empty list." + */ + + FileProviderEnumeratorObserver *enumerationObserver = [FileProviderEnumeratorObserver new]; + + enumerationObserver.changeObserver = observer; + enumerationObserver.changesFromSyncAnchor = syncAnchor; + + @synchronized(self) + { + [_enumerationObservers addObject:enumerationObserver]; + } + + [self _startQuery]; + } +} + +- (void)currentSyncAnchorWithCompletionHandler:(void (^)(NSFileProviderSyncAnchor _Nullable))completionHandler +{ + OCLogDebug(@"#### Request current sync anchor"); + + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler([self->_core.latestSyncAnchor syncAnchorData]); + }); +} + +// - (void)enumerateChangesForObserver:(id)observer fromSyncAnchor:(NSFileProviderSyncAnchor)anchor +// { + /* TODO: + - query the server for updates since the passed-in sync anchor + + If this is an enumerator for the active set: + - note the changes in your local database + + - inform the observer about item deletions and updates (modifications + insertions) + - inform the observer when you have finished enumerating up to a subsequent sync anchor + */ + /** + If the enumeration fails with NSFileProviderErrorSyncAnchorExpired, we will + drop all cached data and start the enumeration over starting with sync anchor + nil. + */ + // - (void)finishEnumeratingWithError:(NSError *)error; +// } + +@end diff --git a/ownCloud File Provider/FileProviderEnumeratorObserver.h b/ownCloud File Provider/FileProviderEnumeratorObserver.h new file mode 100644 index 000000000..baefa3e91 --- /dev/null +++ b/ownCloud File Provider/FileProviderEnumeratorObserver.h @@ -0,0 +1,33 @@ +// +// FileProviderEnumeratorObserver.h +// ownCloud File Provider +// +// Created by Felix Schwarz on 18.07.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import +#import +#import + +@interface FileProviderEnumeratorObserver : NSObject + +@property(strong) id enumerationObserver; +@property(strong) NSFileProviderPage enumerationStartPage; +@property(assign) BOOL didProvideInitialItems; + +@property(strong) id changeObserver; +@property(strong) NSFileProviderSyncAnchor changesFromSyncAnchor; +@property(strong) OCQuery *changeQuery; + +@end diff --git a/ownCloud File Provider/FileProviderEnumeratorObserver.m b/ownCloud File Provider/FileProviderEnumeratorObserver.m new file mode 100644 index 000000000..10de87fc7 --- /dev/null +++ b/ownCloud File Provider/FileProviderEnumeratorObserver.m @@ -0,0 +1,23 @@ +// +// FileProviderEnumeratorObserver.m +// ownCloud File Provider +// +// Created by Felix Schwarz on 18.07.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "FileProviderEnumeratorObserver.h" + +@implementation FileProviderEnumeratorObserver + +@end diff --git a/ownCloud File Provider/FileProviderExtension.h b/ownCloud File Provider/FileProviderExtension.h new file mode 100644 index 000000000..82d445dda --- /dev/null +++ b/ownCloud File Provider/FileProviderExtension.h @@ -0,0 +1,32 @@ +// +// FileProviderExtension.h +// ownCloud File Provider +// +// Created by Felix Schwarz on 07.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import +#import + +@interface FileProviderExtension : NSFileProviderExtension +{ + OCCore *_core; + OCBookmark *_bookmark; +} + +@property(strong,nonatomic,readonly) OCCore *core; +@property(strong,nonatomic,readonly) OCBookmark *bookmark; + +@end + diff --git a/ownCloud File Provider/FileProviderExtension.m b/ownCloud File Provider/FileProviderExtension.m new file mode 100644 index 000000000..a91c9be2a --- /dev/null +++ b/ownCloud File Provider/FileProviderExtension.m @@ -0,0 +1,715 @@ +// +// FileProviderExtension.m +// ownCloud File Provider +// +// Created by Felix Schwarz on 07.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import + +#import "FileProviderExtension.h" +#import "FileProviderEnumerator.h" +#import "OCItem+FileProviderItem.h" +#import "FileProviderExtensionThumbnailRequest.h" + +@interface FileProviderExtension () + +@property (nonatomic, readonly, strong) NSFileManager *fileManager; + +@end + +@implementation FileProviderExtension + +@synthesize core; +@synthesize bookmark; + +- (instancetype)init +{ + NSDictionary *bundleInfoDict = [[NSBundle bundleForClass:[FileProviderExtension class]] infoDictionary]; + + OCCoreManager.sharedCoreManager.memoryConfiguration = OCCoreMemoryConfigurationMinimum; + + OCAppIdentity.sharedAppIdentity.appIdentifierPrefix = bundleInfoDict[@"OCAppIdentifierPrefix"]; + OCAppIdentity.sharedAppIdentity.keychainAccessGroupIdentifier = bundleInfoDict[@"OCKeychainAccessGroupIdentifier"]; + OCAppIdentity.sharedAppIdentity.appGroupIdentifier = bundleInfoDict[@"OCAppGroupIdentifier"]; + + if (self = [super init]) { + _fileManager = [[NSFileManager alloc] init]; + } + + return self; +} + +- (void)dealloc +{ + if (_core != nil) + { + [[OCCoreManager sharedCoreManager] returnCoreForBookmark:self.bookmark completionHandler:nil]; + } +} + +#pragma mark - ItemIdentifier & URL lookup +- (NSFileProviderItem)itemForIdentifier:(NSFileProviderItemIdentifier)identifier error:(NSError *__autoreleasing _Nullable *)outError +{ + __block NSFileProviderItem item = nil; + __block NSError *returnError = nil; + + OCSyncExec(itemRetrieval, { + // Resolve the given identifier to a record in the model + if ([identifier isEqual:NSFileProviderRootContainerItemIdentifier]) + { + // Root item + [self.core.vault.database retrieveCacheItemsAtPath:@"/" itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + item = items.firstObject; + returnError = error; + + OCSyncExecDone(itemRetrieval); + }]; + } + else + { + // Other item + [self.core retrieveItemFromDatabaseForFileID:(OCFileID)identifier completionHandler:^(NSError *error, OCSyncAnchor syncAnchor, OCItem *itemFromDatabase) { + item = itemFromDatabase; + returnError = error; + + OCSyncExecDone(itemRetrieval); + }]; + } + }); + + // OCLogDebug(@"[FP] -itemForIdentifier:error: %@ => %@", identifier, item); + + if ((item == nil) && (returnError == nil)) + { + returnError = [NSError fileProviderErrorForNonExistentItemWithIdentifier:identifier]; + } + + if (outError != NULL) + { + *outError = returnError; + } + + return item; +} + +- (NSURL *)URLForItemWithPersistentIdentifier:(NSFileProviderItemIdentifier)identifier +{ + OCItem *item; + NSURL *url = nil; + + if ((item = (OCItem *)[self itemForIdentifier:identifier error:NULL]) != nil) + { + url = [self.core localURLForItem:item]; + } + + // OCLogDebug(@"[FP] -URLForItemWithPersistentIdentifier: %@ => %@", identifier, url); + + return (url); + + /* + // resolve the given identifier to a file on disk + + // in this implementation, all paths are structured as // + NSFileProviderManager *manager = [NSFileProviderManager defaultManager]; + NSURL *perItemDirectory = [manager.documentStorageURL URLByAppendingPathComponent:identifier isDirectory:YES]; + + return [perItemDirectory URLByAppendingPathComponent:item.filename isDirectory:NO]; + */ +} + +- (NSFileProviderItemIdentifier)persistentIdentifierForItemAtURL:(NSURL *)url +{ + // resolve the given URL to a persistent identifier using a database + NSArray *pathComponents = [url pathComponents]; + + // exploit the fact that the path structure has been defined as + // // above + NSParameterAssert(pathComponents.count > 2); + + // OCLogDebug(@"[FP] -persistentIdentifierForItemAtURL: %@", (pathComponents[pathComponents.count - 2])); + + return pathComponents[pathComponents.count - 2]; +} + +- (void)providePlaceholderAtURL:(NSURL *)url completionHandler:(void (^)(NSError * _Nullable))completionHandler +{ + NSFileProviderItemIdentifier identifier = [self persistentIdentifierForItemAtURL:url]; + if (!identifier) { + completionHandler([NSError errorWithDomain:NSFileProviderErrorDomain code:NSFileProviderErrorNoSuchItem userInfo:nil]); + return; + } + + NSError *error = nil; + NSFileProviderItem fileProviderItem = [self itemForIdentifier:identifier error:&error]; + if (!fileProviderItem) { + completionHandler(error); + return; + } + NSURL *placeholderURL = [NSFileProviderManager placeholderURLForURL:url]; + + [[NSFileManager defaultManager] createDirectoryAtURL:url.URLByDeletingLastPathComponent withIntermediateDirectories:YES attributes:nil error:NULL]; + + if (![NSFileProviderManager writePlaceholderAtURL:placeholderURL withMetadata:fileProviderItem error:&error]) { + completionHandler(error); + return; + } + completionHandler(nil); +} + +- (void)startProvidingItemAtURL:(NSURL *)provideAtURL completionHandler:(void (^)(NSError * _Nullable))completionHandler +{ + NSError *error = nil; + NSFileProviderItemIdentifier itemIdentifier = nil; + NSFileProviderItem item = nil; + + if ((itemIdentifier = [self persistentIdentifierForItemAtURL:provideAtURL]) != nil) + { + if ((item = [self itemForIdentifier:itemIdentifier error:&error]) != nil) + { + [self.core downloadItem:(OCItem *)item options:nil resultHandler:^(NSError *error, OCCore *core, OCItem *item, OCFile *file) { + OCLogDebug(@"[FP] Starting to provide file:\nPAU: %@\nFURL: %@\nID: %@\nErr: %@\nlocalRelativePath: %@", provideAtURL, file.url, item.itemIdentifier, error, item.localRelativePath); + + if ([error isOCErrorWithCode:OCErrorCancelled]) + { + // If we provide a real error here, the Files app will show an error "File not found". + error = nil; + } + + completionHandler(error); + }]; + + return; + } + } + + completionHandler([NSError errorWithDomain:NSCocoaErrorDomain code:NSFeatureUnsupportedError userInfo:@{}]); + + // ### Apple template comments: ### + + // Should ensure that the actual file is in the position returned by URLForItemWithIdentifier:, then call the completion handler + + /* TODO: + This is one of the main entry points of the file provider. We need to check whether the file already exists on disk, + whether we know of a more recent version of the file, and implement a policy for these cases. Pseudocode: + + if (!fileOnDisk) { + downloadRemoteFile(); + callCompletion(downloadErrorOrNil); + } else if (fileIsCurrent) { + callCompletion(nil); + } else { + if (localFileHasChanges) { + // in this case, a version of the file is on disk, but we know of a more recent version + // we need to implement a strategy to resolve this conflict + moveLocalFileAside(); + scheduleUploadOfLocalFile(); + downloadRemoteFile(); + callCompletion(downloadErrorOrNil); + } else { + downloadRemoteFile(); + callCompletion(downloadErrorOrNil); + } + } + */ +} + + +- (void)itemChangedAtURL:(NSURL *)changedItemURL +{ + NSError *error = nil; + NSFileProviderItemIdentifier itemIdentifier = nil; + NSFileProviderItem item = nil, parentItem = nil; + + if ((itemIdentifier = [self persistentIdentifierForItemAtURL:changedItemURL]) != nil) + { + if ((item = [self itemForIdentifier:itemIdentifier error:&error]) != nil) + { + if ((parentItem = [self itemForIdentifier:item.parentItemIdentifier error:&error]) != nil) + { + [self.core reportLocalModificationOfItem:(OCItem *)item parentItem:(OCItem *)parentItem withContentsOfFileAtURL:changedItemURL isSecurityScoped:NO options:nil placeholderCompletionHandler:nil resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { + OCLogDebug(@"[FP] Upload of update finished with error=%@ item=%@", error, item); + }]; + } + } + } + + // ### Apple template comments: ### + + // Called at some point after the file has changed; the provider may then trigger an upload + + /* TODO: + - mark file at as needing an update in the model + - if there are existing NSURLSessionTasks uploading this file, cancel them + - create a fresh background NSURLSessionTask and schedule it to upload the current modifications + - register the NSURLSessionTask with NSFileProviderManager to provide progress updates + */ +} + +- (void)stopProvidingItemAtURL:(NSURL *)url +{ + NSError *error = nil; + NSFileProviderItemIdentifier itemIdentifier = nil; + NSFileProviderItem item = nil; + + OCLogDebug(@"[FP] Stop providing file at %@", url); + + if ((itemIdentifier = [self persistentIdentifierForItemAtURL:url]) != nil) + { + if ((item = [self itemForIdentifier:itemIdentifier error:&error]) != nil) + { + NSArray *downloadProgress = nil; + + // Cancel download if the item is currently downloading + if (item.isDownloading) + { + if ((downloadProgress = [self.core progressForItem:(OCItem *)item matchingEventType:OCEventTypeDownload]) != nil) + { + [downloadProgress makeObjectsPerformSelector:@selector(cancel)]; + } + } + + OCLogDebug(@"[FP] Item %@ is downloading %d: %@", item, item.isDownloading, downloadProgress); + } + } + + // ### Apple template comments: ### + + // Called after the last claim to the file has been released. At this point, it is safe for the file provider to remove the content file. + + // TODO: look up whether the file has local changes + // BOOL fileHasLocalChanges = NO; + // + // if (!fileHasLocalChanges) { + // // remove the existing file to free up space + // [[NSFileManager defaultManager] removeItemAtURL:url error:NULL]; + // + // // write out a placeholder to facilitate future property lookups + // [self providePlaceholderAtURL:url completionHandler:^(NSError * __nullable error) { + // // TODO: handle any error, do any necessary cleanup + // }]; + // } +} + +#pragma mark - Helpers +- (OCItem *)findKnownExistingItemInParent:(OCItem *)parentItem withName:(NSString *)name +{ + OCPath parentPath; + __block OCItem *existingItem = nil; + + if (((parentPath = parentItem.path) != nil) && (name != nil)) + { + OCPath destinationPath = [parentPath stringByAppendingPathComponent:name]; + + OCSyncExec(retrieveExistingItem, { + [self.core.vault.database retrieveCacheItemsAtPath:destinationPath itemOnly:YES completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, NSArray *items) { + existingItem = items.firstObject; + OCSyncExecDone(retrieveExistingItem); + }]; + }); + } + + return (existingItem); +} + + +#pragma mark - Actions + +// ### Apple template comments: ### + +/* TODO: implement the actions for items here + each of the actions follows the same pattern: + - make a note of the change in the local model + - schedule a server request as a background task to inform the server of the change + - call the completion block with the modified item in its post-modification state + */ + +- (void)createDirectoryWithName:(NSString *)directoryName inParentItemIdentifier:(NSFileProviderItemIdentifier)parentItemIdentifier completionHandler:(void (^)(NSFileProviderItem _Nullable, NSError * _Nullable))completionHandler +{ + NSError *error = nil; + OCItem *parentItem; + + if ((parentItem = (OCItem *)[self itemForIdentifier:parentItemIdentifier error:&error]) != nil) + { + // Detect collission with existing items + OCItem *existingItem; + + if ((existingItem = [self findKnownExistingItemInParent:parentItem withName:directoryName]) != nil) + { + completionHandler(nil, [NSError fileProviderErrorForCollisionWithItem:existingItem]); + return; + } + + [self.core createFolder:directoryName inside:parentItem options:nil resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { + if (error != nil) + { + if (error.HTTPStatus.code == OCHTTPStatusCodeMETHOD_NOT_ALLOWED) + { + // Folder already exists on the server + OCItem *existingItem; + + if ((existingItem = [self findKnownExistingItemInParent:parentItem withName:directoryName]) != nil) + { + completionHandler(nil, [NSError fileProviderErrorForCollisionWithItem:existingItem]); + return; + } + } + + } + completionHandler(item, error); + }]; + } + else + { + completionHandler(nil, error); + } +} + +- (void)reparentItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier toParentItemWithIdentifier:(NSFileProviderItemIdentifier)parentItemIdentifier newName:(NSString *)newName completionHandler:(void (^)(NSFileProviderItem _Nullable, NSError * _Nullable))completionHandler +{ + NSError *error = nil; + OCItem *item, *parentItem; + + if (((item = (OCItem *)[self itemForIdentifier:itemIdentifier error:&error]) != nil) && + ((parentItem = (OCItem *)[self itemForIdentifier:parentItemIdentifier error:&error]) != nil)) + { + [self.core moveItem:item to:parentItem withName:((newName != nil) ? newName : item.name) options:nil resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { + completionHandler(item, error); + }]; + } + else + { + completionHandler(nil, error); + } +} + +- (void)renameItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier toName:(NSString *)itemName completionHandler:(void (^)(NSFileProviderItem renamedItem, NSError *error))completionHandler +{ + NSError *error = nil; + OCItem *item, *parentItem; + + if (((item = (OCItem *)[self itemForIdentifier:itemIdentifier error:&error]) != nil) && + ((parentItem = (OCItem *)[self itemForIdentifier:item.parentFileID error:&error]) != nil)) + { + [self.core moveItem:item to:parentItem withName:itemName options:nil resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { + completionHandler(item, error); + }]; + } + else + { + completionHandler(nil, error); + } +} + +- (void)trashItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier completionHandler:(void (^)(NSFileProviderItem _Nullable, NSError * _Nullable))completionHandler +{ + NSError *error = nil; + OCItem *item; + + if ((item = (OCItem *)[self itemForIdentifier:itemIdentifier error:&error]) != nil) + { + [self.core deleteItem:item requireMatch:YES resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { + completionHandler(nil, error); + }]; + } + else + { + completionHandler(nil, error); + } +} + +- (void)deleteItemWithIdentifier:(NSFileProviderItemIdentifier)itemIdentifier completionHandler:(void (^)(NSError * _Nullable))completionHandler +{ + NSError *error = nil; + OCItem *item; + + if ((item = (OCItem *)[self itemForIdentifier:itemIdentifier error:&error]) != nil) + { + [self.core deleteItem:item requireMatch:YES resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { + completionHandler(error); + }]; + } + else + { + completionHandler(error); + } +} + +- (void)importDocumentAtURL:(NSURL *)fileURL toParentItemIdentifier:(NSFileProviderItemIdentifier)parentItemIdentifier completionHandler:(void (^)(NSFileProviderItem _Nullable, NSError * _Nullable))completionHandler +{ + NSError *error = nil; + BOOL isImportingFromVault = NO; + BOOL importByCopying = NO; + NSString *importFileName = fileURL.lastPathComponent; + OCItem *parentItem; + + // Detect import of documents from our own internal storage (=> used by Files.app for duplication of files) + isImportingFromVault = [fileURL.path hasPrefix:self.core.vault.filesRootURL.path]; + + if (isImportingFromVault) + { + NSFileProviderItemIdentifier sourceItemIdentifier; + NSFileProviderItem sourceItem; + + // Determine source item + if (((sourceItemIdentifier = [self persistentIdentifierForItemAtURL:fileURL]) != nil) && + ((sourceItem = [self itemForIdentifier:sourceItemIdentifier error:nil]) != nil)) + { + importByCopying = YES; + } + } + + if ((parentItem = (OCItem *)[self itemForIdentifier:parentItemIdentifier error:&error]) != nil) + { + // Detect name collissions + OCItem *existingItem; + + if ((existingItem = [self findKnownExistingItemInParent:parentItem withName:importFileName]) != nil) + { + // Return collission error + completionHandler(nil, [NSError fileProviderErrorForCollisionWithItem:existingItem]); + return; + } + + // Start import + [self.core importFileNamed:importFileName at:parentItem fromURL:fileURL isSecurityScoped:YES options:@{ OCCoreOptionImportByCopying : @(importByCopying) } placeholderCompletionHandler:^(NSError *error, OCItem *item) { + completionHandler(item, error); + } resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { + if ([error.domain isEqual:OCHTTPStatusErrorDomain] && (error.code == OCHTTPStatusCodePRECONDITION_FAILED)) + { + // Collission: file already exists + if ((parameter != nil) && ([parameter isKindOfClass:[OCItem class]])) + { + OCItem *placeholderItem = (OCItem *)parameter; + + // TODO (defunct): + // Upload errors (such as NSFileProviderErrorInsufficientQuota) should be handled + // with a subsequent update to the [placeholder] item, setting its uploadingError property. + + // TODO (not yet implemented): + // Upload errors should not prevent creating or importing a document, because they + // can be resolved at a later date (for example, when the user has quota again.) + + if (placeholderItem.isPlaceholder) + { + [placeholderItem setUploadingError:[NSError fileProviderErrorForCollisionWithItem:placeholderItem]]; + } + } + } + }]; + } + else + { + completionHandler(nil, error); + } +} + +- (void)setFavoriteRank:(NSNumber *)favoriteRank forItemIdentifier:(NSFileProviderItemIdentifier)itemIdentifier completionHandler:(void (^)(NSFileProviderItem _Nullable, NSError * _Nullable))completionHandler +{ + NSError *error = nil; + OCItem *item; + + if ((item = (OCItem *)[self itemForIdentifier:itemIdentifier error:&error]) != nil) + { +// item.isFavorite = @(favoriteRank != nil); // Stored on server + + [item setLocalFavoriteRank:favoriteRank]; // Stored in local attributes + + [self.core updateItem:item properties:@[ OCItemPropertyNameLocalAttributes ] options:nil resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { + completionHandler(item, error); + }]; + } + else + { + completionHandler(nil, error); + } +} + +- (void)setTagData:(NSData *)tagData forItemIdentifier:(NSFileProviderItemIdentifier)itemIdentifier completionHandler:(void (^)(NSFileProviderItem _Nullable, NSError * _Nullable))completionHandler +{ + NSError *error = nil; + OCItem *item; + +// // Example of self-creating tagData +// tagData = [NSKeyedArchiver archivedDataWithRootObject:@{ +// @"t" : @[ +// @[ @"Root", @(6) ], // First value is the label, second a color number +// @[ @"Beer", @(4) ], +// ], +// +// @"v" : @(1) // Version (?) +// }]; + + if ((item = (OCItem *)[self itemForIdentifier:itemIdentifier error:&error]) != nil) + { + [item setLocalTagData:tagData]; // Stored in local attributes + + [self.core updateItem:item properties:@[ OCItemPropertyNameLocalAttributes ] options:nil resultHandler:^(NSError *error, OCCore *core, OCItem *item, id parameter) { + completionHandler(item, error); + }]; + } + else + { + completionHandler(nil, error); + } +} + +#pragma mark - Enumeration + +- (nullable id)enumeratorForContainerItemIdentifier:(NSFileProviderItemIdentifier)containerItemIdentifier error:(NSError **)error +{ + if (self.domain.identifier == nil) + { + if (error != NULL) + { + *error = [NSError errorWithDomain:NSFileProviderErrorDomain code:NSFileProviderErrorNotAuthenticated userInfo:nil]; + } + + return (nil); + } + + if (![containerItemIdentifier isEqualToString:NSFileProviderWorkingSetContainerItemIdentifier]) + { + FileProviderEnumerator *enumerator = [[FileProviderEnumerator alloc] initWithBookmark:self.bookmark enumeratedItemIdentifier:containerItemIdentifier]; + + enumerator.fileProviderExtension = self; + + return (enumerator); + } + + return (nil); + + // ### Apple template comments: ### + + /* + FileProviderEnumerator *enumerator = nil; + + if ([containerItemIdentifier isEqualToString:NSFileProviderRootContainerItemIdentifier]) { + // TODO: instantiate an enumerator for the container root + } else if ([containerItemIdentifier isEqualToString:NSFileProviderWorkingSetContainerItemIdentifier]) { + // TODO: instantiate an enumerator for the working set + } else { + // TODO: determine if the item is a directory or a file + // - for a directory, instantiate an enumerator of its subitems + // - for a file, instantiate an enumerator that observes changes to the file + } + + return enumerator; + */ +} + +#pragma mark - Thumbnails +- (NSProgress *)fetchThumbnailsForItemIdentifiers:(NSArray *)itemIdentifiers requestedSize:(CGSize)size perThumbnailCompletionHandler:(void (^)(NSFileProviderItemIdentifier _Nonnull, NSData * _Nullable, NSError * _Nullable))perThumbnailCompletionHandler completionHandler:(void (^)(NSError * _Nullable))completionHandler +{ + FileProviderExtensionThumbnailRequest *thumbnailRequest; + + if ((thumbnailRequest = [FileProviderExtensionThumbnailRequest new]) != nil) + { + if (size.width > 256) + { + size.width = 256; + } + + if (size.height > 256) + { + size.height = 256; + } + + thumbnailRequest.extension = self; + thumbnailRequest.itemIdentifiers = itemIdentifiers; + thumbnailRequest.sizeInPixels = size; + thumbnailRequest.perThumbnailCompletionHandler = perThumbnailCompletionHandler; + thumbnailRequest.completionHandler = completionHandler; + thumbnailRequest.progress = [NSProgress progressWithTotalUnitCount:itemIdentifiers.count]; + + [thumbnailRequest requestNextThumbnail]; + } + + return (thumbnailRequest.progress); +} + +#pragma mark - Core +- (OCBookmark *)bookmark +{ + @synchronized(self) + { + if (_bookmark == nil) + { + NSFileProviderDomainIdentifier domainIdentifier; + + if ((domainIdentifier = self.domain.identifier) != nil) + { + NSUUID *bookmarkUUID = [[NSUUID alloc] initWithUUIDString:domainIdentifier]; + + _bookmark = [[OCBookmarkManager sharedBookmarkManager] bookmarkForUUID:bookmarkUUID]; + + if (_bookmark == nil) + { + OCLogError(@"[FP] Error retrieving bookmark for domain %@ (UUID %@) - reloading", OCLogPrivate(self.domain.displayName), OCLogPrivate(self.domain.identifier)); + + [[OCBookmarkManager sharedBookmarkManager] loadBookmarks]; + + _bookmark = [[OCBookmarkManager sharedBookmarkManager] bookmarkForUUID:bookmarkUUID]; + + if (_bookmark == nil) + { + OCLogError(@"[FP] Error retrieving bookmark for domain %@ (UUID %@) - final", OCLogPrivate(self.domain.displayName), OCLogPrivate(self.domain.identifier)); + } + } + } + } + } + + return (_bookmark); +} + +- (OCCore *)core +{ + @synchronized(self) + { + if (_core == nil) + { + if (self.bookmark != nil) + { + OCSyncExec(waitForCore, { + _core = [[OCCoreManager sharedCoreManager] requestCoreForBookmark:self.bookmark completionHandler:^(OCCore *core, NSError *error) { + OCSyncExecDone(waitForCore); + }]; + }); + + _core.delegate = self; + } + } + + if (_core == nil) + { + OCLogError(@"[FP] Error getting core for domain %@ (UUID %@)", OCLogPrivate(self.domain.displayName), OCLogPrivate(self.domain.identifier)); + } + } + + return (_core); +} + +- (void)core:(OCCore *)core handleError:(NSError *)error issue:(OCConnectionIssue *)issue +{ + OCLogDebug(@"[FP] CORE ERROR: error=%@, issue=%@", error, issue); + + if (issue.type == OCConnectionIssueTypeMultipleChoice) + { + [issue cancel]; + } +} + +@end + diff --git a/ownCloud File Provider/FileProviderExtensionThumbnailRequest.h b/ownCloud File Provider/FileProviderExtensionThumbnailRequest.h new file mode 100644 index 000000000..d23160654 --- /dev/null +++ b/ownCloud File Provider/FileProviderExtensionThumbnailRequest.h @@ -0,0 +1,44 @@ +// +// FileProviderExtensionThumbnailRequest.h +// ownCloud File Provider +// +// Created by Felix Schwarz on 09.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import +#import "FileProviderExtension.h" + +typedef void (^FileProviderExtensionThumbnailRequestPerThumbnailCompletionHandler)(NSFileProviderItemIdentifier identifier, NSData * _Nullable imageData, NSError * _Nullable error); +typedef void (^FileProviderExtensionThumbnailRequestCompletionHandler)(NSError * _Nullable error); + +@interface FileProviderExtensionThumbnailRequest : NSObject +{ + BOOL _isDone; +} + +@property(strong) FileProviderExtension *extension; + +@property(strong) NSArray *itemIdentifiers; +@property(assign) NSUInteger cursorPosition; + +@property(assign) CGSize sizeInPixels; + +@property(copy) FileProviderExtensionThumbnailRequestPerThumbnailCompletionHandler perThumbnailCompletionHandler; +@property(copy) FileProviderExtensionThumbnailRequestCompletionHandler completionHandler; + +@property(strong) NSProgress *progress; + +- (void)requestNextThumbnail; + +@end diff --git a/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m b/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m new file mode 100644 index 000000000..6e703e7cd --- /dev/null +++ b/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m @@ -0,0 +1,99 @@ +// +// FileProviderExtensionThumbnailRequest.m +// ownCloud File Provider +// +// Created by Felix Schwarz on 09.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "FileProviderExtensionThumbnailRequest.h" + +@implementation FileProviderExtensionThumbnailRequest + +- (void)dealloc +{ + OCLogDebug(@"Dealloc %@", self); +} + +- (void)requestNextThumbnail +{ + if (_isDone) + { + return; + } + + if (self.progress.cancelled) + { + [self completedRequest]; + return; + } + + if (self.cursorPosition < self.itemIdentifiers.count) + { + NSFileProviderItemIdentifier itemIdentifier = self.itemIdentifiers[self.cursorPosition]; + + OCLogDebug(@"Retrieving %ld / %ld:", self.cursorPosition, self.itemIdentifiers.count); + + self.cursorPosition += 1; + + [self.extension.core retrieveItemFromDatabaseForFileID:(OCFileID)itemIdentifier completionHandler:^(NSError *error, OCSyncAnchor syncAnchor, OCItem *itemFromDatabase) { + OCLogDebug(@"Retrieving %ld: %@", self.cursorPosition-1, itemFromDatabase.name); + + if ((itemFromDatabase.type == OCItemTypeCollection) || (itemFromDatabase.thumbnailAvailability == OCItemThumbnailAvailabilityNone)) + { + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ + self.perThumbnailCompletionHandler(itemIdentifier, nil, nil); + + OCLogDebug(@"Replied %ld: %@ -> none available", self.cursorPosition-1, itemFromDatabase.name); + + [self requestNextThumbnail]; + }); + } + else + { + NSProgress *retrieveProgress = [self.extension.core retrieveThumbnailFor:itemFromDatabase maximumSize:self.sizeInPixels scale:1.0 retrieveHandler:^(NSError *error, OCCore *core, OCItem *item, OCItemThumbnail *thumbnail, BOOL isOngoing, NSProgress *progress) { + + OCLogDebug(@"Retrieved %ld: %@ -> %d", self.cursorPosition-1, itemFromDatabase.name, isOngoing); + + if (!isOngoing) + { + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ + self.perThumbnailCompletionHandler(itemIdentifier, thumbnail.data, (thumbnail==nil) ? nil : error); + + [self requestNextThumbnail]; + }); + } + }]; + + [self.progress addChild:retrieveProgress withPendingUnitCount:1]; + } + }]; + } + else + { + OCLogDebug(@"Done retrieving %ld / %ld", self.cursorPosition, self.itemIdentifiers.count); + + [self completedRequest]; + } +} + +- (void)completedRequest +{ + _isDone = YES; + + dispatch_async(dispatch_get_main_queue(), ^{ + self.completionHandler(nil); + }); +} + +@end diff --git a/ownCloud File Provider/Info.plist b/ownCloud File Provider/Info.plist new file mode 100644 index 000000000..9a5bb8488 --- /dev/null +++ b/ownCloud File Provider/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + ownCloud + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 81 + OCAppGroupIdentifier + group.com.owncloud.ios-app + OCAppIdentifierPrefix + $(AppIdentifierPrefix) + OCKeychainAccessGroupIdentifier + group.com.owncloud.ios-app + OCHasFileProvider + + NSExtension + + NSExtensionFileProviderDocumentGroup + group.com.owncloud.ios-app + NSExtensionFileProviderSupportsEnumeration + + NSExtensionPointIdentifier + com.apple.fileprovider-nonui + NSExtensionPrincipalClass + FileProviderExtension + + + diff --git a/ownCloud File Provider/NSNumber+OCSyncAnchorData.h b/ownCloud File Provider/NSNumber+OCSyncAnchorData.h new file mode 100644 index 000000000..8d1b9a045 --- /dev/null +++ b/ownCloud File Provider/NSNumber+OCSyncAnchorData.h @@ -0,0 +1,27 @@ +// +// NSNumber+OCSyncAnchorData.h +// ownCloud File Provider +// +// Created by Felix Schwarz on 18.07.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import +#import + +@interface NSNumber (OCSyncAnchorData) + ++ (instancetype)numberFromSyncAnchorData:(NSFileProviderSyncAnchor)syncAnchor; +- (NSFileProviderSyncAnchor)syncAnchorData; + +@end diff --git a/ownCloud File Provider/NSNumber+OCSyncAnchorData.m b/ownCloud File Provider/NSNumber+OCSyncAnchorData.m new file mode 100644 index 000000000..e43bb2e0b --- /dev/null +++ b/ownCloud File Provider/NSNumber+OCSyncAnchorData.m @@ -0,0 +1,33 @@ +// +// NSNumber+OCSyncAnchorData.m +// ownCloud File Provider +// +// Created by Felix Schwarz on 18.07.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "NSNumber+OCSyncAnchorData.h" + +@implementation NSNumber (OCSyncAnchorData) + ++ (instancetype)numberFromSyncAnchorData:(NSFileProviderSyncAnchor)syncAnchor +{ + return ([NSKeyedUnarchiver unarchiveObjectWithData:syncAnchor]); +} + +- (NSFileProviderSyncAnchor)syncAnchorData +{ + return ([NSKeyedArchiver archivedDataWithRootObject:self]); +} + +@end diff --git a/ownCloud File Provider/OCBookmark+FileProvider.h b/ownCloud File Provider/OCBookmark+FileProvider.h new file mode 100644 index 000000000..170a74cee --- /dev/null +++ b/ownCloud File Provider/OCBookmark+FileProvider.h @@ -0,0 +1,25 @@ +// +// OCBookmark+FileProvider.h +// ownCloud File Provider +// +// Created by Felix Schwarz on 09.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import + +@interface OCBookmark (FileProvider) + +- (NSString *)pathRelativeToDocumentStorage; //!< "The path of the domain's subdirectory relative to the file provider's shared container." + +@end diff --git a/ownCloud File Provider/OCBookmark+FileProvider.m b/ownCloud File Provider/OCBookmark+FileProvider.m new file mode 100644 index 000000000..d3a9fbb57 --- /dev/null +++ b/ownCloud File Provider/OCBookmark+FileProvider.m @@ -0,0 +1,28 @@ +// +// OCBookmark+FileProvider.m +// ownCloud File Provider +// +// Created by Felix Schwarz on 09.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "OCBookmark+FileProvider.h" + +@implementation OCBookmark (FileProvider) + +- (NSString *)pathRelativeToDocumentStorage +{ + return (self.uuid.UUIDString); +} + +@end diff --git a/ownCloud File Provider/OCCore+FileProviderTools.h b/ownCloud File Provider/OCCore+FileProviderTools.h new file mode 100644 index 000000000..9cc1a4ed9 --- /dev/null +++ b/ownCloud File Provider/OCCore+FileProviderTools.h @@ -0,0 +1,26 @@ +// +// OCCore+FileProviderTools.h +// ownCloud File Provider +// +// Created by Felix Schwarz on 09.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import +#import + +@interface OCCore (FileProviderTools) + +- (OCItem *)synchronousRetrieveItemFromDatabaseForFileID:(OCFileID)fileID syncAnchor:(OCSyncAnchor __autoreleasing *)outSyncAnchor error:(NSError * __autoreleasing *)outError; + +@end diff --git a/ownCloud File Provider/OCCore+FileProviderTools.m b/ownCloud File Provider/OCCore+FileProviderTools.m new file mode 100644 index 000000000..2d0196286 --- /dev/null +++ b/ownCloud File Provider/OCCore+FileProviderTools.m @@ -0,0 +1,48 @@ +// +// OCCore+FileProviderTools.m +// ownCloud File Provider +// +// Created by Felix Schwarz on 09.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import "OCCore+FileProviderTools.h" + +@implementation OCCore (FileProviderTools) + +- (OCItem *)synchronousRetrieveItemFromDatabaseForFileID:(OCFileID)fileID syncAnchor:(OCSyncAnchor __autoreleasing *)outSyncAnchor error:(NSError * __autoreleasing *)outError +{ + __block OCItem *item = nil; + + OCSyncExec(databaseRetrieval, { + [self.vault.database retrieveCacheItemForFileID:fileID completionHandler:^(OCDatabase *db, NSError *error, OCSyncAnchor syncAnchor, OCItem *itemFromDatabase) { + item = itemFromDatabase; + + if (outSyncAnchor != NULL) + { + *outSyncAnchor = syncAnchor; + } + + if (outError != NULL) + { + *outError = error; + } + + OCSyncExecDone(databaseRetrieval); + }]; + }); + + return (item); +} + +@end diff --git a/ownCloud File Provider/OCItem+FileProviderItem.h b/ownCloud File Provider/OCItem+FileProviderItem.h new file mode 100644 index 000000000..506c24220 --- /dev/null +++ b/ownCloud File Provider/OCItem+FileProviderItem.h @@ -0,0 +1,28 @@ +// +// OCItem+FileProviderItem.h +// ownCloud File Provider +// +// Created by Felix Schwarz on 08.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import + +@interface OCItem (FileProviderItem) + +- (void)setLocalFavoriteRank:(NSNumber *)localFavoriteRank; +- (void)setLocalTagData:(NSData *)localTagData; + +- (void)setUploadingError:(NSError *)uploadingError; + +@end diff --git a/ownCloud File Provider/OCItem+FileProviderItem.m b/ownCloud File Provider/OCItem+FileProviderItem.m new file mode 100644 index 000000000..7d43055df --- /dev/null +++ b/ownCloud File Provider/OCItem+FileProviderItem.m @@ -0,0 +1,245 @@ +// +// OCItem+FileProviderItem.m +// ownCloud File Provider +// +// Created by Felix Schwarz on 08.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2018, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +#import + +#import "OCItem+FileProviderItem.h" + +static NSMutableDictionary *sOCItemUploadingErrors; + +@implementation OCItem (FileProviderItem) + +@dynamic filename; + +// TODO: implement an initializer to create an item from your extension's backing model +// TODO: implement the accessors to return the values from your extension's backing model + +- (NSFileProviderItemIdentifier)itemIdentifier +{ + if ([self.path isEqual:@"/"]) + { + return (NSFileProviderRootContainerItemIdentifier); + } + + return (self.fileID); +} + +- (NSFileProviderItemIdentifier)parentItemIdentifier +{ + if ([[self.path stringByDeletingLastPathComponent] isEqualToString:@"/"]) + { + return (NSFileProviderRootContainerItemIdentifier); + } + + return (self.parentFileID); +} + +- (NSString *)filename +{ + return (self.name); +} + +- (NSString *)typeIdentifier +{ + // Return special UTI type for folders + if (self.type == OCItemTypeCollection) + { + return ((__bridge NSString *)kUTTypeFolder); + } + + // Convert MIME type to UTI type identifier + return ((NSString *)CFBridgingRelease(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)self.mimeType, NULL))); +} + +- (NSFileProviderItemCapabilities)capabilities +{ + OCItemPermissions permissions = self.permissions; + + switch (self.type) + { + case OCItemTypeFile: + return ( + NSFileProviderItemCapabilitiesAllowsReading | + ((permissions & OCItemPermissionWritable) ? NSFileProviderItemCapabilitiesAllowsWriting : 0) | + ((permissions & OCItemPermissionMove) ? NSFileProviderItemCapabilitiesAllowsReparenting : 0) | + ((permissions & OCItemPermissionRename) ? NSFileProviderItemCapabilitiesAllowsRenaming : 0) | + ((permissions & OCItemPermissionDelete) ? NSFileProviderItemCapabilitiesAllowsTrashing : 0) | + ((permissions & OCItemPermissionDelete) ? NSFileProviderItemCapabilitiesAllowsDeleting : 0) + ); + break; + + case OCItemTypeCollection: + return (NSFileProviderItemCapabilitiesAllowsContentEnumerating | + ((permissions & OCItemPermissionMove) ? NSFileProviderItemCapabilitiesAllowsReparenting : 0) | + ((permissions & OCItemPermissionRename) ? NSFileProviderItemCapabilitiesAllowsRenaming : 0) | + ((permissions & OCItemPermissionDelete) ? NSFileProviderItemCapabilitiesAllowsDeleting : 0) | + ((permissions & (OCItemPermissionCreateFile|OCItemPermissionCreateFolder)) ? NSFileProviderItemCapabilitiesAllowsAddingSubItems : 0) + ); + break; + } + + return (NSFileProviderItemCapabilitiesAllowsAll); +} + +- (NSData *)versionIdentifier +{ + return ([[NSString stringWithFormat:@"%@_:_%@", self.eTag, self.fileID] dataUsingEncoding:NSUTF8StringEncoding]); +} + +- (NSNumber *)documentSize +{ + if (self.type == OCItemTypeFile) + { + return (@(self.size)); + } + + return (nil); +} + +- (BOOL)isDownloading +{ + return ((self.syncActivity & OCItemSyncActivityDownloading) == OCItemSyncActivityDownloading); +} + +- (BOOL)isDownloaded +{ + if (self.localRelativePath != nil) + { + return (YES); + } + + return (NO); +} + +- (BOOL)isUploading +{ + return ((self.syncActivity & OCItemSyncActivityUploading) == OCItemSyncActivityUploading); +} + +- (BOOL)isUploaded +{ + if (![self isUploading]) + { + return (!self.locallyModified); + } + + return (NO); +} + +- (BOOL)isMostRecentVersionDownloaded +{ + if (((self.localRelativePath != nil) && (self.remoteItem == nil)) || self.isUploading) + { + return (YES); + } + + return (NO); +} + +//- (BOOL)respondsToSelector:(SEL)aSelector +//{ +// OCLogDebug(@"Probing for %@", NSStringFromSelector(aSelector)); +// +// return ([super respondsToSelector:aSelector]); +//} +// + +- (NSDate *)contentModificationDate +{ + return (self.lastModified); +} + +- (NSNumber *)childItemCount +{ + if (self.type == OCItemTypeFile) + { + return (@(0)); + } + + return (nil); +} + +- (void)setLocalFavoriteRank:(NSNumber *)localFavoriteRank +{ + [self setValue:localFavoriteRank forLocalAttribute:OCLocalAttributeFavoriteRank]; +} + +- (NSNumber *)favoriteRank +{ + NSNumber *favoriteRank = [self valueForLocalAttribute:OCLocalAttributeFavoriteRank]; + +// NSNumber *favoriteRank = nil; +// +// if (self.isFavorite) +// { +// if ((favoriteRank = [self valueForLocalAttribute:OCLocalAttributeFavoriteRank]) == nil) +// { +// favoriteRank = @(NSFileProviderFavoriteRankUnranked); +// } +// } + + return (favoriteRank); +} + +- (void)setLocalTagData:(NSData *)localTagData +{ + [self setValue:localTagData forLocalAttribute:OCLocalAttributeTagData]; +} + +- (NSData *)tagData +{ + return ([self valueForLocalAttribute:OCLocalAttributeTagData]); +} + +- (NSError *)uploadingError +{ + if (self.fileID != nil) + { + if (self.isPlaceholder) + { + NSLog(@"Request uploadingError for %@", self.fileID); + } + + @synchronized ([OCItem class]) + { + return (sOCItemUploadingErrors[self.fileID]); + } + } + + return (nil); +} + +- (void)setUploadingError:(NSError *)uploadingError +{ + NSLog(@"Set uploadingError for %@ to %@", self.fileID, uploadingError); + + if (self.fileID != nil) + { + @synchronized ([OCItem class]) + { + if (sOCItemUploadingErrors == nil) + { + sOCItemUploadingErrors = [NSMutableDictionary new]; + } + + sOCItemUploadingErrors[self.fileID] = uploadingError; + } + } +} + +@end diff --git a/ownCloud File Provider/ownCloud_File_Provider.entitlements b/ownCloud File Provider/ownCloud_File_Provider.entitlements new file mode 100644 index 000000000..988c47786 --- /dev/null +++ b/ownCloud File Provider/ownCloud_File_Provider.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.application-groups + + group.com.owncloud.ios-app + + keychain-access-groups + + $(AppIdentifierPrefix)group.com.owncloud.ios-app + + + diff --git a/ownCloud File ProviderUI/Base.lproj/MainInterface.storyboard b/ownCloud File ProviderUI/Base.lproj/MainInterface.storyboard new file mode 100644 index 000000000..2b0ddeb50 --- /dev/null +++ b/ownCloud File ProviderUI/Base.lproj/MainInterface.storyboard @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ownCloud File ProviderUI/DocumentActionViewController.h b/ownCloud File ProviderUI/DocumentActionViewController.h new file mode 100644 index 000000000..e90a2ea60 --- /dev/null +++ b/ownCloud File ProviderUI/DocumentActionViewController.h @@ -0,0 +1,14 @@ +// +// DocumentActionViewController.h +// ownCloud File ProviderUI +// +// Created by Felix Schwarz on 07.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +#import +#import + +@interface DocumentActionViewController : FPUIActionExtensionViewController + +@end diff --git a/ownCloud File ProviderUI/DocumentActionViewController.m b/ownCloud File ProviderUI/DocumentActionViewController.m new file mode 100644 index 000000000..49d5dde12 --- /dev/null +++ b/ownCloud File ProviderUI/DocumentActionViewController.m @@ -0,0 +1,38 @@ +// +// DocumentActionViewController.m +// ownCloud File ProviderUI +// +// Created by Felix Schwarz on 07.06.18. +// Copyright © 2018 ownCloud GmbH. All rights reserved. +// + +#import "DocumentActionViewController.h" + +@interface DocumentActionViewController() + @property (weak) IBOutlet UILabel *identifierLabel; + @property (weak) IBOutlet UILabel *actionTypeLabel; +@end + +@implementation DocumentActionViewController + +- (void)prepareForActionWithIdentifier:(NSString *)actionIdentifier itemIdentifiers:(NSArray *)itemIdentifiers { + self.identifierLabel.text = actionIdentifier; + self.actionTypeLabel.text = @"Custom action"; +} + +- (void)prepareForError:(NSError *)error { + self.identifierLabel.text = error.localizedDescription; + self.actionTypeLabel.text = @"Error"; +} + +- (IBAction)doneButtonTapped:(id)sender { + // Perform the action and call the completion block. If an unrecoverable error occurs you must still call the completion block with an error. Use the error code FPUIExtensionErrorCodeFailed to signal the failure. + [self.extensionContext completeRequest]; +} + +- (IBAction)cancelButtonTapped:(id)sender { + [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:FPUIErrorDomain code:FPUIExtensionErrorCodeUserCancelled userInfo:nil]]; +} + +@end + diff --git a/ownCloud File ProviderUI/Info.plist b/ownCloud File ProviderUI/Info.plist new file mode 100644 index 000000000..dcf2f75d2 --- /dev/null +++ b/ownCloud File ProviderUI/Info.plist @@ -0,0 +1,42 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + ownCloud File ProviderUI + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 81 + NSExtension + + NSExtensionFileProviderActions + + + NSExtensionFileProviderActionActivationRule + TRUEPREDICATE + NSExtensionFileProviderActionIdentifier + com.mycompany.FileProviderUI.CustomAction + NSExtensionFileProviderActionName + Custom Action + + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.fileprovider-actionsui + + + diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index e13ac2a50..ea1465359 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -63,6 +63,7 @@ A45A8D98137C902524B84E6D /* EarlGrey.framework in EarlGrey Copy Files */ = {isa = PBXBuildFile; fileRef = D0D9C062DD1E85A838608B0F /* EarlGrey.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; DC018F8320A0F56300135198 /* UIView+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC018F8220A0F56300135198 /* UIView+Animation.swift */; }; DC018F8C20A1060A00135198 /* ProgressHUDViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC018F8B20A1060A00135198 /* ProgressHUDViewController.swift */; }; + DC0196AB20F7690C00C41B78 /* OCBookmark+FileProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A1A420CBEF85008ACB6C /* OCBookmark+FileProvider.m */; }; DC01CDCC212EDDF600FC8E38 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC01CDCB212EDDF600FC8E38 /* TextViewController.swift */; }; DC0B379420514E4700189B9A /* ServerListBookmarkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B379320514E4700189B9A /* ServerListBookmarkCell.swift */; }; DC0B37972051681600189B9A /* ThemeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B37962051681600189B9A /* ThemeButton.swift */; }; @@ -74,6 +75,13 @@ DC1B270A209CF0D3004715E1 /* ConnectionIssueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1B2706209CF0D3004715E1 /* ConnectionIssueViewController.swift */; }; DC1B270C209CF34B004715E1 /* BookmarkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */; }; DC248C67213E7DB00067FE94 /* NSLayoutConstraint+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC248C66213E7DB00067FE94 /* NSLayoutConstraint+Extension.swift */; }; + DC27A18E20CA9F66008ACB6C /* OCItem+FileProviderItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A18D20CA9F66008ACB6C /* OCItem+FileProviderItem.m */; }; + DC27A18F20CAA0BA008ACB6C /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; }; + DC27A19120CAA0BA008ACB6C /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC27A19020CAA0BA008ACB6C /* MobileCoreServices.framework */; }; + DC27A19D20CAB602008ACB6C /* FileProviderInterfaceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC27A19C20CAB602008ACB6C /* FileProviderInterfaceManager.swift */; }; + DC27A1A520CBEF85008ACB6C /* OCBookmark+FileProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A1A420CBEF85008ACB6C /* OCBookmark+FileProvider.m */; }; + DC27A1A820CC095C008ACB6C /* OCCore+FileProviderTools.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A1A720CC095C008ACB6C /* OCCore+FileProviderTools.m */; }; + DC27A1E920CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A1E820CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.m */; }; DC321261207EB01B00DB171D /* ThemeImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC321260207EB01B00DB171D /* ThemeImage.swift */; }; DC3317CE2084966700E36C8F /* ThemeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3317CD2084966700E36C8F /* ThemeTableViewCell.swift */; }; DC3BE0D72077BC5D002A0AC0 /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2347445E2076138000859C93 /* openssl.framework */; }; @@ -108,6 +116,8 @@ DC85980620D85EE600A433C6 /* AsyncSequentialQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC85980120D85EE500A433C6 /* AsyncSequentialQueue.swift */; }; DC869A592153B1F60088977E /* OCMockingManagerSwiftExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC869A582153B1F60088977E /* OCMockingManagerSwiftExtension.swift */; }; DC89C45D20860B5D0044BCAE /* ProgressSummarizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC89C45C20860B5D0044BCAE /* ProgressSummarizer.swift */; }; + DC98BBCB20FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.m in Sources */ = {isa = PBXBuildFile; fileRef = DC98BBCA20FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.m */; }; + DC98BBD420FF824600F4ED3E /* FileProviderEnumeratorObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = DC98BBD320FF824600F4ED3E /* FileProviderEnumeratorObserver.m */; }; DC9BFBB320A19AF4007064B5 /* doc in Resources */ = {isa = PBXBuildFile; fileRef = DC9BFBB220A19AF3007064B5 /* doc */; }; DC9BFBB920A1AF2C007064B5 /* icon-locked.tvg in Resources */ = {isa = PBXBuildFile; fileRef = DC9BFBB820A1AF2B007064B5 /* icon-locked.tvg */; }; DC9BFBBB20A1B3CA007064B5 /* icon-password-manager.tvg in Resources */ = {isa = PBXBuildFile; fileRef = DC9BFBBA20A1B3CA007064B5 /* icon-password-manager.tvg */; }; @@ -115,6 +125,11 @@ DCB44D7D2186F0F600DAA4CC /* ThemeStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB44D7C2186F0F600DAA4CC /* ThemeStyle.swift */; }; DCB44D852186FEF700DAA4CC /* ThemeStyle+DefaultStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB44D842186FEF700DAA4CC /* ThemeStyle+DefaultStyles.swift */; }; DCB44D87218718BA00DAA4CC /* VendorServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB44D86218718BA00DAA4CC /* VendorServices.swift */; }; + DCC6564A20C9B7E400110A97 /* FileProviderExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC6564920C9B7E400110A97 /* FileProviderExtension.m */; }; + DCC6565020C9B7E400110A97 /* FileProviderEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC6564F20C9B7E400110A97 /* FileProviderEnumerator.m */; }; + DCC6565B20C9B7E400110A97 /* DocumentActionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DCC6565A20C9B7E400110A97 /* DocumentActionViewController.m */; }; + DCC6565E20C9B7E400110A97 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DCC6565C20C9B7E400110A97 /* MainInterface.storyboard */; }; + DCC6566520C9B7E400110A97 /* ownCloud File Provider.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = DCC6564620C9B7E300110A97 /* ownCloud File Provider.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; DCE5E8A12080D781005F60CE /* video.tvg in Resources */ = {isa = PBXBuildFile; fileRef = DCE5E88C2080D77E005F60CE /* video.tvg */; }; DCE5E8A22080D781005F60CE /* folder-external.tvg in Resources */ = {isa = PBXBuildFile; fileRef = DCE5E88D2080D77E005F60CE /* folder-external.tvg */; }; DCE5E8A32080D781005F60CE /* file.tvg in Resources */ = {isa = PBXBuildFile; fileRef = DCE5E88E2080D77E005F60CE /* file.tvg */; }; @@ -219,6 +234,13 @@ remoteGlobalIDString = DCA8922620F5F13E00AEFF98; remoteInfo = ownCloudMocking; }; + DC27A19220CAA0C6008ACB6C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = DCC8F9AA202852A200EB6701; + remoteInfo = ownCloudSDK; + }; DC3BE0CC2077BC52002A0AC0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */; @@ -268,6 +290,20 @@ remoteGlobalIDString = C79800BA1A21CB5300380860; remoteInfo = "PocketSVG (iOS)"; }; + DCC6566020C9B7E400110A97 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 233BDE94204FEFE500C06732 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DCC6565620C9B7E400110A97; + remoteInfo = "ownCloud File ProviderUI"; + }; + DCC6566320C9B7E400110A97 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 233BDE94204FEFE500C06732 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DCC6564520C9B7E300110A97; + remoteInfo = "ownCloud File Provider"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -310,6 +346,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + DCC6566520C9B7E400110A97 /* ownCloud File Provider.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -392,7 +429,16 @@ DC1B2706209CF0D3004715E1 /* ConnectionIssueViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionIssueViewController.swift; sourceTree = ""; }; DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkViewController.swift; sourceTree = ""; }; DC248C66213E7DB00067FE94 /* NSLayoutConstraint+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLayoutConstraint+Extension.swift"; sourceTree = ""; }; + DC27A18C20CA9F66008ACB6C /* OCItem+FileProviderItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCItem+FileProviderItem.h"; sourceTree = ""; }; + DC27A18D20CA9F66008ACB6C /* OCItem+FileProviderItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCItem+FileProviderItem.m"; sourceTree = ""; }; DC27A19020CAA0BA008ACB6C /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; + DC27A19C20CAB602008ACB6C /* FileProviderInterfaceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderInterfaceManager.swift; sourceTree = ""; }; + DC27A1A320CBEF85008ACB6C /* OCBookmark+FileProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCBookmark+FileProvider.h"; sourceTree = ""; }; + DC27A1A420CBEF85008ACB6C /* OCBookmark+FileProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCBookmark+FileProvider.m"; sourceTree = ""; }; + DC27A1A620CC095C008ACB6C /* OCCore+FileProviderTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCCore+FileProviderTools.h"; sourceTree = ""; }; + DC27A1A720CC095C008ACB6C /* OCCore+FileProviderTools.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCCore+FileProviderTools.m"; sourceTree = ""; }; + DC27A1E720CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileProviderExtensionThumbnailRequest.h; sourceTree = ""; }; + DC27A1E820CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileProviderExtensionThumbnailRequest.m; sourceTree = ""; }; DC321260207EB01B00DB171D /* ThemeImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeImage.swift; sourceTree = ""; }; DC3317CD2084966700E36C8F /* ThemeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeTableViewCell.swift; sourceTree = ""; }; DC3BE0DC2077CC13002A0AC0 /* ClientQueryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientQueryViewController.swift; sourceTree = ""; }; @@ -424,6 +470,10 @@ DC85980120D85EE500A433C6 /* AsyncSequentialQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncSequentialQueue.swift; sourceTree = ""; }; DC869A582153B1F60088977E /* OCMockingManagerSwiftExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCMockingManagerSwiftExtension.swift; sourceTree = ""; }; DC89C45C20860B5D0044BCAE /* ProgressSummarizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressSummarizer.swift; sourceTree = ""; }; + DC98BBC920FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSNumber+OCSyncAnchorData.h"; sourceTree = ""; }; + DC98BBCA20FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSNumber+OCSyncAnchorData.m"; sourceTree = ""; }; + DC98BBD220FF824600F4ED3E /* FileProviderEnumeratorObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileProviderEnumeratorObserver.h; sourceTree = ""; }; + DC98BBD320FF824600F4ED3E /* FileProviderEnumeratorObserver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileProviderEnumeratorObserver.m; sourceTree = ""; }; DC9BFBB220A19AF3007064B5 /* doc */ = {isa = PBXFileReference; lastKnownFileType = folder; path = doc; sourceTree = ""; }; DC9BFBB820A1AF2B007064B5 /* icon-locked.tvg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "icon-locked.tvg"; path = "img/filetypes-tvg/icon-locked.tvg"; sourceTree = SOURCE_ROOT; }; DC9BFBBA20A1B3CA007064B5 /* icon-password-manager.tvg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "icon-password-manager.tvg"; path = "img/filetypes-tvg/icon-password-manager.tvg"; sourceTree = SOURCE_ROOT; }; @@ -431,6 +481,18 @@ DCB44D7C2186F0F600DAA4CC /* ThemeStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeStyle.swift; sourceTree = ""; }; DCB44D842186FEF700DAA4CC /* ThemeStyle+DefaultStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThemeStyle+DefaultStyles.swift"; sourceTree = ""; }; DCB44D86218718BA00DAA4CC /* VendorServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VendorServices.swift; sourceTree = ""; }; + DCC6564620C9B7E300110A97 /* ownCloud File Provider.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "ownCloud File Provider.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + DCC6564820C9B7E400110A97 /* FileProviderExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileProviderExtension.h; sourceTree = ""; }; + DCC6564920C9B7E400110A97 /* FileProviderExtension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileProviderExtension.m; sourceTree = ""; }; + DCC6564E20C9B7E400110A97 /* FileProviderEnumerator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileProviderEnumerator.h; sourceTree = ""; }; + DCC6564F20C9B7E400110A97 /* FileProviderEnumerator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileProviderEnumerator.m; sourceTree = ""; }; + DCC6565120C9B7E400110A97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DCC6565220C9B7E400110A97 /* ownCloud_File_Provider.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ownCloud_File_Provider.entitlements; sourceTree = ""; }; + DCC6565720C9B7E400110A97 /* ownCloud File ProviderUI.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "ownCloud File ProviderUI.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + DCC6565920C9B7E400110A97 /* DocumentActionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DocumentActionViewController.h; sourceTree = ""; }; + DCC6565A20C9B7E400110A97 /* DocumentActionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DocumentActionViewController.m; sourceTree = ""; }; + DCC6565D20C9B7E400110A97 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + DCC6565F20C9B7E400110A97 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DCD344A5205BD0C000189B9A /* openssl.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = openssl.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DCD344AF205BD0FA00189B9A /* openssl.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = openssl.xcodeproj; path = "ios-sdk/ownCloudUI/openssl/framework/openssl.xcodeproj"; sourceTree = ""; }; DCE5E88C2080D77E005F60CE /* video.tvg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = video.tvg; path = "img/filetypes-tvg/video.tvg"; sourceTree = SOURCE_ROOT; }; @@ -495,6 +557,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DCC6564320C9B7E300110A97 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DC27A19120CAA0BA008ACB6C /* MobileCoreServices.framework in Frameworks */, + DC27A18F20CAA0BA008ACB6C /* ownCloudSDK.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCC6565420C9B7E400110A97 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -518,6 +596,8 @@ DC7DBA10207F59C500E7337D /* External */, 233BDEB3204FEFE500C06732 /* ownCloudTests */, DC7DBA3B207F86E900E7337D /* Tools */, + DCC6564720C9B7E400110A97 /* ownCloud File Provider */, + DCC6565820C9B7E400110A97 /* ownCloud File ProviderUI */, 233BDE9D204FEFE500C06732 /* Products */, DC85573220513CC700189B9A /* Frameworks */, 5EE06126BB49344475598790 /* Pods */, @@ -531,6 +611,8 @@ 233BDE9C204FEFE500C06732 /* ownCloud.app */, 233BDEB0204FEFE500C06732 /* ownCloudTests.xctest */, DC7DBA34207F84BF00E7337D /* MakeTVG */, + DCC6564620C9B7E300110A97 /* ownCloud File Provider.appex */, + DCC6565720C9B7E400110A97 /* ownCloud File ProviderUI.appex */, ); name = Products; sourceTree = ""; @@ -549,6 +631,7 @@ 239F1314205A69240029F186 /* UIKit Extensions */, DCE5E8B62080D8B8005F60CE /* SDK Extensions */, DCF4F1872052BA3500189B9A /* Tools */, + DC27A19B20CAB5D7008ACB6C /* FileProvider Integration */, DC85573120513C7500189B9A /* Resources */, DC0B37952051541C00189B9A /* ownCloud.entitlements */, ); @@ -700,6 +783,14 @@ path = "Issues Subclasses"; sourceTree = ""; }; + DC27A19B20CAB5D7008ACB6C /* FileProvider Integration */ = { + isa = PBXGroup; + children = ( + DC27A19C20CAB602008ACB6C /* FileProviderInterfaceManager.swift */, + ); + path = "FileProvider Integration"; + sourceTree = ""; + }; DC3BE0DB2077CC13002A0AC0 /* Client */ = { isa = PBXGroup; children = ( @@ -848,6 +939,42 @@ path = Progress; sourceTree = ""; }; + DCC6564720C9B7E400110A97 /* ownCloud File Provider */ = { + isa = PBXGroup; + children = ( + DCC6564920C9B7E400110A97 /* FileProviderExtension.m */, + DCC6564820C9B7E400110A97 /* FileProviderExtension.h */, + DC27A1E820CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.m */, + DC27A1E720CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.h */, + DC27A18D20CA9F66008ACB6C /* OCItem+FileProviderItem.m */, + DC27A18C20CA9F66008ACB6C /* OCItem+FileProviderItem.h */, + DC27A1A420CBEF85008ACB6C /* OCBookmark+FileProvider.m */, + DC27A1A320CBEF85008ACB6C /* OCBookmark+FileProvider.h */, + DC27A1A720CC095C008ACB6C /* OCCore+FileProviderTools.m */, + DC27A1A620CC095C008ACB6C /* OCCore+FileProviderTools.h */, + DCC6564F20C9B7E400110A97 /* FileProviderEnumerator.m */, + DCC6564E20C9B7E400110A97 /* FileProviderEnumerator.h */, + DC98BBD320FF824600F4ED3E /* FileProviderEnumeratorObserver.m */, + DC98BBD220FF824600F4ED3E /* FileProviderEnumeratorObserver.h */, + DC98BBCA20FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.m */, + DC98BBC920FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.h */, + DCC6565120C9B7E400110A97 /* Info.plist */, + DCC6565220C9B7E400110A97 /* ownCloud_File_Provider.entitlements */, + ); + path = "ownCloud File Provider"; + sourceTree = ""; + }; + DCC6565820C9B7E400110A97 /* ownCloud File ProviderUI */ = { + isa = PBXGroup; + children = ( + DCC6565A20C9B7E400110A97 /* DocumentActionViewController.m */, + DCC6565920C9B7E400110A97 /* DocumentActionViewController.h */, + DCC6565C20C9B7E400110A97 /* MainInterface.storyboard */, + DCC6565F20C9B7E400110A97 /* Info.plist */, + ); + path = "ownCloud File ProviderUI"; + sourceTree = ""; + }; DCCE54092080175B00067D1D /* TVGs */ = { isa = PBXGroup; children = ( @@ -970,6 +1097,8 @@ DC3BE0CD2077BC52002A0AC0 /* PBXTargetDependency */, DC3BE0CF2077BC52002A0AC0 /* PBXTargetDependency */, DC3BE0D12077BC52002A0AC0 /* PBXTargetDependency */, + DCC6566120C9B7E400110A97 /* PBXTargetDependency */, + DCC6566420C9B7E400110A97 /* PBXTargetDependency */, ); name = ownCloud; productName = ownCloud; @@ -1015,6 +1144,41 @@ productReference = DC7DBA34207F84BF00E7337D /* MakeTVG */; productType = "com.apple.product-type.tool"; }; + DCC6564520C9B7E300110A97 /* ownCloud File Provider */ = { + isa = PBXNativeTarget; + buildConfigurationList = DCC6566F20C9B7E400110A97 /* Build configuration list for PBXNativeTarget "ownCloud File Provider" */; + buildPhases = ( + DCC6564220C9B7E300110A97 /* Sources */, + DCC6564320C9B7E300110A97 /* Frameworks */, + DCC6564420C9B7E300110A97 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DC27A19320CAA0C6008ACB6C /* PBXTargetDependency */, + ); + name = "ownCloud File Provider"; + productName = "ownCloud File Provider"; + productReference = DCC6564620C9B7E300110A97 /* ownCloud File Provider.appex */; + productType = "com.apple.product-type.app-extension"; + }; + DCC6565620C9B7E400110A97 /* ownCloud File ProviderUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = DCC6566E20C9B7E400110A97 /* Build configuration list for PBXNativeTarget "ownCloud File ProviderUI" */; + buildPhases = ( + DCC6565320C9B7E400110A97 /* Sources */, + DCC6565420C9B7E400110A97 /* Frameworks */, + DCC6565520C9B7E400110A97 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ownCloud File ProviderUI"; + productName = "ownCloud File ProviderUI"; + productReference = DCC6565720C9B7E400110A97 /* ownCloud File ProviderUI.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -1052,6 +1216,19 @@ CreatedOnToolsVersion = 9.3; ProvisioningStyle = Automatic; }; + DCC6564520C9B7E300110A97 = { + CreatedOnToolsVersion = 9.4; + ProvisioningStyle = Manual; + SystemCapabilities = { + com.apple.Keychain = { + enabled = 1; + }; + }; + }; + DCC6565620C9B7E400110A97 = { + CreatedOnToolsVersion = 9.4; + ProvisioningStyle = Manual; + }; }; }; buildConfigurationList = 233BDE97204FEFE500C06732 /* Build configuration list for PBXProject "ownCloud" */; @@ -1085,6 +1262,8 @@ 233BDE9B204FEFE500C06732 /* ownCloud */, 233BDEAF204FEFE500C06732 /* ownCloudTests */, DC7DBA33207F84BF00E7337D /* MakeTVG */, + DCC6564520C9B7E300110A97 /* ownCloud File Provider */, + DCC6565620C9B7E400110A97 /* ownCloud File ProviderUI */, ); }; /* End PBXProject section */ @@ -1216,6 +1395,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DCC6564420C9B7E300110A97 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCC6565520C9B7E400110A97 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DCC6565E20C9B7E400110A97 /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -1360,6 +1554,7 @@ DCFED972208095E200A2D984 /* ClientItemCell.swift in Sources */, 23E22BB720C6A5C40024D11E /* UIDevice+UIUserInterfaceIdiom.swift in Sources */, 23F6238120B587EF004FDE8B /* SortMethod.swift in Sources */, + DC27A19D20CAB602008ACB6C /* FileProviderInterfaceManager.swift in Sources */, 23EC77582137F3DD0032D4E6 /* PDFViewerViewController.swift in Sources */, 239F1319205A693A0029F186 /* UIColor+Extension.swift in Sources */, 23EC775B2137F3DD0032D4E6 /* OCExtensionType+Extension.swift in Sources */, @@ -1394,6 +1589,7 @@ DC434D1320D7A8F100740056 /* UIAlertController+OCConnectionIssue.swift in Sources */, DC3317CE2084966700E36C8F /* ThemeTableViewCell.swift in Sources */, 6E83C77E20A32C1B0066EC23 /* SettingsSection.swift in Sources */, + DC0196AB20F7690C00C41B78 /* OCBookmark+FileProvider.m in Sources */, DC422450207CB2500006A2A6 /* NSObject+ThemeApplication.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1418,6 +1614,29 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DCC6564220C9B7E300110A97 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DC98BBD420FF824600F4ED3E /* FileProviderEnumeratorObserver.m in Sources */, + DCC6565020C9B7E400110A97 /* FileProviderEnumerator.m in Sources */, + DC27A1E920CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.m in Sources */, + DC27A1A820CC095C008ACB6C /* OCCore+FileProviderTools.m in Sources */, + DCC6564A20C9B7E400110A97 /* FileProviderExtension.m in Sources */, + DC27A1A520CBEF85008ACB6C /* OCBookmark+FileProvider.m in Sources */, + DC27A18E20CA9F66008ACB6C /* OCItem+FileProviderItem.m in Sources */, + DC98BBCB20FF815C00F4ED3E /* NSNumber+OCSyncAnchorData.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCC6565320C9B7E400110A97 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DCC6565B20C9B7E400110A97 /* DocumentActionViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -1431,6 +1650,11 @@ name = ownCloudMocking; targetProxy = DC188986218A772900CFB3F9 /* PBXContainerItemProxy */; }; + DC27A19320CAA0C6008ACB6C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ownCloudSDK; + targetProxy = DC27A19220CAA0C6008ACB6C /* PBXContainerItemProxy */; + }; DC3BE0CD2077BC52002A0AC0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = openssl; @@ -1451,6 +1675,16 @@ name = "PocketSVG (iOS)"; targetProxy = DC7DBA1D207F59F200E7337D /* PBXContainerItemProxy */; }; + DCC6566120C9B7E400110A97 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DCC6565620C9B7E400110A97 /* ownCloud File ProviderUI */; + targetProxy = DCC6566020C9B7E400110A97 /* PBXContainerItemProxy */; + }; + DCC6566420C9B7E400110A97 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DCC6564520C9B7E300110A97 /* ownCloud File Provider */; + targetProxy = DCC6566320C9B7E400110A97 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -1480,6 +1714,14 @@ name = InfoPlist.strings; sourceTree = ""; }; + DCC6565C20C9B7E400110A97 /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + DCC6565D20C9B7E400110A97 /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -1715,6 +1957,88 @@ }; name = Release; }; + DCC6566620C9B7E400110A97 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_ENTITLEMENTS = "ownCloud File Provider/ownCloud_File_Provider.entitlements"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = 4AP2STM4H5; + INFOPLIST_FILE = "ownCloud File Provider/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.owncloud.ios-app.ownCloud-File-Provider"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "b9cd7516-5bdd-4c15-b338-55d02d1ed4aa"; + PROVISIONING_PROFILE_SPECIFIER = "match Development com.owncloud.ios-app.ownCloud-File-Provider"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + DCC6566720C9B7E400110A97 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_ENTITLEMENTS = "ownCloud File Provider/ownCloud_File_Provider.entitlements"; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = 4AP2STM4H5; + INFOPLIST_FILE = "ownCloud File Provider/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.owncloud.ios-app.ownCloud-File-Provider"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "0be66300-94bb-43be-8e1f-6aee08a7c154"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.owncloud.ios-app.ownCloud-File-Provider"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + DCC6566820C9B7E400110A97 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = 4AP2STM4H5; + INFOPLIST_FILE = "ownCloud File ProviderUI/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.owncloud.ios-app.ownCloud-File-ProviderUI"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "2fcc6dc1-dade-404b-b2c3-4ba9674f5680"; + PROVISIONING_PROFILE_SPECIFIER = "match Development com.owncloud.ios-app.ownCloud-File-ProviderUI"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + DCC6566920C9B7E400110A97 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = 4AP2STM4H5; + INFOPLIST_FILE = "ownCloud File ProviderUI/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.owncloud.ios-app.ownCloud-File-ProviderUI"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "fe9f450c-3f81-4041-903f-b72a48954466"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.owncloud.ios-app.ownCloud-File-ProviderUI"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1754,6 +2078,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + DCC6566E20C9B7E400110A97 /* Build configuration list for PBXNativeTarget "ownCloud File ProviderUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DCC6566820C9B7E400110A97 /* Debug */, + DCC6566920C9B7E400110A97 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DCC6566F20C9B7E400110A97 /* Build configuration list for PBXNativeTarget "ownCloud File Provider" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DCC6566620C9B7E400110A97 /* Debug */, + DCC6566720C9B7E400110A97 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 233BDE94204FEFE500C06732 /* Project object */; diff --git a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme index 381aec9f2..a8f6cc6a5 100644 --- a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme +++ b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud.xcscheme @@ -55,6 +55,11 @@ value = "false" isEnabled = "YES"> + + @@ -90,6 +95,11 @@ value = "0" isEnabled = "YES"> + + . + * + */ + +import UIKit +import ownCloudSDK + +class FileProviderInterfaceManager: NSObject { + static let shared : FileProviderInterfaceManager = { + var manager = FileProviderInterfaceManager() + + return (manager) + }() + + override init() { + super.init() + + NotificationCenter.default.addObserver(self, selector: #selector(self.bookmarksChanged), name: Notification.Name.OCBookmarkManagerListChanged, object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self, name: Notification.Name.OCBookmarkManagerListChanged, object: nil) + } + + @objc func bookmarksChanged() { + OnMainThread { + self.updateDomainsFromBookmarks() + } + } + + func updateDomainsFromBookmarks() { + NSFileProviderManager.getDomainsWithCompletionHandler { (fileProviderDomains, error) in + OnMainThread { + if error != nil { + Log.error("Error getting domains: \(String(describing: error))") + return + } + + if let bookmarks = OCBookmarkManager.shared.bookmarks as? [OCBookmark] { + var bookmarkUUIDStrings : [String] = [] + var bookmarksByUUIDString : [String:OCBookmark] = [:] + var displayNamesByUUIDString : [String:String] = [:] + var usedBookmarkNames : Set = Set() + let waitForManagerGroup = DispatchGroup() + + // Collect info on bookmarks + for bookmark in bookmarks { + let bookmarkUUIDString = bookmark.uuid.uuidString + + bookmarkUUIDStrings.append(bookmarkUUIDString) + bookmarksByUUIDString[bookmarkUUIDString] = bookmark + + // Make sure displayName is unique + var displayName = bookmark.shortName + var iteration = 1 + + while usedBookmarkNames.contains(displayName) { + iteration += 1 + displayName = bookmark.shortName + " \(iteration)" + } + + usedBookmarkNames.insert(displayName) + displayNamesByUUIDString[bookmarkUUIDString] = displayName + } + + for domain in fileProviderDomains { + let domainIdentifierString = domain.identifier.rawValue + var removeDomain : Bool = false + + if let removeAtIndex = bookmarkUUIDStrings.index(of: domainIdentifierString) { + // Domain is already registered for this bookmark -> check if name also still matches + if displayNamesByUUIDString[domainIdentifierString] == domain.displayName { + // Identical -> no changes needed for this bookmark + bookmarkUUIDStrings.remove(at: removeAtIndex) + } else { + // Different -> remove + removeDomain = true + } + } else { + // Domain is no longer backed by a bookmark -> remove + removeDomain = true + } + + if removeDomain { + waitForManagerGroup.enter() + + NSFileProviderManager.remove(domain, completionHandler: { (error) in + if error != nil { + Log.error("Error removing domain: \(domain) error: \(String(describing: error))") + } + waitForManagerGroup.leave() + }) + } + } + + // Wait for NSFileProviderManager operations to settle + _ = waitForManagerGroup.wait(timeout: DispatchTime.distantFuture) + + // Add domains for bookmarks + for bookmarkUUIDToAdd in bookmarkUUIDStrings { + if let bookmark = bookmarksByUUIDString[bookmarkUUIDToAdd] { + // Create new domain + let newDomain = NSFileProviderDomain(identifier: NSFileProviderDomainIdentifier(rawValue: bookmarkUUIDToAdd), + displayName: displayNamesByUUIDString[bookmarkUUIDToAdd] ?? bookmark.shortName, + pathRelativeToDocumentStorage: bookmarkUUIDToAdd) + + waitForManagerGroup.enter() + + NSFileProviderManager.add(newDomain, completionHandler: { (error) in + if error != nil { + Log.error("Error adding domain: \(newDomain) error: \(String(describing: error))") + } + waitForManagerGroup.leave() + }) + } + } + + // Wait for NSFileProviderManager operations to settle + _ = waitForManagerGroup.wait(timeout: DispatchTime.distantFuture) + } + } + } + } +} diff --git a/ownCloud/Resources/Info.plist b/ownCloud/Resources/Info.plist index ffa505107..b249fad81 100644 --- a/ownCloud/Resources/Info.plist +++ b/ownCloud/Resources/Info.plist @@ -37,6 +37,8 @@ $(AppIdentifierPrefix) OCKeychainAccessGroupIdentifier group.$(PRODUCT_BUNDLE_IDENTIFIER) + OCHasFileProvider + UIBackgroundModes fetch diff --git a/ownCloud/SDK Extensions/OCBookmark+Extension.swift b/ownCloud/SDK Extensions/OCBookmark+Extension.swift index 7e9184525..6fb10fd2b 100644 --- a/ownCloud/SDK Extensions/OCBookmark+Extension.swift +++ b/ownCloud/SDK Extensions/OCBookmark+Extension.swift @@ -32,18 +32,20 @@ extension OCBookmark { } var shortName: String { - var userNamePrefix = "" - - if let userName = self.userName { - userNamePrefix = userName + " @ " - } - if self.name != nil { return self.name - } else if self.originURL?.host != nil { - return userNamePrefix + self.originURL.host! - } else if self.url?.host != nil { - return userNamePrefix + self.url.host! + } else { + var userNamePrefix = "" + + if let userName = self.userName { + userNamePrefix = userName + " @ " + } + + if self.originURL?.host != nil { + return userNamePrefix + self.originURL.host! + } else if self.url?.host != nil { + return userNamePrefix + self.url.host! + } } return "bookmark" diff --git a/ownCloud/Settings/LogSettingsViewController.swift b/ownCloud/Settings/LogSettingsViewController.swift index dff458cc1..d560cb8ae 100644 --- a/ownCloud/Settings/LogSettingsViewController.swift +++ b/ownCloud/Settings/LogSettingsViewController.swift @@ -121,7 +121,7 @@ class LogSettingsViewController: StaticTableViewController { logOutputSection?.add(row: row) } - logOutputSection?.add(row: StaticTableViewRow(buttonWithAction: { (row, _) in + logOutputSection?.add(row: StaticTableViewRow(buttonWithAction: { [weak self] (row, _) in if let logFileWriter = OCLogger.shared.writer(withIdentifier: .file) as? OCLogFileWriter { if !FileManager.default.fileExists(atPath: logFileWriter.logFileURL.path) { let alert = UIAlertController(title: "No log file found".localized, message: "The log file can't be shared because no log file could be found or the log file is empty.".localized, preferredStyle: .alert) @@ -135,7 +135,7 @@ class LogSettingsViewController: StaticTableViewController { })) } - self.present(alert, animated: true, completion: nil) + self?.present(alert, animated: true, completion: nil) } else { let logURL = FileManager.default.temporaryDirectory.appendingPathComponent("ownCloud App Log.txt") diff --git a/ownCloud/Settings/MoreSettingsSection.swift b/ownCloud/Settings/MoreSettingsSection.swift index a86f65063..dc0a1ebca 100644 --- a/ownCloud/Settings/MoreSettingsSection.swift +++ b/ownCloud/Settings/MoreSettingsSection.swift @@ -47,9 +47,9 @@ class MoreSettingsSection: SettingsSection { private func createRows() { - helpRow = StaticTableViewRow(rowWithAction: { (_, _) in + helpRow = StaticTableViewRow(rowWithAction: { [weak self] (_, _) in let url = URL(string: "https://www.owncloud.com/help") - self.openSFWebViewWithConfirmation(for: url!) + self?.openSFWebViewWithConfirmation(for: url!) }, title: "Help".localized, accessoryType: .disclosureIndicator) sendFeedbackRow = StaticTableViewRow(rowWithAction: { [weak self] (_, _) in @@ -64,9 +64,9 @@ class MoreSettingsSection: SettingsSection { } }, title: "Recommend to a friend".localized, accessoryType: .disclosureIndicator) - privacyPolicyRow = StaticTableViewRow(rowWithAction: { (_, _) in + privacyPolicyRow = StaticTableViewRow(rowWithAction: { [weak self] (_, _) in let url = URL(string: "https://owncloud.org/privacy-policy/") - self.openSFWebViewWithConfirmation(for: url!) + self?.openSFWebViewWithConfirmation(for: url!) }, title: "Privacy Policy".localized, accessoryType: .disclosureIndicator) acknowledgementsRow = StaticTableViewRow(rowWithAction: { (row, _) in diff --git a/ownCloud/Settings/SecuritySettingsSection.swift b/ownCloud/Settings/SecuritySettingsSection.swift index 2ca73bd95..84e8d0f07 100644 --- a/ownCloud/Settings/SecuritySettingsSection.swift +++ b/ownCloud/Settings/SecuritySettingsSection.swift @@ -99,8 +99,8 @@ class SecuritySettingsSection: SettingsSection { func createRows() { // Creation of the frequency row. - frequencyRow = StaticTableViewRow(subtitleRowWithAction: { (row, _) in - if let vc = self.viewController { + frequencyRow = StaticTableViewRow(subtitleRowWithAction: { [weak self] (row, _) in + if let vc = self?.viewController { let newVC = StaticTableViewController(style: .grouped) let frequencySection = StaticTableViewSection(headerTitle: "Lock application".localized, footerTitle: nil) @@ -113,10 +113,10 @@ class SecuritySettingsSection: SettingsSection { frequencySection.add(radioGroupWithArrayOfLabelValueDictionaries: radioButtons, radioAction: { (row, _) in if let rawFrequency = row.value! as? Int, let frequency = SecurityAskFrequency.init(rawValue: rawFrequency) { - self.frequency = frequency - self.frequencyRow?.cell?.detailTextLabel?.text = frequency.toString() + self?.frequency = frequency + self?.frequencyRow?.cell?.detailTextLabel?.text = frequency.toString() } - }, groupIdentifier: "frequency-group-identifier", selectedValue: self.frequency.rawValue, animated: true) + }, groupIdentifier: "frequency-group-identifier", selectedValue: self!.frequency.rawValue, animated: true) newVC.addSection(frequencySection) vc.navigationController?.pushViewController(newVC, animated: true) @@ -125,9 +125,9 @@ class SecuritySettingsSection: SettingsSection { }, title: "Lock application".localized, subtitle: frequency.toString(), accessoryType: .disclosureIndicator, identifier: "lockFrequency") // Creation of the passcode row. - passcodeRow = StaticTableViewRow(switchWithAction: { (_, sender) in + passcodeRow = StaticTableViewRow(switchWithAction: { [weak self] (row, sender) in if let passcodeSwitch = sender as? UISwitch { - if let viewController = self.viewController { + if let viewController = row.viewController { var passcodeViewController: PasscodeViewController? var defaultMessage : String? @@ -135,9 +135,9 @@ class SecuritySettingsSection: SettingsSection { // Handlers let cancelHandler:PasscodeViewControllerCancelHandler = { (passcodeViewController: PasscodeViewController) in passcodeViewController.dismiss(animated: true, completion: { - self.isPasscodeSecurityEnabled = !passcodeSwitch.isOn + self?.isPasscodeSecurityEnabled = !passcodeSwitch.isOn }) - self.passcodeFromFirstStep = nil + self?.passcodeFromFirstStep = nil } if passcodeSwitch.isOn { @@ -153,8 +153,8 @@ class SecuritySettingsSection: SettingsSection { // Success AppLockManager.shared.passcode = nil passcodeViewController.dismiss(animated: true, completion: { - self.isPasscodeSecurityEnabled = passcodeSwitch.isOn - self.updateUI() + self?.isPasscodeSecurityEnabled = passcodeSwitch.isOn + self?.updateUI() }) } else { // Error @@ -164,20 +164,20 @@ class SecuritySettingsSection: SettingsSection { } } else { // Add - if self.passcodeFromFirstStep == nil { + if self?.passcodeFromFirstStep == nil { // First step - self.passcodeFromFirstStep = passcode + self?.passcodeFromFirstStep = passcode passcodeViewController.message = "Repeat code".localized passcodeViewController.passcode = nil } else { // Second step - if self.passcodeFromFirstStep == passcode { + if self?.passcodeFromFirstStep == passcode { // Passcode right // Save to keychain AppLockManager.shared.passcode = passcode passcodeViewController.dismiss(animated: true, completion: { - self.isPasscodeSecurityEnabled = passcodeSwitch.isOn - self.updateUI() + self?.isPasscodeSecurityEnabled = passcodeSwitch.isOn + self?.updateUI() }) } else { //Passcode is not the same @@ -185,7 +185,7 @@ class SecuritySettingsSection: SettingsSection { passcodeViewController.errorMessage = "The entered codes are different".localized passcodeViewController.passcode = nil } - self.passcodeFromFirstStep = nil + self?.passcodeFromFirstStep = nil } } }) @@ -198,20 +198,20 @@ class SecuritySettingsSection: SettingsSection { // Creation of the biometrical row. if let biometricalSecurityName = LAContext().supportedBiometricsAuthenticationName() { - biometricalRow = StaticTableViewRow(switchWithAction: { (_, sender) in + biometricalRow = StaticTableViewRow(switchWithAction: { [weak self] (row, sender) in if let biometricalSwitch = sender as? UISwitch { - if let viewController = self.viewController { + if let viewController = row.viewController { var passcodeViewController: PasscodeViewController? passcodeViewController = PasscodeViewController(cancelHandler: { (passcodeViewController: PasscodeViewController) in passcodeViewController.dismiss(animated: true, completion: { - biometricalSwitch.setOn(self.isBiometricalSecurityEnabled, animated: true) + biometricalSwitch.setOn(self!.isBiometricalSecurityEnabled, animated: true) }) }, completionHandler: { (passcodeViewController: PasscodeViewController, passcode: String) in if passcode == AppLockManager.shared.passcode { // Success passcodeViewController.dismiss(animated: true, completion: { - self.isBiometricalSecurityEnabled = biometricalSwitch.isOn + self?.isBiometricalSecurityEnabled = biometricalSwitch.isOn }) } else { // Error diff --git a/ownCloud/Settings/UploadsSettingsSection.swift b/ownCloud/Settings/UploadsSettingsSection.swift index 2a55417c5..f670a7a8a 100644 --- a/ownCloud/Settings/UploadsSettingsSection.swift +++ b/ownCloud/Settings/UploadsSettingsSection.swift @@ -150,15 +150,15 @@ class UploadsSettingsSection: SettingsSection { private func createPhotoRows() { - photosRow = StaticTableViewRow(switchWithAction: { (_, sender) in + photosRow = StaticTableViewRow(switchWithAction: { [weak self] (_, sender) in if let photosSwitch = sender as? UISwitch { - self.isPhotoUploadEnabled = photosSwitch.isOn + self?.isPhotoUploadEnabled = photosSwitch.isOn } }, title: "Photos".localized, value: isPhotoUploadEnabled) - photosWifiOnlyRow = StaticTableViewRow(switchWithAction: { (_, sender) in + photosWifiOnlyRow = StaticTableViewRow(switchWithAction: { [weak self] (_, sender) in if let photosWifiOnlySwitch = sender as? UISwitch { - self.isPhotoWifiOnlyUploadsEnabled = photosWifiOnlySwitch.isOn + self?.isPhotoWifiOnlyUploadsEnabled = photosWifiOnlySwitch.isOn } }, title: "Upload pictures via WiFi only".localized, value: isPhotoWifiOnlyUploadsEnabled) @@ -169,15 +169,15 @@ class UploadsSettingsSection: SettingsSection { private func createVideoRows() { - videosRow = StaticTableViewRow(switchWithAction: { (_, sender) in + videosRow = StaticTableViewRow(switchWithAction: { [weak self] (_, sender) in if let videosSwitch = sender as? UISwitch { - self.isVideoUploadEnabled = videosSwitch.isOn + self?.isVideoUploadEnabled = videosSwitch.isOn } }, title: "Videos".localized, value: isVideoUploadEnabled) - videosWifiOnlyRow = StaticTableViewRow(switchWithAction: { (_, sender) in + videosWifiOnlyRow = StaticTableViewRow(switchWithAction: { [weak self] (_, sender) in if let videosWifiOnlySwitch = sender as? UISwitch { - self.isVideoWifiOnlyUploadsEnabled = videosWifiOnlySwitch.isOn + self?.isVideoWifiOnlyUploadsEnabled = videosWifiOnlySwitch.isOn } }, title: "Upload videos via WiFi only".localized, value: isVideoWifiOnlyUploadsEnabled) @@ -187,9 +187,9 @@ class UploadsSettingsSection: SettingsSection { } private func createCommonRows() { - backgroundUploadsRow = StaticTableViewRow(switchWithAction: { (_, sender) in + backgroundUploadsRow = StaticTableViewRow(switchWithAction: { [weak self] (_, sender) in if let backgroundsSwitch = sender as? UISwitch { - self.backgroundUploadsEnabled = backgroundsSwitch.isOn + self?.backgroundUploadsEnabled = backgroundsSwitch.isOn } }, title: "Background uploads".localized, value: backgroundUploadsEnabled) } diff --git a/ownCloud/Settings/UserInterfaceSettingsSection.swift b/ownCloud/Settings/UserInterfaceSettingsSection.swift index df4c54b37..79d49197e 100644 --- a/ownCloud/Settings/UserInterfaceSettingsSection.swift +++ b/ownCloud/Settings/UserInterfaceSettingsSection.swift @@ -91,5 +91,4 @@ class UserInterfaceSettingsSection: SettingsSection { func pushLogSettings() { self.viewController?.navigationController?.pushViewController(LogSettingsViewController(style: .grouped), animated: true) } - } diff --git a/ownCloud/Theming/Theme.swift b/ownCloud/Theming/Theme.swift index 0afbe0be5..4d2f1c761 100644 --- a/ownCloud/Theming/Theme.swift +++ b/ownCloud/Theming/Theme.swift @@ -31,7 +31,7 @@ protocol Themeable : class { typealias ThemeApplier = (_ theme : Theme, _ ThemeCollection: ThemeCollection, _ event: ThemeEvent) -> Void typealias ThemeApplierToken = Int -struct WeakThemeable { +final class WeakThemeable { weak var weakClient : Themeable? init(_ client: Themeable) { diff --git a/ownCloud/UI Elements/Card Presentation Controller/CardTransitionDelegate.swift b/ownCloud/UI Elements/Card Presentation Controller/CardTransitionDelegate.swift index f9261a6c3..98dbd2c5f 100644 --- a/ownCloud/UI Elements/Card Presentation Controller/CardTransitionDelegate.swift +++ b/ownCloud/UI Elements/Card Presentation Controller/CardTransitionDelegate.swift @@ -19,7 +19,7 @@ import UIKit final class CardTransitionDelegate: NSObject, UIViewControllerTransitioningDelegate { - private let vcToPresent: UIViewController + private weak var vcToPresent: UIViewController? private weak var alreadyPresentedVC: UIViewController? init(viewControllerToPresent: UIViewController, presentingViewController: UIViewController) {