diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj index f6e94d704..c381dfb0b 100644 --- a/Cryptomator.xcodeproj/project.pbxproj +++ b/Cryptomator.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 4A09E54E27071F4F0056D32A /* ErrorMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A09E54D27071F4F0056D32A /* ErrorMapper.swift */; }; 4A0C07E225AC80C100B83211 /* UIView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0C07E125AC80C100B83211 /* UIView+Preview.swift */; }; 4A0C07EB25AC832900B83211 /* VaultListPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0C07EA25AC832900B83211 /* VaultListPosition.swift */; }; + 4A0EAAD2296F604200E27B56 /* SessionTaskRegistratorMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0EAAD1296F604200E27B56 /* SessionTaskRegistratorMock.swift */; }; 4A123EA824BEF5F0001D1CF7 /* CloudProviderPaginationMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A123EA724BEF5F0001D1CF7 /* CloudProviderPaginationMock.swift */; }; 4A136124276767D60077EB7F /* Snapshots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A136123276767D60077EB7F /* Snapshots.swift */; }; 4A13612D276768000077EB7F /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A13612C276768000077EB7F /* SnapshotHelper.swift */; }; @@ -190,6 +191,7 @@ 4A717CD924C835740048E08F /* ReparentTaskManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A717CD824C835740048E08F /* ReparentTaskManagerTests.swift */; }; 4A74337A28B3E3AB00AECD21 /* WebDAVAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A74337928B3E3AB00AECD21 /* WebDAVAuthentication.swift */; }; 4A74DBB1282132EC00A332C4 /* FileProviderAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A74DBB0282132EC00A332C4 /* FileProviderAction.swift */; }; + 4A7514A12937F777002E802E /* SessionTaskRegistrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7514A02937F777002E802E /* SessionTaskRegistrator.swift */; }; 4A753DB92678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */; }; 4A773907286D86C20006B3C3 /* S3AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A773906286D86C20006B3C3 /* S3AuthenticationViewModel.swift */; }; 4A773909286D87AB0006B3C3 /* S3AuthenticationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A773908286D87AB0006B3C3 /* S3AuthenticationViewModelTests.swift */; }; @@ -545,6 +547,7 @@ 4A09E54D27071F4F0056D32A /* ErrorMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMapper.swift; sourceTree = ""; }; 4A0C07E125AC80C100B83211 /* UIView+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Preview.swift"; sourceTree = ""; }; 4A0C07EA25AC832900B83211 /* VaultListPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultListPosition.swift; sourceTree = ""; }; + 4A0EAAD1296F604200E27B56 /* SessionTaskRegistratorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTaskRegistratorMock.swift; sourceTree = ""; }; 4A123EA724BEF5F0001D1CF7 /* CloudProviderPaginationMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudProviderPaginationMock.swift; sourceTree = ""; }; 4A136121276767D60077EB7F /* Snapshots.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Snapshots.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 4A136123276767D60077EB7F /* Snapshots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Snapshots.swift; sourceTree = ""; }; @@ -715,6 +718,7 @@ 4A717CD824C835740048E08F /* ReparentTaskManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReparentTaskManagerTests.swift; sourceTree = ""; }; 4A74337928B3E3AB00AECD21 /* WebDAVAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebDAVAuthentication.swift; sourceTree = ""; }; 4A74DBB0282132EC00A332C4 /* FileProviderAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderAction.swift; sourceTree = ""; }; + 4A7514A02937F777002E802E /* SessionTaskRegistrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTaskRegistrator.swift; sourceTree = ""; }; 4A753DB82678A226005F79C1 /* OpenExistingLegacyVaultPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExistingLegacyVaultPasswordViewModel.swift; sourceTree = ""; }; 4A753FBB2832A371006A9C3F /* CryptomatorIntents.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CryptomatorIntents.entitlements; sourceTree = ""; }; 4A773906286D86C20006B3C3 /* S3AuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = S3AuthenticationViewModel.swift; sourceTree = ""; }; @@ -1716,6 +1720,7 @@ 4AA782E3282A9007001A71E3 /* NSFileProviderDomainProviderMock.swift */, 4AEECD3A279EB24300C6E2B5 /* NSFileProviderEnumerationObserverMock.swift */, 4AFBFA172829414A00E30818 /* ProgressManagerMock.swift */, + 4A0EAAD1296F604200E27B56 /* SessionTaskRegistratorMock.swift */, 4ADC66C627A95E67002E6CC7 /* UnlockMonitorTaskExecutorMock.swift */, 4AAD444627E26D1800D16707 /* UploadTaskManagerMock.swift */, 4AEECD30279EA50D00C6E2B5 /* WorkingSetObservingMock.swift */, @@ -1893,6 +1898,7 @@ 4AEE6EE92825716400E1B35E /* ProgressManager.swift */, 4AC1157527F5BD890023F51B /* Promise+AllIgnoringResult.swift */, 4ADD233F26737CD400374E4E /* RootFileProviderItem.swift */, + 4A7514A02937F777002E802E /* SessionTaskRegistrator.swift */, 4ADC66C027A7F426002E6CC7 /* UnlockMonitor.swift */, 740375F52587AEB50023FF53 /* URL+NameCollisionExtension.swift */, 4A74DBAF2821312200A332C4 /* Actions */, @@ -2564,6 +2570,7 @@ 4AA782E4282A9007001A71E3 /* NSFileProviderDomainProviderMock.swift in Sources */, 4A9C8DFD27A007C2000063E4 /* FileProviderNotificatorTests.swift in Sources */, 4AB1C325265CE69700DC7A49 /* DownloadTaskExecutorTests.swift in Sources */, + 4A0EAAD2296F604200E27B56 /* SessionTaskRegistratorMock.swift in Sources */, 4AEECD3B279EB24300C6E2B5 /* NSFileProviderEnumerationObserverMock.swift in Sources */, 4A8F149C266A29E400ADBCE4 /* OnlineItemNameCollisionHandlerTests.swift in Sources */, 4A8F14A2266A302A00ADBCE4 /* FileProviderAdapterTestCase.swift in Sources */, @@ -2925,6 +2932,7 @@ 4AB1D4EC27D0E027009060AB /* LocalURLProviderType.swift in Sources */, 4A511D4E2660FF9E000A0E01 /* WorkflowScheduler.swift in Sources */, 4AD9481A2909A66900072110 /* MaintenanceModeHelperServiceSource.swift in Sources */, + 4A7514A12937F777002E802E /* SessionTaskRegistrator.swift in Sources */, 4A2482352671110A002D9F59 /* DBManagerError.swift in Sources */, 4AA2531B28216E45003B45EE /* ServiceSource.swift in Sources */, 4A2060D5279AF67C00DA6C62 /* WorkingSetObserver.swift in Sources */, diff --git a/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8c452447f..940334a38 100644 --- a/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/cryptomator/cloud-access-swift.git", "state": { "branch": null, - "revision": "5c9e38ec05c6d8ed84547a1369a8a0358b3be9c7", - "version": "1.6.0" + "revision": "64b4a985fa3555aeda55ab118ca2dfee5c0ba53b", + "version": "1.7.0" } }, { diff --git a/CryptomatorCommon/Package.swift b/CryptomatorCommon/Package.swift index e93a39e3c..5617494f4 100644 --- a/CryptomatorCommon/Package.swift +++ b/CryptomatorCommon/Package.swift @@ -26,7 +26,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/cryptomator/cloud-access-swift.git", .upToNextMinor(from: "1.6.0")), + .package(url: "https://github.com/cryptomator/cloud-access-swift.git", .upToNextMinor(from: "1.7.0")), .package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack.git", .upToNextMinor(from: "3.8.0")) ], targets: [ diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/LocalizedCloudProviderDecorator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/LocalizedCloudProviderDecorator.swift index cd64797b9..ff6bf4d60 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/LocalizedCloudProviderDecorator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/LocalizedCloudProviderDecorator.swift @@ -38,8 +38,8 @@ public class LocalizedCloudProviderDecorator: CloudProvider { } } - public func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise { - return delegate.downloadFile(from: cloudPath, to: localURL).recover { error -> Void in + public func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { + return delegate.downloadFile(from: cloudPath, to: localURL, onTaskCreation: onTaskCreation).recover { error -> Void in if let error = error as? CloudProviderError { switch error { case .itemAlreadyExists: @@ -53,8 +53,8 @@ public class LocalizedCloudProviderDecorator: CloudProvider { } } - public func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise { - return delegate.uploadFile(from: localURL, to: cloudPath, replaceExisting: replaceExisting).recover { error -> CloudItemMetadata in + public func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise { + return delegate.uploadFile(from: localURL, to: cloudPath, replaceExisting: replaceExisting, onTaskCreation: onTaskCreation).recover { error -> CloudItemMetadata in if let error = error as? CloudProviderError { switch error { case .itemNotFound, .itemTypeMismatch: diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/CloudProviderDBManager.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/CloudProviderDBManager.swift index a5c852c9c..4a52aee6e 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/CloudProviderDBManager.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/CloudProviderDBManager.swift @@ -8,6 +8,7 @@ import CryptomatorCloudAccessCore import Foundation +import PCloudSDKSwift public protocol CloudProviderManager { func getProvider(with accountUID: String) throws -> CloudProvider @@ -60,8 +61,7 @@ public class CloudProviderDBManager: CloudProviderManager, CloudProviderUpdating useBackgroundSession: useBackgroundSession, maxPageSize: useBackgroundSession ? maxPageSizeForFileProvider : .max) case .pCloud: - let credential = try PCloudCredential(userID: accountUID) - provider = try PCloudCloudProvider(credential: credential) + provider = try createPCloudProvider(for: accountUID) case .webDAV: guard let credential = WebDAVCredentialManager.shared.getCredentialFromKeychain(with: accountUID) else { throw CloudProviderAccountError.accountNotFoundError @@ -96,6 +96,17 @@ public class CloudProviderDBManager: CloudProviderManager, CloudProviderUpdating } } + private func createPCloudProvider(for accountUID: String) throws -> CloudProvider { + let credential = try PCloudCredential(userID: accountUID) + let client: PCloudClient + if useBackgroundSession { + client = PCloud.createBackgroundClient(with: credential.user, sharedContainerIdentifier: CryptomatorConstants.appGroupName) + } else { + client = PCloud.createClient(with: credential.user) + } + return try PCloudCloudProvider(client: client) + } + public func providerShouldUpdate(with accountUID: String) { CloudProviderDBManager.cachedProvider[accountUID] = nil // call XPCService for FileProvider diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Mocks/CloudProviderMock.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Mocks/CloudProviderMock.swift index 824c0e587..1243d2fae 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Mocks/CloudProviderMock.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Mocks/CloudProviderMock.swift @@ -113,7 +113,7 @@ final class CloudProviderMock: CloudProvider { } } - func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise { + func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { if let error = downloadFileFromToThrowableError { return Promise(error) } @@ -136,7 +136,7 @@ final class CloudProviderMock: CloudProvider { var uploadFileFromToReplaceExistingReturnValue: Promise! var uploadFileFromToReplaceExistingClosure: ((URL, CloudPath, Bool) -> Promise)? - func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise { + func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise { if let error = uploadFileFromToReplaceExistingThrowableError { return Promise(error) } diff --git a/CryptomatorFileProvider/CloudTask/DownloadTask.swift b/CryptomatorFileProvider/CloudTask/DownloadTask.swift index 4389458bb..e125a7e11 100644 --- a/CryptomatorFileProvider/CloudTask/DownloadTask.swift +++ b/CryptomatorFileProvider/CloudTask/DownloadTask.swift @@ -12,9 +12,12 @@ import GRDB struct DownloadTask: CloudTask { let taskRecord: DownloadTaskRecord let itemMetadata: ItemMetadata + let onURLSessionTaskCreation: URLSessionTaskCreationClosure? enum CodingKeys: String, CodingKey { case taskRecord = "downloadTask" case itemMetadata } } + +typealias URLSessionTaskCreationClosure = (URLSessionTask) -> Void diff --git a/CryptomatorFileProvider/CloudTask/UploadTask.swift b/CryptomatorFileProvider/CloudTask/UploadTask.swift index b09929300..ac604cc9a 100644 --- a/CryptomatorFileProvider/CloudTask/UploadTask.swift +++ b/CryptomatorFileProvider/CloudTask/UploadTask.swift @@ -6,14 +6,11 @@ // Copyright © 2020 Skymatic GmbH. All rights reserved. // +import Foundation import GRDB -struct UploadTask: CloudTask, FetchableRecord, Decodable { +struct UploadTask: CloudTask { let taskRecord: UploadTaskRecord let itemMetadata: ItemMetadata - - enum CodingKeys: String, CodingKey { - case taskRecord = "uploadTask" - case itemMetadata - } + let onURLSessionTaskCreation: URLSessionTaskCreationClosure? } diff --git a/CryptomatorFileProvider/DB/DownloadTaskDBManager.swift b/CryptomatorFileProvider/DB/DownloadTaskDBManager.swift index 7893b4e72..ee94e818d 100644 --- a/CryptomatorFileProvider/DB/DownloadTaskDBManager.swift +++ b/CryptomatorFileProvider/DB/DownloadTaskDBManager.swift @@ -10,7 +10,7 @@ import Foundation import GRDB protocol DownloadTaskManager { - func createTask(for item: ItemMetadata, replaceExisting: Bool, localURL: URL) throws -> DownloadTask + func createTask(for item: ItemMetadata, replaceExisting: Bool, localURL: URL, onURLSessionTaskCreation: URLSessionTaskCreationClosure?) throws -> DownloadTask func removeTaskRecord(_ task: DownloadTaskRecord) throws } @@ -24,11 +24,11 @@ class DownloadTaskDBManager: DownloadTaskManager { } } - func createTask(for item: ItemMetadata, replaceExisting: Bool, localURL: URL) throws -> DownloadTask { + func createTask(for item: ItemMetadata, replaceExisting: Bool, localURL: URL, onURLSessionTaskCreation: URLSessionTaskCreationClosure?) throws -> DownloadTask { try database.write { db in let taskRecord = DownloadTaskRecord(correspondingItem: item.id!, replaceExisting: replaceExisting, localURL: localURL) try taskRecord.save(db) - return DownloadTask(taskRecord: taskRecord, itemMetadata: item) + return DownloadTask(taskRecord: taskRecord, itemMetadata: item, onURLSessionTaskCreation: onURLSessionTaskCreation) } } diff --git a/CryptomatorFileProvider/DB/UploadTaskDBManager.swift b/CryptomatorFileProvider/DB/UploadTaskDBManager.swift index 8d1a97149..8930cb2c3 100644 --- a/CryptomatorFileProvider/DB/UploadTaskDBManager.swift +++ b/CryptomatorFileProvider/DB/UploadTaskDBManager.swift @@ -17,7 +17,7 @@ protocol UploadTaskManager { func updateTaskRecord(_ task: inout UploadTaskRecord, error: NSError) throws func getCorrespondingTaskRecords(ids: [Int64]) throws -> [UploadTaskRecord?] func removeTaskRecord(for id: Int64) throws - func getTask(for uploadTask: UploadTaskRecord) throws -> UploadTask + func getTask(for uploadTask: UploadTaskRecord, onURLSessionTaskCreation: URLSessionTaskCreationClosure?) throws -> UploadTask } extension UploadTaskManager { @@ -115,12 +115,12 @@ class UploadTaskDBManager: UploadTaskManager { } } - func getTask(for uploadTask: UploadTaskRecord) throws -> UploadTask { + func getTask(for uploadTask: UploadTaskRecord, onURLSessionTaskCreation: URLSessionTaskCreationClosure?) throws -> UploadTask { try database.read { db in guard let itemMetadata = try uploadTask.itemMetadata.fetchOne(db) else { throw DBManagerError.missingItemMetadata } - return UploadTask(taskRecord: uploadTask, itemMetadata: itemMetadata) + return UploadTask(taskRecord: uploadTask, itemMetadata: itemMetadata, onURLSessionTaskCreation: onURLSessionTaskCreation) } } } diff --git a/CryptomatorFileProvider/FileProviderAdapter.swift b/CryptomatorFileProvider/FileProviderAdapter.swift index fc4a8bd2a..5f9c8a3c1 100644 --- a/CryptomatorFileProvider/FileProviderAdapter.swift +++ b/CryptomatorFileProvider/FileProviderAdapter.swift @@ -72,8 +72,23 @@ public class FileProviderAdapter: FileProviderAdapterType { private let workflowFactory: WorkflowFactoryLocking private let domainIdentifier: NSFileProviderDomainIdentifier private let fileCoordinator: NSFileCoordinator - - init(domainIdentifier: NSFileProviderDomainIdentifier, uploadTaskManager: UploadTaskManager, cachedFileManager: CachedFileManager, itemMetadataManager: ItemMetadataManager, reparentTaskManager: ReparentTaskManager, deletionTaskManager: DeletionTaskManager, itemEnumerationTaskManager: ItemEnumerationTaskManager, downloadTaskManager: DownloadTaskManager, scheduler: WorkflowScheduler, provider: CloudProvider, coordinator: NSFileCoordinator, notificator: FileProviderItemUpdateDelegate? = nil, localURLProvider: LocalURLProviderType, fullVersionChecker: FullVersionChecker = UserDefaultsFullVersionChecker.shared) { + private let taskRegistrator: SessionTaskRegistrator + + init(domainIdentifier: NSFileProviderDomainIdentifier, + uploadTaskManager: UploadTaskManager, + cachedFileManager: CachedFileManager, + itemMetadataManager: ItemMetadataManager, + reparentTaskManager: ReparentTaskManager, + deletionTaskManager: DeletionTaskManager, + itemEnumerationTaskManager: ItemEnumerationTaskManager, + downloadTaskManager: DownloadTaskManager, + scheduler: WorkflowScheduler, + provider: CloudProvider, + coordinator: NSFileCoordinator, + notificator: FileProviderItemUpdateDelegate? = nil, + localURLProvider: LocalURLProviderType, + fullVersionChecker: FullVersionChecker = UserDefaultsFullVersionChecker.shared, + taskRegistrator: SessionTaskRegistrator) { self.lastUnlockedDate = Date() self.domainIdentifier = domainIdentifier self.uploadTaskManager = uploadTaskManager @@ -99,6 +114,7 @@ public class FileProviderAdapter: FileProviderAdapterType { self.localURLProvider = localURLProvider self.fullVersionChecker = fullVersionChecker self.fileCoordinator = coordinator + self.taskRegistrator = taskRegistrator } /** @@ -202,7 +218,9 @@ public class FileProviderAdapter: FileProviderAdapterType { } } // Network Stuff - self.uploadFile(taskRecord: localItemImportResult.uploadTaskRecord, completionHandler: localImportHandler).then { item in + self.uploadFile(taskRecord: localItemImportResult.uploadTaskRecord, + completionHandler: localImportHandler, + itemIdentifier: localItemImportResult.item.itemIdentifier).then { item in self.notificator?.signalUpdate(for: item) }.catch { error in DDLogError("importDocument uploadFile failed: \(error)") @@ -293,7 +311,7 @@ public class FileProviderAdapter: FileProviderAdapterType { DDLogError("itemChanged - register file in upload queue with url: \(url) and identifier: \(itemIdentifier) failed with error: \(error)") return } - uploadFile(taskRecord: uploadTaskRecord).then { item in + uploadFile(taskRecord: uploadTaskRecord, itemIdentifier: itemIdentifier).then { item in self.notificator?.signalUpdate(for: item) } } @@ -318,15 +336,23 @@ public class FileProviderAdapter: FileProviderAdapterType { let localURL = localCachedFileInfo.localURL let item = FileProviderItem(metadata: itemMetadata, domainIdentifier: domainIdentifier, newestVersionLocallyCached: newestVersionLocallyCached, localURL: localURL, error: nil) notificator?.signalUpdate(for: item) - uploadFile(taskRecord: uploadTaskRecord).then { item in + uploadFile(taskRecord: uploadTaskRecord, itemIdentifier: itemIdentifier).then { item in self.notificator?.signalUpdate(for: item) } } - func uploadFile(taskRecord: UploadTaskRecord, completionHandler: ((Error?) -> Void)? = nil) -> Promise { + func uploadFile(taskRecord: UploadTaskRecord, completionHandler: ((Error?) -> Void)? = nil, itemIdentifier: NSFileProviderItemIdentifier) -> Promise { let task: UploadTask do { - task = try uploadTaskManager.getTask(for: taskRecord) + task = try uploadTaskManager.getTask(for: taskRecord, onURLSessionTaskCreation: { [weak self] urlSessionTask in + self?.taskRegistrator.register(urlSessionTask, forItemWithIdentifier: itemIdentifier, completionHandler: { error in + if let error { + DDLogError("Register URLSessionUploadTask for identifier: \(itemIdentifier) failed with error: \(error)") + } else { + DDLogInfo("Successfully registered URLSessionUploadTask for identifier: \(itemIdentifier)") + } + }) + }) } catch { completionHandler?(error) return Promise(error) @@ -685,7 +711,15 @@ public class FileProviderAdapter: FileProviderAdapterType { let task: DownloadTask do { let itemMetadata = try getCachedMetadata(for: identifier) - task = try downloadTaskManager.createTask(for: itemMetadata, replaceExisting: replaceExisting, localURL: localURL) + task = try downloadTaskManager.createTask(for: itemMetadata, replaceExisting: replaceExisting, localURL: localURL, onURLSessionTaskCreation: { [weak self] urlSessionTask in + self?.taskRegistrator.register(urlSessionTask, forItemWithIdentifier: identifier, completionHandler: { error in + if let error { + DDLogError("Register URLSessionTask for identifier: \(identifier) failed with error: \(error)") + } else { + DDLogInfo("Successfully registered URLSessionTask for identifier: \(identifier)") + } + }) + }) } catch { return Promise(error) } diff --git a/CryptomatorFileProvider/FileProviderAdapterManager.swift b/CryptomatorFileProvider/FileProviderAdapterManager.swift index 7875b16fe..3a374fef3 100644 --- a/CryptomatorFileProvider/FileProviderAdapterManager.swift +++ b/CryptomatorFileProvider/FileProviderAdapterManager.swift @@ -16,7 +16,7 @@ import Promises protocol FileProviderAdapterProviding { var unlockMonitor: UnlockMonitorType { get } - func getAdapter(forDomain domain: NSFileProviderDomain, dbPath: URL, delegate: LocalURLProviderType, notificator: FileProviderNotificatorType) throws -> FileProviderAdapterType + func getAdapter(forDomain domain: NSFileProviderDomain, dbPath: URL, delegate: LocalURLProviderType, notificator: FileProviderNotificatorType, taskRegistrator: SessionTaskRegistrator) throws -> FileProviderAdapterType } public class FileProviderAdapterManager: FileProviderAdapterProviding { @@ -48,7 +48,7 @@ public class FileProviderAdapterManager: FileProviderAdapterProviding { self.providerIdentifier = providerIdentifier } - public func getAdapter(forDomain domain: NSFileProviderDomain, dbPath: URL, delegate: FileProviderAdapterDelegate, notificator: FileProviderNotificatorType) throws -> FileProviderAdapterType { + public func getAdapter(forDomain domain: NSFileProviderDomain, dbPath: URL, delegate: FileProviderAdapterDelegate, notificator: FileProviderNotificatorType, taskRegistrator: SessionTaskRegistrator) throws -> FileProviderAdapterType { try queue.sync { let cachedAdapterItem = adapterCache.getItem(identifier: domain.identifier) let vaultUID = domain.identifier.rawValue @@ -66,7 +66,7 @@ public class FileProviderAdapterManager: FileProviderAdapterProviding { adapter = cachedAdapter } else { DDLogDebug("Try to automatically unlock \(domain.displayName) - \(domain.identifier)") - let autoUnlockItem = try autoUnlockVault(withVaultUID: vaultUID, domainIdentifier: domain.identifier, dbPath: dbPath, delegate: delegate, notificator: notificator) + let autoUnlockItem = try autoUnlockVault(withVaultUID: vaultUID, domainIdentifier: domain.identifier, dbPath: dbPath, delegate: delegate, notificator: notificator, taskRegistrator: taskRegistrator) adapterCache.cacheItem(autoUnlockItem, identifier: domain.identifier) adapter = autoUnlockItem.adapter } @@ -75,12 +75,13 @@ public class FileProviderAdapterManager: FileProviderAdapterProviding { } } - public func unlockVault(with domainIdentifier: NSFileProviderDomainIdentifier, kek: [UInt8], dbPath: URL?, delegate: FileProviderAdapterDelegate, notificator: FileProviderNotificatorType) throws { + // swiftlint:disable:next function_parameter_count + public func unlockVault(with domainIdentifier: NSFileProviderDomainIdentifier, kek: [UInt8], dbPath: URL?, delegate: FileProviderAdapterDelegate, notificator: FileProviderNotificatorType, taskRegistrator: SessionTaskRegistrator) throws { guard let dbPath = dbPath else { return } let provider = try vaultManager.manualUnlockVault(withUID: domainIdentifier.rawValue, kek: kek) - let item = try createAdapterCacheItem(domainIdentifier: domainIdentifier, cloudProvider: provider, dbPath: dbPath, delegate: delegate, notificator: notificator) + let item = try createAdapterCacheItem(domainIdentifier: domainIdentifier, cloudProvider: provider, dbPath: dbPath, delegate: delegate, notificator: notificator, taskRegistrator: taskRegistrator) try vaultKeepUnlockedSettings.setLastUsedDate(Date(), forVaultUID: domainIdentifier.rawValue) adapterCache.cacheItem(item, identifier: domainIdentifier) let notificator = try notificatorManager.getFileProviderNotificator(for: NSFileProviderDomain(identifier: domainIdentifier, displayName: "", pathRelativeToDocumentStorage: "")) @@ -121,7 +122,8 @@ public class FileProviderAdapterManager: FileProviderAdapterProviding { try maintenanceManager.disableMaintenanceMode() } - private func autoUnlockVault(withVaultUID vaultUID: String, domainIdentifier: NSFileProviderDomainIdentifier, dbPath: URL, delegate: FileProviderAdapterDelegate, notificator: FileProviderNotificatorType) throws -> AdapterCacheItem { + // swiftlint:disable:next function_parameter_count + private func autoUnlockVault(withVaultUID vaultUID: String, domainIdentifier: NSFileProviderDomainIdentifier, dbPath: URL, delegate: FileProviderAdapterDelegate, notificator: FileProviderNotificatorType, taskRegistrator: SessionTaskRegistrator) throws -> AdapterCacheItem { guard vaultKeepUnlockedHelper.shouldAutoUnlockVault(withVaultUID: vaultUID) else { try masterkeyCacheManager.removeCachedMasterkey(forVaultUID: vaultUID) throw unlockMonitor.getUnlockError(forVaultUID: vaultUID) @@ -130,12 +132,13 @@ public class FileProviderAdapterManager: FileProviderAdapterProviding { throw unlockMonitor.getUnlockError(forVaultUID: vaultUID) } let provider = try vaultManager.createVaultProvider(withUID: vaultUID, masterkey: cachedMasterkey) - let adapterCacheItem = try createAdapterCacheItem(domainIdentifier: domainIdentifier, cloudProvider: provider, dbPath: dbPath, delegate: delegate, notificator: notificator) + let adapterCacheItem = try createAdapterCacheItem(domainIdentifier: domainIdentifier, cloudProvider: provider, dbPath: dbPath, delegate: delegate, notificator: notificator, taskRegistrator: taskRegistrator) notificator.refreshWorkingSet() return adapterCacheItem } - private func createAdapterCacheItem(domainIdentifier: NSFileProviderDomainIdentifier, cloudProvider: CloudProvider, dbPath: URL, delegate: FileProviderAdapterDelegate, notificator: FileProviderNotificatorType) throws -> AdapterCacheItem { + // swiftlint:disable:next function_parameter_count + private func createAdapterCacheItem(domainIdentifier: NSFileProviderDomainIdentifier, cloudProvider: CloudProvider, dbPath: URL, delegate: FileProviderAdapterDelegate, notificator: FileProviderNotificatorType, taskRegistrator: SessionTaskRegistrator) throws -> AdapterCacheItem { let fileCoordinator = NSFileCoordinator() fileCoordinator.purposeIdentifier = providerIdentifier let database = try DatabaseHelper.default.getMigratedDB(at: dbPath, purposeIdentifier: providerIdentifier) @@ -161,7 +164,8 @@ public class FileProviderAdapterManager: FileProviderAdapterProviding { provider: cloudProvider, coordinator: fileCoordinator, notificator: notificator, - localURLProvider: delegate) + localURLProvider: delegate, + taskRegistrator: taskRegistrator) let workingSetObserver = WorkingSetObserver(domainIdentifier: domainIdentifier, database: database, notificator: notificator, uploadTaskManager: uploadTaskManager, cachedFileManager: cachedFileManager) workingSetObserver.startObservation() return AdapterCacheItem(adapter: adapter, maintenanceManager: maintenanceManager, workingSetObserver: workingSetObserver) diff --git a/CryptomatorFileProvider/FileProviderEnumerator.swift b/CryptomatorFileProvider/FileProviderEnumerator.swift index 3bc96d688..3a5d50f14 100644 --- a/CryptomatorFileProvider/FileProviderEnumerator.swift +++ b/CryptomatorFileProvider/FileProviderEnumerator.swift @@ -16,23 +16,26 @@ public class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { private let dbPath: URL private let adapterProvider: FileProviderAdapterProviding private let localURLProvider: LocalURLProviderType + private let taskRegistrator: SessionTaskRegistrator - public convenience init(enumeratedItemIdentifier: NSFileProviderItemIdentifier, notificator: FileProviderNotificatorType, domain: NSFileProviderDomain, dbPath: URL, localURLProvider: LocalURLProviderType) { + public convenience init(enumeratedItemIdentifier: NSFileProviderItemIdentifier, notificator: FileProviderNotificatorType, domain: NSFileProviderDomain, dbPath: URL, localURLProvider: LocalURLProviderType, taskRegistrator: SessionTaskRegistrator) { self.init(enumeratedItemIdentifier: enumeratedItemIdentifier, notificator: notificator, domain: domain, dbPath: dbPath, localURLProvider: localURLProvider, - adapterProvider: FileProviderAdapterManager.shared) + adapterProvider: FileProviderAdapterManager.shared, + taskRegistrator: taskRegistrator) } - init(enumeratedItemIdentifier: NSFileProviderItemIdentifier, notificator: FileProviderNotificatorType, domain: NSFileProviderDomain, dbPath: URL, localURLProvider: LocalURLProviderType, adapterProvider: FileProviderAdapterProviding) { + init(enumeratedItemIdentifier: NSFileProviderItemIdentifier, notificator: FileProviderNotificatorType, domain: NSFileProviderDomain, dbPath: URL, localURLProvider: LocalURLProviderType, adapterProvider: FileProviderAdapterProviding, taskRegistrator: SessionTaskRegistrator) { self.enumeratedItemIdentifier = enumeratedItemIdentifier self.notificator = notificator self.domain = domain self.dbPath = dbPath self.localURLProvider = localURLProvider self.adapterProvider = adapterProvider + self.taskRegistrator = taskRegistrator super.init() } @@ -70,7 +73,7 @@ public class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { adapterProvider.unlockMonitor.execute { let adapter: FileProviderAdapterType do { - adapter = try self.adapterProvider.getAdapter(forDomain: self.domain, dbPath: self.dbPath, delegate: self.localURLProvider, notificator: self.notificator) + adapter = try self.adapterProvider.getAdapter(forDomain: self.domain, dbPath: self.dbPath, delegate: self.localURLProvider, notificator: self.notificator, taskRegistrator: self.taskRegistrator) } catch { self.handleEnumerateItemsError(error, for: observer) return @@ -115,7 +118,7 @@ public class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { let adapter: FileProviderAdapterType do { - adapter = try adapterProvider.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProvider, notificator: notificator) + adapter = try adapterProvider.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProvider, notificator: notificator, taskRegistrator: taskRegistrator) } catch { if workingSetSyncAnchor.invalidated { DDLogDebug("Working set for \(domain.displayName) is already invalidated -> return empty array") diff --git a/CryptomatorFileProvider/Middleware/TaskExecutor/DownloadTaskExecutor.swift b/CryptomatorFileProvider/Middleware/TaskExecutor/DownloadTaskExecutor.swift index 9b2181e66..c3feed4e7 100644 --- a/CryptomatorFileProvider/Middleware/TaskExecutor/DownloadTaskExecutor.swift +++ b/CryptomatorFileProvider/Middleware/TaskExecutor/DownloadTaskExecutor.swift @@ -49,7 +49,12 @@ class DownloadTaskExecutor: WorkflowMiddleware { let itemMetadata = task.itemMetadata return provider.fetchItemMetadata(at: itemMetadata.cloudPath).then { cloudMetadata -> Promise in lastModifiedDate = cloudMetadata.lastModifiedDate - return self.provider.downloadFile(from: itemMetadata.cloudPath, to: downloadDestination) + return self.provider.downloadFile(from: itemMetadata.cloudPath, to: downloadDestination, onTaskCreation: { task in + guard let task else { + return + } + downloadTask.onURLSessionTaskCreation?(task) + }) }.then { _ -> FileProviderItem in try self.downloadPostProcessing(for: itemMetadata, lastModifiedDate: lastModifiedDate, localURL: taskRecord.localURL, downloadDestination: downloadDestination) }.always { diff --git a/CryptomatorFileProvider/Middleware/TaskExecutor/UploadTaskExecutor.swift b/CryptomatorFileProvider/Middleware/TaskExecutor/UploadTaskExecutor.swift index 169d90644..a670cc153 100644 --- a/CryptomatorFileProvider/Middleware/TaskExecutor/UploadTaskExecutor.swift +++ b/CryptomatorFileProvider/Middleware/TaskExecutor/UploadTaskExecutor.swift @@ -43,7 +43,7 @@ class UploadTaskExecutor: WorkflowMiddleware { } func execute(task: CloudTask) -> Promise { - guard task is UploadTask else { + guard let uploadTask = task as? UploadTask else { return Promise(WorkflowMiddlewareError.incompatibleCloudTask) } let itemMetadata = task.itemMetadata @@ -70,7 +70,15 @@ class UploadTaskExecutor: WorkflowMiddleware { progressManager.saveProgress(progress, for: NSFileProviderItemIdentifier(domainIdentifier: domainIdentifier, itemID: itemID)) } progress.becomeCurrent(withPendingUnitCount: 1) - let uploadPromise = provider.uploadFile(from: localURL, to: itemMetadata.cloudPath, replaceExisting: !itemMetadata.isPlaceholderItem) + let uploadPromise = provider.uploadFile(from: localURL, + to: itemMetadata.cloudPath, + replaceExisting: !itemMetadata.isPlaceholderItem, + onTaskCreation: { task in + guard let task else { + return + } + uploadTask.onURLSessionTaskCreation?(task) + }) progress.resignCurrent() return uploadPromise.then { cloudItemMetadata in try self.uploadPostProcessing(taskItemMetadata: itemMetadata, cloudItemMetadata: cloudItemMetadata, localURL: localURL, localFileSizeBeforeUpload: localFileSize) diff --git a/CryptomatorFileProvider/ServiceSource/FileImportingServiceSource.swift b/CryptomatorFileProvider/ServiceSource/FileImportingServiceSource.swift index ee708d6c4..8e3111efa 100644 --- a/CryptomatorFileProvider/ServiceSource/FileImportingServiceSource.swift +++ b/CryptomatorFileProvider/ServiceSource/FileImportingServiceSource.swift @@ -18,23 +18,26 @@ public class FileImportingServiceSource: ServiceSource, FileImporting { private let localURLProvider: LocalURLProviderType private let adapterManager: FileProviderAdapterProviding private let fullVersionChecker: FullVersionChecker + private let taskRegistrator: SessionTaskRegistrator - public convenience init(domain: NSFileProviderDomain, notificator: FileProviderNotificatorType, dbPath: URL, delegate: LocalURLProviderType) { + public convenience init(domain: NSFileProviderDomain, notificator: FileProviderNotificatorType, dbPath: URL, delegate: LocalURLProviderType, taskRegistrator: SessionTaskRegistrator) { self.init(domain: domain, notificator: notificator, dbPath: dbPath, delegate: delegate, adapterManager: FileProviderAdapterManager.shared, - fullVersionChecker: UserDefaultsFullVersionChecker.shared) + fullVersionChecker: UserDefaultsFullVersionChecker.shared, + taskRegistrator: taskRegistrator) } - init(domain: NSFileProviderDomain, notificator: FileProviderNotificatorType, dbPath: URL, delegate: LocalURLProviderType, adapterManager: FileProviderAdapterProviding, fullVersionChecker: FullVersionChecker) { + init(domain: NSFileProviderDomain, notificator: FileProviderNotificatorType, dbPath: URL, delegate: LocalURLProviderType, adapterManager: FileProviderAdapterProviding, fullVersionChecker: FullVersionChecker, taskRegistrator: SessionTaskRegistrator) { self.domain = domain self.notificator = notificator self.dbPath = dbPath self.localURLProvider = delegate self.adapterManager = adapterManager self.fullVersionChecker = fullVersionChecker + self.taskRegistrator = taskRegistrator super.init(serviceName: .fileImporting, exportedInterface: NSXPCInterface(with: FileImporting.self)) } @@ -55,7 +58,8 @@ public class FileImportingServiceSource: ServiceSource, FileImporting { adapter = try adapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProvider, - notificator: notificator) + notificator: notificator, + taskRegistrator: taskRegistrator) } catch { throw ErrorWrapper.wrapError(error, domain: domain)._nsError } @@ -87,7 +91,8 @@ public class FileImportingServiceSource: ServiceSource, FileImporting { adapter = try adapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProvider, - notificator: notificator) + notificator: notificator, + taskRegistrator: taskRegistrator) } catch { throw ErrorWrapper.wrapError(error, domain: domain)._nsError } diff --git a/CryptomatorFileProvider/ServiceSource/UploadRetryingServiceSource.swift b/CryptomatorFileProvider/ServiceSource/UploadRetryingServiceSource.swift index b1b76df1d..de864493b 100644 --- a/CryptomatorFileProvider/ServiceSource/UploadRetryingServiceSource.swift +++ b/CryptomatorFileProvider/ServiceSource/UploadRetryingServiceSource.swift @@ -17,22 +17,25 @@ public class UploadRetryingServiceSource: ServiceSource, UploadRetrying { private let dbPath: URL private let localURLProvider: LocalURLProviderType private let progressManager: ProgressManager + private let taskRegistrator: SessionTaskRegistrator - public convenience init(domain: NSFileProviderDomain, notificator: FileProviderNotificatorType, dbPath: URL, delegate: LocalURLProviderType) { + public convenience init(domain: NSFileProviderDomain, notificator: FileProviderNotificatorType, dbPath: URL, delegate: LocalURLProviderType, taskRegistrator: SessionTaskRegistrator) { self.init(domain: domain, notificator: notificator, dbPath: dbPath, delegate: delegate, - adapterManager: FileProviderAdapterManager.shared) + adapterManager: FileProviderAdapterManager.shared, + taskRegistrator: taskRegistrator) } - init(domain: NSFileProviderDomain, notificator: FileProviderNotificatorType, dbPath: URL, delegate: LocalURLProviderType, adapterManager: FileProviderAdapterProviding = FileProviderAdapterManager.shared, progressManager: ProgressManager = InMemoryProgressManager.shared) { + init(domain: NSFileProviderDomain, notificator: FileProviderNotificatorType, dbPath: URL, delegate: LocalURLProviderType, adapterManager: FileProviderAdapterProviding = FileProviderAdapterManager.shared, progressManager: ProgressManager = InMemoryProgressManager.shared, taskRegistrator: SessionTaskRegistrator) { self.domain = domain self.notificator = notificator self.dbPath = dbPath self.localURLProvider = delegate self.adapterManager = adapterManager self.progressManager = progressManager + self.taskRegistrator = taskRegistrator super.init(serviceName: .uploadRetryingService, exportedInterface: NSXPCInterface(with: UploadRetrying.self)) } @@ -42,7 +45,8 @@ public class UploadRetryingServiceSource: ServiceSource, UploadRetrying { adapter = try adapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProvider, - notificator: notificator) + notificator: notificator, + taskRegistrator: taskRegistrator) } catch { reply(error) return diff --git a/CryptomatorFileProvider/ServiceSource/VaultUnlockingServiceSource.swift b/CryptomatorFileProvider/ServiceSource/VaultUnlockingServiceSource.swift index 0e0e51e37..c5103ac12 100644 --- a/CryptomatorFileProvider/ServiceSource/VaultUnlockingServiceSource.swift +++ b/CryptomatorFileProvider/ServiceSource/VaultUnlockingServiceSource.swift @@ -20,11 +20,14 @@ public class VaultUnlockingServiceSource: ServiceSource, VaultUnlocking { return domain.identifier.rawValue } - public init(domain: NSFileProviderDomain, notificator: FileProviderNotificatorType?, dbPath: URL?, delegate: LocalURLProviderType) { + private let taskRegistrator: SessionTaskRegistrator + + public init(domain: NSFileProviderDomain, notificator: FileProviderNotificatorType?, dbPath: URL?, delegate: LocalURLProviderType, taskRegistrator: SessionTaskRegistrator) { self.domain = domain self.notificator = notificator self.dbPath = dbPath self.localURLProvider = delegate + self.taskRegistrator = taskRegistrator super.init(serviceName: .vaultUnlocking, exportedInterface: NSXPCInterface(with: VaultUnlocking.self)) } @@ -40,7 +43,7 @@ public class VaultUnlockingServiceSource: ServiceSource, VaultUnlocking { return } do { - try FileProviderAdapterManager.shared.unlockVault(with: domain.identifier, kek: kek, dbPath: self.dbPath, delegate: self.localURLProvider, notificator: notificator) + try FileProviderAdapterManager.shared.unlockVault(with: domain.identifier, kek: kek, dbPath: self.dbPath, delegate: self.localURLProvider, notificator: notificator, taskRegistrator: self.taskRegistrator) FileProviderAdapterManager.shared.unlockMonitor.unlockSucceeded(forVaultUID: vaultUID) DDLogInfo("Unlocked vault \"\(domain.displayName)\" (\(domain.identifier.rawValue))") reply(nil) diff --git a/CryptomatorFileProvider/SessionTaskRegistrator.swift b/CryptomatorFileProvider/SessionTaskRegistrator.swift new file mode 100644 index 000000000..1921343af --- /dev/null +++ b/CryptomatorFileProvider/SessionTaskRegistrator.swift @@ -0,0 +1,30 @@ +// +// SessionTaskRegistrator.swift +// CryptomatorFileProvider +// +// Created by Philipp Schmid on 30.11.22. +// Copyright © 2022 Skymatic GmbH. All rights reserved. +// + +import FileProvider +import Foundation + +public protocol SessionTaskRegistrator { + func register(_ task: SessionTask, forItemWithIdentifier identifier: NSFileProviderItemIdentifier, completionHandler completion: @escaping (Error?) -> Void) +} + +public protocol SessionTask {} + +extension URLSessionTask: SessionTask {} + +extension NSFileProviderManager: SessionTaskRegistrator { + public func register(_ task: SessionTask, forItemWithIdentifier identifier: NSFileProviderItemIdentifier, completionHandler completion: @escaping (Error?) -> Void) { + guard let urlSessionTask = task as? URLSessionTask else { + completion(UnexpectedSessionTaskTypeError()) + return + } + register(urlSessionTask, forItemWithIdentifier: identifier, completionHandler: completion) + } +} + +struct UnexpectedSessionTaskTypeError: Error {} diff --git a/CryptomatorFileProviderTests/DB/DownloadTaskManagerTests.swift b/CryptomatorFileProviderTests/DB/DownloadTaskManagerTests.swift index 1bbe88ee4..abb9f426e 100644 --- a/CryptomatorFileProviderTests/DB/DownloadTaskManagerTests.swift +++ b/CryptomatorFileProviderTests/DB/DownloadTaskManagerTests.swift @@ -48,7 +48,7 @@ class DownloadTaskManagerTests: XCTestCase { let itemMetadata = ItemMetadata(name: "Test", type: .file, size: nil, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: cloudPath, isPlaceholderItem: false) try itemMetadataManager.cacheMetadata(itemMetadata) let localURL = URL(string: "/Test")! - return try manager.createTask(for: itemMetadata, replaceExisting: true, localURL: localURL) + return try manager.createTask(for: itemMetadata, replaceExisting: true, localURL: localURL, onURLSessionTaskCreation: nil) } private func getTaskRecord(for id: Int64) throws -> DownloadTaskRecord { diff --git a/CryptomatorFileProviderTests/DB/MaintenanceManagerTests.swift b/CryptomatorFileProviderTests/DB/MaintenanceManagerTests.swift index 90c51423b..e072973bb 100644 --- a/CryptomatorFileProviderTests/DB/MaintenanceManagerTests.swift +++ b/CryptomatorFileProviderTests/DB/MaintenanceManagerTests.swift @@ -86,7 +86,7 @@ class MaintenanceManagerTests: XCTestCase { // Prevent INSERT try manager.enableMaintenanceMode() - checkThrowsMaintenanceError(try downloadTaskManager.createTask(for: itemMetadata, replaceExisting: true, localURL: URL(string: "/Test")!)) + checkThrowsMaintenanceError(try downloadTaskManager.createTask(for: itemMetadata, replaceExisting: true, localURL: URL(string: "/Test")!, onURLSessionTaskCreation: nil)) } // MARK: - Prevent enabling maintenance mode for running tasks @@ -132,7 +132,7 @@ class MaintenanceManagerTests: XCTestCase { func testPreventEnablingMaintenanceModeForRunningDownloadTask() throws { let itemMetadata = ItemMetadata(name: "Test", type: .file, size: nil, parentID: 1, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: CloudPath("/Test"), isPlaceholderItem: false) try itemMetadataManager.cacheMetadata(itemMetadata) - _ = try downloadTaskManager.createTask(for: itemMetadata, replaceExisting: false, localURL: URL(string: "/Test")!) + _ = try downloadTaskManager.createTask(for: itemMetadata, replaceExisting: false, localURL: URL(string: "/Test")!, onURLSessionTaskCreation: nil) try assertOnlyFalseAllowedForInsertOrUpdate() } diff --git a/CryptomatorFileProviderTests/DB/UploadTaskManagerTests.swift b/CryptomatorFileProviderTests/DB/UploadTaskManagerTests.swift index fd6c275c3..585a8d53b 100644 --- a/CryptomatorFileProviderTests/DB/UploadTaskManagerTests.swift +++ b/CryptomatorFileProviderTests/DB/UploadTaskManagerTests.swift @@ -94,7 +94,7 @@ class UploadTaskManagerTests: XCTestCase { let itemMetadata = ItemMetadata(name: "Test", type: .file, size: nil, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: cloudPath, isPlaceholderItem: false) try itemMetadataManager.cacheMetadata(itemMetadata) let taskRecord = try manager.createNewTaskRecord(for: itemMetadata) - let fetchedTask = try manager.getTask(for: taskRecord) + let fetchedTask = try manager.getTask(for: taskRecord, onURLSessionTaskCreation: nil) XCTAssertEqual(itemMetadata, fetchedTask.itemMetadata) XCTAssertEqual(itemMetadata.id, fetchedTask.taskRecord.correspondingItem) } diff --git a/CryptomatorFileProviderTests/FileImportingServiceSourceTests.swift b/CryptomatorFileProviderTests/FileImportingServiceSourceTests.swift index fb035e914..21d4d470f 100644 --- a/CryptomatorFileProviderTests/FileImportingServiceSourceTests.swift +++ b/CryptomatorFileProviderTests/FileImportingServiceSourceTests.swift @@ -18,6 +18,7 @@ class FileImportingServiceSourceTests: XCTestCase { var adapterProvidingMock: FileProviderAdapterProvidingMock! var urlProviderMock: LocalURLProviderMock! var fullVersionCheckerMock: FullVersionCheckerMock! + var taskRegistratorMock: SessionTaskRegistratorMock! let dbPath = FileManager.default.temporaryDirectory let domain = NSFileProviderDomain(identifier: .test, displayName: "Foo", pathRelativeToDocumentStorage: "/") let itemStub = FileProviderItem(metadata: .init(name: "Foo", type: .file, size: nil, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploading, cloudPath: CloudPath("/foo"), isPlaceholderItem: false), domainIdentifier: .test) @@ -28,12 +29,14 @@ class FileImportingServiceSourceTests: XCTestCase { adapterProvidingMock = FileProviderAdapterProvidingMock() fullVersionCheckerMock = FullVersionCheckerMock() fullVersionCheckerMock.isFullVersion = true + taskRegistratorMock = SessionTaskRegistratorMock() serviceSource = FileImportingServiceSource(domain: domain, notificator: notificatorMock, dbPath: dbPath, delegate: urlProviderMock, adapterManager: adapterProvidingMock, - fullVersionChecker: fullVersionCheckerMock) + fullVersionChecker: fullVersionCheckerMock, + taskRegistrator: taskRegistratorMock) } func testGetItemIdentifier() throws { @@ -41,7 +44,7 @@ class FileImportingServiceSourceTests: XCTestCase { let adapterMock = FileProviderAdapterTypeMock() let expectedItemIdentifier = NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 2) adapterMock.getItemIdentifierForReturnValue = Promise(expectedItemIdentifier) - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue = adapterMock let itemIdentifierPromise = serviceSource.getIdentifierForItem(at: cloudPath) wait(for: itemIdentifierPromise, timeout: 1.0) let rawItemIdentifier = try XCTUnwrap(itemIdentifierPromise.value) @@ -53,7 +56,7 @@ class FileImportingServiceSourceTests: XCTestCase { func testGetItemIdentifierForLockedVault() throws { let cloudPath = "/foo/bar" - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorThrowableError = UnlockMonitorError.defaultLock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorThrowableError = UnlockMonitorError.defaultLock let itemIdentifierPromise = serviceSource.getIdentifierForItem(at: cloudPath) let expectedWrappedError = ErrorWrapper.wrapError(UnlockMonitorError.defaultLock, domain: domain) XCTAssertRejects(itemIdentifierPromise, with: expectedWrappedError._nsError) @@ -68,7 +71,7 @@ class FileImportingServiceSourceTests: XCTestCase { let adapterMock = FileProviderAdapterTypeMock() let parentItemIdentifier = NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 2) - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue = adapterMock adapterMock.importDocumentAtToParentItemIdentifierCompletionHandlerClosure = { _, _, completion in completion(self.itemStub, nil) } @@ -86,7 +89,7 @@ class FileImportingServiceSourceTests: XCTestCase { let adapterMock = FileProviderAdapterTypeMock() let parentItemIdentifier = NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 2) - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue = adapterMock adapterMock.importDocumentAtToParentItemIdentifierCompletionHandlerClosure = { _, _, completion in completion(nil, NSError.fileProviderErrorForCollision(with: self.itemStub)) } @@ -104,7 +107,7 @@ class FileImportingServiceSourceTests: XCTestCase { func testImportFileForLockedVault() throws { let localURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) let parentItemIdentifier = NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 2) - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorThrowableError = UnlockMonitorError.defaultLock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorThrowableError = UnlockMonitorError.defaultLock let importFilePromise = serviceSource.importFile(at: localURL, toParentItemIdentifier: parentItemIdentifier.rawValue) let expectedWrappedError = ErrorWrapper.wrapError(UnlockMonitorError.defaultLock, domain: domain) XCTAssertRejects(importFilePromise, with: expectedWrappedError._nsError) @@ -120,7 +123,7 @@ class FileImportingServiceSourceTests: XCTestCase { } private func assertAdapterProvidingMockGetAdapterCalled() { - let adapterProviderManagerReceivedArguments = adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReceivedArguments + let adapterProviderManagerReceivedArguments = adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReceivedArguments XCTAssertEqual(domain, adapterProviderManagerReceivedArguments?.domain) XCTAssertEqual(dbPath, adapterProviderManagerReceivedArguments?.dbPath) XCTAssert(urlProviderMock === adapterProviderManagerReceivedArguments?.delegate) diff --git a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterCreateDirectoryTests.swift b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterCreateDirectoryTests.swift index cac1f0b55..75b709593 100644 --- a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterCreateDirectoryTests.swift +++ b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterCreateDirectoryTests.swift @@ -15,7 +15,7 @@ class FileProviderAdapterCreateDirectoryTests: FileProviderAdapterTestCase { let expectation = XCTestExpectation() let rootItemMetadata = ItemMetadata(id: NSFileProviderItemIdentifier.rootContainerDatabaseValue, name: "Home", type: .folder, size: nil, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: CloudPath("/"), isPlaceholderItem: false) try metadataManagerMock.cacheMetadata(rootItemMetadata) - let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, coordinator: fileCoordinator, localURLProvider: localURLProviderMock) + let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, coordinator: fileCoordinator, localURLProvider: localURLProviderMock, taskRegistrator: taskRegistratorMock) adapter.createDirectory(withName: "TestFolder", inParentItemIdentifier: .rootContainer) { item, error in XCTAssertNil(error) guard let fileProviderItem = item as? FileProviderItem else { @@ -40,7 +40,7 @@ class FileProviderAdapterCreateDirectoryTests: FileProviderAdapterTestCase { func testCreateDirectoryFailsIfParentDoesNotExist() throws { let expectation = XCTestExpectation() - let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, coordinator: fileCoordinator, localURLProvider: LocalURLProviderMock()) + let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, coordinator: fileCoordinator, localURLProvider: LocalURLProviderMock(), taskRegistrator: taskRegistratorMock) adapter.createDirectory(withName: "TestFolder", inParentItemIdentifier: NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 2)) { item, error in XCTAssertNil(item) guard let error = error else { diff --git a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDirectoryTests.swift b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDirectoryTests.swift index 7bf7aa7d9..9faadb402 100644 --- a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDirectoryTests.swift +++ b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDirectoryTests.swift @@ -27,7 +27,7 @@ class FileProviderAdapterImportDirectoryTests: FileProviderAdapterTestCase { metadataManagerMock.cachedMetadata[1] = ItemMetadata(item: .init(name: "/", cloudPath: CloudPath("/"), itemType: .folder, lastModifiedDate: nil, size: nil), withParentID: 1) let provider = CloudProviderGraphMock() let scheduler = WorkflowSchedulerMock(maxParallelUploads: 2, maxParallelDownloads: 2) - let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: scheduler, provider: provider, coordinator: fileCoordinator, localURLProvider: localURLProviderMock) + let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: scheduler, provider: provider, coordinator: fileCoordinator, localURLProvider: localURLProviderMock, taskRegistrator: taskRegistratorMock) var parentIdentifier: NSFileProviderItemIdentifier = .rootContainer for _ in 0 ..< 5 { @@ -66,11 +66,11 @@ class FileProviderAdapterImportDirectoryTests: FileProviderAdapterTestCase { return Promise(MockError.notMocked) } - func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise { + func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { return Promise(MockError.notMocked) } - func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise { + func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise { return Promise(()).delay(1.0).then { _ -> Promise in do { let data = try Data(contentsOf: localURL) diff --git a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDocumentTests.swift b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDocumentTests.swift index 62e9d6244..fb7323b30 100644 --- a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDocumentTests.swift +++ b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDocumentTests.swift @@ -219,7 +219,7 @@ class FileProviderAdapterImportDocumentTests: FileProviderAdapterTestCase { let fileContent = "TestContent" try fileContent.write(to: fileURL, atomically: true, encoding: .utf8) - let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, coordinator: fileCoordinator, localURLProvider: localURLProviderMock) + let adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, coordinator: fileCoordinator, localURLProvider: localURLProviderMock, taskRegistrator: taskRegistratorMock) adapter.deleteItem(withIdentifier: NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: itemID), completionHandler: ({ error in XCTAssertNil(error) diff --git a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterTestCase.swift b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterTestCase.swift index 294f8dbe8..97568ed13 100644 --- a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterTestCase.swift +++ b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterTestCase.swift @@ -18,6 +18,7 @@ class FileProviderAdapterTestCase: CloudTaskExecutorTestCase { var localURLProviderMock: LocalURLProviderMock! var fullVersionCheckerMock: FullVersionCheckerMock! var fileProviderItemUpdateDelegateMock: FileProviderItemUpdateDelegateMock! + var taskRegistratorMock: SessionTaskRegistratorMock! override func setUpWithError() throws { try super.setUpWithError() @@ -26,6 +27,7 @@ class FileProviderAdapterTestCase: CloudTaskExecutorTestCase { fileProviderItemUpdateDelegateMock = FileProviderItemUpdateDelegateMock() fullVersionCheckerMock = FullVersionCheckerMock() fullVersionCheckerMock.isFullVersion = true + taskRegistratorMock = SessionTaskRegistratorMock() adapter = FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, @@ -39,14 +41,15 @@ class FileProviderAdapterTestCase: CloudTaskExecutorTestCase { coordinator: fileCoordinator, notificator: fileProviderItemUpdateDelegateMock, localURLProvider: localURLProviderMock, - fullVersionChecker: fullVersionCheckerMock) + fullVersionChecker: fullVersionCheckerMock, + taskRegistrator: taskRegistratorMock) uploadTaskManagerMock.createNewTaskRecordForClosure = { return UploadTaskRecord(correspondingItem: $0.id!, lastFailedUploadDate: nil, uploadErrorCode: nil, uploadErrorDomain: nil) } - uploadTaskManagerMock.getTaskForClosure = { + uploadTaskManagerMock.getTaskForOnURLSessionTaskCreationClosure = { let id = $0.correspondingItem let metadata = try XCTUnwrap(self.metadataManagerMock.cachedMetadata[id]) - return UploadTask(taskRecord: $0, itemMetadata: metadata) + return UploadTask(taskRecord: $0, itemMetadata: metadata, onURLSessionTaskCreation: $1) } } @@ -61,7 +64,7 @@ class FileProviderAdapterTestCase: CloudTaskExecutorTestCase { } func createFullyMockedAdapter() -> FileProviderAdapter { - return FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, coordinator: fileCoordinator, localURLProvider: localURLProviderMock) + return FileProviderAdapter(domainIdentifier: .test, uploadTaskManager: uploadTaskManagerMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, downloadTaskManager: downloadTaskManagerMock, scheduler: WorkflowSchedulerMock(), provider: cloudProviderMock, coordinator: fileCoordinator, localURLProvider: localURLProviderMock, taskRegistrator: taskRegistratorMock) } } diff --git a/CryptomatorFileProviderTests/FileProviderAdapterManagerTests.swift b/CryptomatorFileProviderTests/FileProviderAdapterManagerTests.swift index 22f4bca3f..683b8e843 100644 --- a/CryptomatorFileProviderTests/FileProviderAdapterManagerTests.swift +++ b/CryptomatorFileProviderTests/FileProviderAdapterManagerTests.swift @@ -29,6 +29,7 @@ class FileProviderAdapterManagerTests: XCTestCase { var workingSetObservationMock: WorkingSetObservingMock! var fileProviderNotificatorMock: FileProviderNotificatorTypeMock! var localURLProviderMock: LocalURLProviderMock! + var taskRegistratorMock: SessionTaskRegistratorMock! private enum ErrorMock: Error { case test } @@ -44,6 +45,7 @@ class FileProviderAdapterManagerTests: XCTestCase { workingSetObservationMock = WorkingSetObservingMock() fileProviderNotificatorMock = FileProviderNotificatorTypeMock() localURLProviderMock = LocalURLProviderMock() + taskRegistratorMock = SessionTaskRegistratorMock() fileProviderAdapterManager = FileProviderAdapterManager(masterkeyCacheManager: masterkeyCacheManagerMock, vaultKeepUnlockedHelper: vaultKeepUnlockedHelperMock, vaultKeepUnlockedSettings: vaultKeepUnlockedSettingsMock, vaultManager: vaultManagerMock, adapterCache: adapterCacheMock, notificatorManager: notificatorManagerMock, unlockMonitor: UnlockMonitor(), providerIdentifier: providerIdentifier) tmpURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) dbPath = tmpURL.appendingPathComponent("db.sqlite", isDirectory: false) @@ -60,7 +62,7 @@ class FileProviderAdapterManagerTests: XCTestCase { func testGetAdapterNotCachedNoAutoUnlock() throws { vaultKeepUnlockedHelperMock.shouldAutoUnlockVaultWithVaultUIDReturnValue = false - XCTAssertThrowsError(try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock)) { error in + XCTAssertThrowsError(try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock, taskRegistrator: taskRegistratorMock)) { error in XCTAssertEqual(.defaultLock, error as? UnlockMonitorError) } XCTAssertEqual([vaultUID], vaultKeepUnlockedHelperMock.shouldAutoUnlockVaultWithVaultUIDReceivedInvocations) @@ -72,7 +74,7 @@ class FileProviderAdapterManagerTests: XCTestCase { vaultManagerMock.createVaultProviderWithUIDMasterkeyReturnValue = CustomCloudProviderMock() let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) masterkeyCacheManagerMock.getMasterkeyForVaultUIDReturnValue = masterkey - let adapter = try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock) + let adapter = try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock, taskRegistrator: taskRegistratorMock) XCTAssertEqual([vaultUID], vaultKeepUnlockedHelperMock.shouldAutoUnlockVaultWithVaultUIDReceivedInvocations) XCTAssertFalse(masterkeyCacheManagerMock.removeCachedMasterkeyForVaultUIDCalled) @@ -91,7 +93,7 @@ class FileProviderAdapterManagerTests: XCTestCase { vaultManagerMock.createVaultProviderWithUIDMasterkeyThrowableError = ErrorMock.test let masterkey = Masterkey.createFromRaw(aesMasterKey: [UInt8](repeating: 0x55, count: 32), macMasterKey: [UInt8](repeating: 0x77, count: 32)) masterkeyCacheManagerMock.getMasterkeyForVaultUIDReturnValue = masterkey - XCTAssertThrowsError(try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock)) { error in + XCTAssertThrowsError(try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock, taskRegistrator: taskRegistratorMock)) { error in XCTAssertEqual(.test, error as? ErrorMock) } XCTAssertEqual([vaultUID], vaultKeepUnlockedHelperMock.shouldAutoUnlockVaultWithVaultUIDReceivedInvocations) @@ -103,7 +105,7 @@ class FileProviderAdapterManagerTests: XCTestCase { func testGetAdapterNotCachedAutoUnlockMissingMasterkey() throws { vaultKeepUnlockedHelperMock.shouldAutoUnlockVaultWithVaultUIDReturnValue = true masterkeyCacheManagerMock.getMasterkeyForVaultUIDReturnValue = nil - XCTAssertThrowsError(try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock)) { error in + XCTAssertThrowsError(try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock, taskRegistrator: taskRegistratorMock)) { error in XCTAssertEqual(.defaultLock, error as? UnlockMonitorError) } XCTAssertEqual([vaultUID], vaultKeepUnlockedHelperMock.shouldAutoUnlockVaultWithVaultUIDReceivedInvocations) @@ -118,7 +120,7 @@ class FileProviderAdapterManagerTests: XCTestCase { vaultKeepUnlockedHelperMock.shouldAutoLockVaultWithVaultUIDReturnValue = false let fileProviderAdapterStub = FileProviderAdapterTypeMock() adapterCacheMock.getItemIdentifierReturnValue = AdapterCacheItem(adapter: fileProviderAdapterStub, maintenanceManager: MaintenanceManagerMock(), workingSetObserver: workingSetObservationMock) - let adapter = try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock) + let adapter = try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock, taskRegistrator: taskRegistratorMock) XCTAssert(adapter === fileProviderAdapterStub) try assertLastUsedDateSet() } @@ -129,7 +131,7 @@ class FileProviderAdapterManagerTests: XCTestCase { let maintenanceManagerMock = MaintenanceManagerMock() notificatorManagerMock.getFileProviderNotificatorForReturnValue = fileProviderNotificatorMock adapterCacheMock.getItemIdentifierReturnValue = AdapterCacheItem(adapter: fileProviderAdapterStub, maintenanceManager: maintenanceManagerMock, workingSetObserver: workingSetObservationMock) - XCTAssertThrowsError(try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock)) { error in + XCTAssertThrowsError(try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock, taskRegistrator: taskRegistratorMock)) { error in XCTAssertEqual(.defaultLock, error as? UnlockMonitorError) } XCTAssertEqual(1, maintenanceManagerMock.enableMaintenanceModeCallsCount) @@ -144,7 +146,7 @@ class FileProviderAdapterManagerTests: XCTestCase { let maintenanceManagerMock = MaintenanceManagerMock() maintenanceManagerMock.enableMaintenanceModeThrowableError = ErrorMock.test adapterCacheMock.getItemIdentifierReturnValue = AdapterCacheItem(adapter: fileProviderAdapterStub, maintenanceManager: maintenanceManagerMock, workingSetObserver: workingSetObservationMock) - XCTAssertThrowsError(try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock)) { error in + XCTAssertThrowsError(try fileProviderAdapterManager.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock, taskRegistrator: taskRegistratorMock)) { error in XCTAssertEqual(.defaultLock, error as? UnlockMonitorError) } XCTAssertFalse(maintenanceManagerMock.disableMaintenanceModeCalled) @@ -177,7 +179,7 @@ class FileProviderAdapterManagerTests: XCTestCase { let kek = [UInt8](repeating: 0x55, count: 32) vaultManagerMock.manualUnlockVaultWithUIDKekReturnValue = CustomCloudProviderMock() notificatorManagerMock.getFileProviderNotificatorForReturnValue = fileProviderNotificatorMock - try fileProviderAdapterManager.unlockVault(with: domain.identifier, kek: kek, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock) + try fileProviderAdapterManager.unlockVault(with: domain.identifier, kek: kek, dbPath: dbPath, delegate: localURLProviderMock, notificator: fileProviderNotificatorMock, taskRegistrator: taskRegistratorMock) XCTAssertEqual(1, vaultManagerMock.manualUnlockVaultWithUIDKekCallsCount) XCTAssertEqual(vaultUID, vaultManagerMock.manualUnlockVaultWithUIDKekReceivedArguments?.vaultUID) diff --git a/CryptomatorFileProviderTests/FileProviderEnumeratorTests.swift b/CryptomatorFileProviderTests/FileProviderEnumeratorTests.swift index 1a723d968..74febd900 100644 --- a/CryptomatorFileProviderTests/FileProviderEnumeratorTests.swift +++ b/CryptomatorFileProviderTests/FileProviderEnumeratorTests.swift @@ -20,6 +20,7 @@ class FileProviderEnumeratorTestCase: XCTestCase { var adapterProvidingMock: FileProviderAdapterProvidingMock! var adapterMock: FileProviderAdapterTypeMock! var localURLProviderMock: LocalURLProviderMock! + var taskRegistratorMock: SessionTaskRegistratorMock! let dbPath = FileManager.default.temporaryDirectory let domain = NSFileProviderDomain(vaultUID: "VaultUID-12345", displayName: "Test Vault") let items: [FileProviderItem] = [ @@ -36,6 +37,7 @@ class FileProviderEnumeratorTestCase: XCTestCase { adapterMock = FileProviderAdapterTypeMock() adapterProvidingMock.unlockMonitor = UnlockMonitor() localURLProviderMock = LocalURLProviderMock() + taskRegistratorMock = SessionTaskRegistratorMock() } func createSyncAnchor(from date: Date, locked: Bool = false) throws -> NSFileProviderSyncAnchor { @@ -43,7 +45,7 @@ class FileProviderEnumeratorTestCase: XCTestCase { } func createFullyMockedEnumerator(for itemIdentifier: NSFileProviderItemIdentifier) -> FileProviderEnumerator { - return FileProviderEnumerator(enumeratedItemIdentifier: itemIdentifier, notificator: notificatorMock, domain: domain, dbPath: dbPath, localURLProvider: localURLProviderMock, adapterProvider: adapterProvidingMock) + return FileProviderEnumerator(enumeratedItemIdentifier: itemIdentifier, notificator: notificatorMock, domain: domain, dbPath: dbPath, localURLProvider: localURLProviderMock, adapterProvider: adapterProvidingMock, taskRegistrator: taskRegistratorMock) } func assertChangeObserverUpdated(deletedItems: [NSFileProviderItemIdentifier], updatedItems: [FileProviderItem], currentSyncAnchor: NSFileProviderSyncAnchor) { @@ -64,7 +66,7 @@ class FileProviderEnumeratorTests: FileProviderEnumeratorTestCase { let enumerator = createFullyMockedEnumerator(for: .rootContainer) let page = NSFileProviderPage(NSFileProviderPage.initialPageSortedByName as Data) let itemList = FileProviderItemList(items: items, nextPageToken: nil) - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue = adapterMock adapterMock.enumerateItemsForWithPageTokenReturnValue = Promise(itemList) enumerationObserverMock.finishEnumeratingUpToClosure = { _ in expectation.fulfill() @@ -83,7 +85,7 @@ class FileProviderEnumeratorTests: FileProviderEnumeratorTestCase { let nextPageToken = "Foo" let nextFileProviderPage = NSFileProviderPage(nextPageToken.data(using: .utf8)!) let itemList = FileProviderItemList(items: items, nextPageToken: nextFileProviderPage) - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue = adapterMock adapterMock.enumerateItemsForWithPageTokenReturnValue = Promise(itemList) enumerationObserverMock.finishEnumeratingUpToClosure = { _ in expectation.fulfill() @@ -101,7 +103,7 @@ class FileProviderEnumeratorTests: FileProviderEnumeratorTestCase { let pageToken = "Foo" let page = NSFileProviderPage(pageToken.data(using: .utf8)!) let itemList = FileProviderItemList(items: items, nextPageToken: nil) - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue = adapterMock adapterMock.enumerateItemsForWithPageTokenReturnValue = Promise(itemList) enumerationObserverMock.finishEnumeratingUpToClosure = { _ in expectation.fulfill() @@ -117,7 +119,7 @@ class FileProviderEnumeratorTests: FileProviderEnumeratorTestCase { let expectation = XCTestExpectation() let enumerator = createFullyMockedEnumerator(for: .rootContainer) let page = NSFileProviderPage(NSFileProviderPage.initialPageSortedByName as Data) - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue = adapterMock adapterMock.enumerateItemsForWithPageTokenReturnValue = Promise(CloudProviderError.noInternetConnection) enumerationObserverMock.finishEnumeratingWithErrorClosure = { _ in expectation.fulfill() @@ -133,7 +135,7 @@ class FileProviderEnumeratorTests: FileProviderEnumeratorTestCase { let expectation = XCTestExpectation() let enumerator = createFullyMockedEnumerator(for: .rootContainer) let page = NSFileProviderPage(NSFileProviderPage.initialPageSortedByName as Data) - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorThrowableError = UnlockMonitorError.defaultLock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorThrowableError = UnlockMonitorError.defaultLock enumerationObserverMock.finishEnumeratingWithErrorClosure = { _ in expectation.fulfill() } diff --git a/CryptomatorFileProviderTests/Middleware/TaskExecutor/CloudTaskExecutorTestCase.swift b/CryptomatorFileProviderTests/Middleware/TaskExecutor/CloudTaskExecutorTestCase.swift index f4dfd8ac0..bf1d2b202 100644 --- a/CryptomatorFileProviderTests/Middleware/TaskExecutor/CloudTaskExecutorTestCase.swift +++ b/CryptomatorFileProviderTests/Middleware/TaskExecutor/CloudTaskExecutorTestCase.swift @@ -215,11 +215,11 @@ class CloudTaskExecutorTestCase: XCTestCase { fetchItemListResponse?(cloudPath, pageToken) ?? Promise(MockError.notMocked) } - func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise { + func downloadFile(from cloudPath: CryptomatorCloudAccessCore.CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promises.Promise { downloadFileResponse?(cloudPath, localURL) ?? Promise(MockError.notMocked) } - func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise { + func uploadFile(from localURL: URL, to cloudPath: CryptomatorCloudAccessCore.CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promises.Promise { uploadFileResponse?(localURL, cloudPath, replaceExisting) ?? Promise(MockError.notMocked) } @@ -341,9 +341,9 @@ class CloudTaskExecutorTestCase: XCTestCase { class DownloadTaskManagerMock: DownloadTaskManager { var removedTasks = [DownloadTaskRecord]() - func createTask(for item: ItemMetadata, replaceExisting: Bool, localURL: URL) throws -> DownloadTask { + func createTask(for item: ItemMetadata, replaceExisting: Bool, localURL: URL, onURLSessionTaskCreation: CryptomatorFileProvider.URLSessionTaskCreationClosure?) throws -> DownloadTask { let taskRecord = DownloadTaskRecord(correspondingItem: item.id!, replaceExisting: replaceExisting, localURL: localURL) - return DownloadTask(taskRecord: taskRecord, itemMetadata: item) + return DownloadTask(taskRecord: taskRecord, itemMetadata: item, onURLSessionTaskCreation: onURLSessionTaskCreation) } func removeTaskRecord(_ task: DownloadTaskRecord) throws { diff --git a/CryptomatorFileProviderTests/Middleware/TaskExecutor/DownloadTaskExecutorTests.swift b/CryptomatorFileProviderTests/Middleware/TaskExecutor/DownloadTaskExecutorTests.swift index b595eb4fc..fac842469 100644 --- a/CryptomatorFileProviderTests/Middleware/TaskExecutor/DownloadTaskExecutorTests.swift +++ b/CryptomatorFileProviderTests/Middleware/TaskExecutor/DownloadTaskExecutorTests.swift @@ -22,7 +22,7 @@ class DownloadTaskExecutorTests: CloudTaskExecutorTestCase { let itemMetadata = ItemMetadata(id: itemID, name: "File 1", type: .file, size: 14, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: cloudPath, isPlaceholderItem: false) let downloadTaskRecord = DownloadTaskRecord(correspondingItem: itemMetadata.id!, replaceExisting: false, localURL: localURL) - let downloadTask = DownloadTask(taskRecord: downloadTaskRecord, itemMetadata: itemMetadata) + let downloadTask = DownloadTask(taskRecord: downloadTaskRecord, itemMetadata: itemMetadata, onURLSessionTaskCreation: nil) let taskExecutor = DownloadTaskExecutor(domainIdentifier: .test, provider: cloudProviderMock, itemMetadataManager: metadataManagerMock, cachedFileManager: cachedFileManagerMock, downloadTaskManager: downloadTaskManagerMock) @@ -61,7 +61,7 @@ class DownloadTaskExecutorTests: CloudTaskExecutorTestCase { } let downloadTaskRecord = DownloadTaskRecord(correspondingItem: itemMetadata.id!, replaceExisting: false, localURL: localURL) - let downloadTask = DownloadTask(taskRecord: downloadTaskRecord, itemMetadata: itemMetadata) + let downloadTask = DownloadTask(taskRecord: downloadTaskRecord, itemMetadata: itemMetadata, onURLSessionTaskCreation: nil) let taskExecutor = DownloadTaskExecutor(domainIdentifier: .test, provider: errorCloudProviderMock, itemMetadataManager: metadataManagerMock, cachedFileManager: cachedFileManagerMock, downloadTaskManager: downloadTaskManagerMock) @@ -92,7 +92,7 @@ class DownloadTaskExecutorTests: CloudTaskExecutorTestCase { let itemMetadata = ItemMetadata(id: itemID, name: "File 1", type: .file, size: 14, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: cloudPath, isPlaceholderItem: false) let downloadTaskRecord = DownloadTaskRecord(correspondingItem: itemMetadata.id!, replaceExisting: true, localURL: localURL) - let downloadTask = DownloadTask(taskRecord: downloadTaskRecord, itemMetadata: itemMetadata) + let downloadTask = DownloadTask(taskRecord: downloadTaskRecord, itemMetadata: itemMetadata, onURLSessionTaskCreation: nil) let taskExecutor = DownloadTaskExecutor(domainIdentifier: .test, provider: cloudProviderMock, itemMetadataManager: metadataManagerMock, cachedFileManager: cachedFileManagerMock, downloadTaskManager: downloadTaskManagerMock) diff --git a/CryptomatorFileProviderTests/Middleware/TaskExecutor/UploadTaskExecutorTests.swift b/CryptomatorFileProviderTests/Middleware/TaskExecutor/UploadTaskExecutorTests.swift index b6a8ed5d3..2c2b4f9ee 100644 --- a/CryptomatorFileProviderTests/Middleware/TaskExecutor/UploadTaskExecutorTests.swift +++ b/CryptomatorFileProviderTests/Middleware/TaskExecutor/UploadTaskExecutorTests.swift @@ -27,7 +27,7 @@ class UploadTaskExecutorTests: CloudTaskExecutorTestCase { let mockedCloudDate = Date(timeIntervalSinceReferenceDate: 0) cloudProviderMock.lastModifiedDate[itemMetadata.cloudPath.path] = mockedCloudDate let uploadTaskRecord = UploadTaskRecord(correspondingItem: itemID, lastFailedUploadDate: nil, uploadErrorCode: nil, uploadErrorDomain: nil) - let uploadTask = UploadTask(taskRecord: uploadTaskRecord, itemMetadata: itemMetadata) + let uploadTask = UploadTask(taskRecord: uploadTaskRecord, itemMetadata: itemMetadata, onURLSessionTaskCreation: nil) uploadTaskExecutor.execute(task: uploadTask).then { _ in XCTAssertEqual("TestContent".data(using: .utf8), self.cloudProviderMock.createdFiles["/FileToBeUploaded"]) @@ -69,7 +69,7 @@ class UploadTaskExecutorTests: CloudTaskExecutorTestCase { let uploadTaskExecutor = UploadTaskExecutor(domainIdentifier: .test, provider: cloudProviderMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, uploadTaskManager: uploadTaskManagerMock) let uploadTaskRecord = UploadTaskRecord(correspondingItem: itemMetadata.id!, lastFailedUploadDate: nil, uploadErrorCode: nil, uploadErrorDomain: nil) - let uploadTask = UploadTask(taskRecord: uploadTaskRecord, itemMetadata: itemMetadata) + let uploadTask = UploadTask(taskRecord: uploadTaskRecord, itemMetadata: itemMetadata, onURLSessionTaskCreation: nil) uploadTaskExecutor.execute(task: uploadTask).then { _ in XCTFail("Promise should not fulfill for missing local cached file info") @@ -100,7 +100,7 @@ class UploadTaskExecutorTests: CloudTaskExecutorTestCase { let uploadTaskExecutor = UploadTaskExecutor(domainIdentifier: .test, provider: cloudProviderUploadInconsistencyMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, uploadTaskManager: uploadTaskManagerMock) let uploadTaskRecord = UploadTaskRecord(correspondingItem: itemMetadata.id!, lastFailedUploadDate: nil, uploadErrorCode: nil, uploadErrorDomain: nil) - let uploadTask = UploadTask(taskRecord: uploadTaskRecord, itemMetadata: itemMetadata) + let uploadTask = UploadTask(taskRecord: uploadTaskRecord, itemMetadata: itemMetadata, onURLSessionTaskCreation: nil) uploadTaskExecutor.execute(task: uploadTask).then { item in // Verify that the file has been modified since the upload began. @@ -144,7 +144,7 @@ class UploadTaskExecutorTests: CloudTaskExecutorTestCase { let uploadTaskExecutor = UploadTaskExecutor(domainIdentifier: .test, provider: errorCloudProviderMock, cachedFileManager: cachedFileManagerMock, itemMetadataManager: metadataManagerMock, uploadTaskManager: uploadTaskManagerMock) let uploadTaskRecord = UploadTaskRecord(correspondingItem: itemMetadata.id!, lastFailedUploadDate: nil, uploadErrorCode: nil, uploadErrorDomain: nil) - let uploadTask = UploadTask(taskRecord: uploadTaskRecord, itemMetadata: itemMetadata) + let uploadTask = UploadTask(taskRecord: uploadTaskRecord, itemMetadata: itemMetadata, onURLSessionTaskCreation: nil) let promise = uploadTaskExecutor.execute(task: uploadTask) wait(for: promise) @@ -163,7 +163,7 @@ class UploadTaskExecutorTests: CloudTaskExecutorTestCase { } private class CloudProviderUploadInconsistencyMock: CustomCloudProviderMock { - override func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise { + override func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise { precondition(localURL.isFileURL) precondition(!localURL.hasDirectoryPath) do { diff --git a/CryptomatorFileProviderTests/Mocks/CustomCloudProviderMock.swift b/CryptomatorFileProviderTests/Mocks/CustomCloudProviderMock.swift index 9acd4db17..dd523242a 100644 --- a/CryptomatorFileProviderTests/Mocks/CustomCloudProviderMock.swift +++ b/CryptomatorFileProviderTests/Mocks/CustomCloudProviderMock.swift @@ -96,7 +96,7 @@ class CustomCloudProviderMock: CloudProvider { } } - public func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise { + public func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { precondition(localURL.isFileURL) precondition(!localURL.hasDirectoryPath) if let data = files[cloudPath.path] { @@ -111,7 +111,7 @@ class CustomCloudProviderMock: CloudProvider { } } - public func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise { + public func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise { precondition(localURL.isFileURL) precondition(!localURL.hasDirectoryPath) switch cloudPath { diff --git a/CryptomatorFileProviderTests/Mocks/FileProviderAdapterProvidingMock.swift b/CryptomatorFileProviderTests/Mocks/FileProviderAdapterProvidingMock.swift index 56d537e61..c6dd6d56c 100644 --- a/CryptomatorFileProviderTests/Mocks/FileProviderAdapterProvidingMock.swift +++ b/CryptomatorFileProviderTests/Mocks/FileProviderAdapterProvidingMock.swift @@ -28,25 +28,25 @@ final class FileProviderAdapterProvidingMock: FileProviderAdapterProviding { // MARK: - getAdapter - var getAdapterForDomainDbPathDelegateNotificatorThrowableError: Error? - var getAdapterForDomainDbPathDelegateNotificatorCallsCount = 0 - var getAdapterForDomainDbPathDelegateNotificatorCalled: Bool { - getAdapterForDomainDbPathDelegateNotificatorCallsCount > 0 + var getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorThrowableError: Error? + var getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorCallsCount = 0 + var getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorCalled: Bool { + getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorCallsCount > 0 } - var getAdapterForDomainDbPathDelegateNotificatorReceivedArguments: (domain: NSFileProviderDomain, dbPath: URL, delegate: LocalURLProviderType, notificator: FileProviderNotificatorType)? - var getAdapterForDomainDbPathDelegateNotificatorReceivedInvocations: [(domain: NSFileProviderDomain, dbPath: URL, delegate: LocalURLProviderType?, notificator: FileProviderNotificatorType)] = [] - var getAdapterForDomainDbPathDelegateNotificatorReturnValue: FileProviderAdapterType! - var getAdapterForDomainDbPathDelegateNotificatorClosure: ((NSFileProviderDomain, URL, LocalURLProviderType, FileProviderNotificatorType) throws -> FileProviderAdapterType)? + var getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReceivedArguments: (domain: NSFileProviderDomain, dbPath: URL, delegate: LocalURLProviderType, notificator: FileProviderNotificatorType, taskRegistrator: SessionTaskRegistrator)? + var getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReceivedInvocations: [(domain: NSFileProviderDomain, dbPath: URL, delegate: LocalURLProviderType, notificator: FileProviderNotificatorType, taskRegistrator: SessionTaskRegistrator)] = [] + var getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue: FileProviderAdapterType! + var getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorClosure: ((NSFileProviderDomain, URL, LocalURLProviderType, FileProviderNotificatorType, SessionTaskRegistrator) throws -> FileProviderAdapterType)? - func getAdapter(forDomain domain: NSFileProviderDomain, dbPath: URL, delegate: LocalURLProviderType, notificator: FileProviderNotificatorType) throws -> FileProviderAdapterType { - if let error = getAdapterForDomainDbPathDelegateNotificatorThrowableError { + func getAdapter(forDomain domain: NSFileProviderDomain, dbPath: URL, delegate: LocalURLProviderType, notificator: FileProviderNotificatorType, taskRegistrator: SessionTaskRegistrator) throws -> FileProviderAdapterType { + if let error = getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorThrowableError { throw error } - getAdapterForDomainDbPathDelegateNotificatorCallsCount += 1 - getAdapterForDomainDbPathDelegateNotificatorReceivedArguments = (domain: domain, dbPath: dbPath, delegate: delegate, notificator: notificator) - getAdapterForDomainDbPathDelegateNotificatorReceivedInvocations.append((domain: domain, dbPath: dbPath, delegate: delegate, notificator: notificator)) - return try getAdapterForDomainDbPathDelegateNotificatorClosure.map({ try $0(domain, dbPath, delegate, notificator) }) ?? getAdapterForDomainDbPathDelegateNotificatorReturnValue + getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorCallsCount += 1 + getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReceivedArguments = (domain: domain, dbPath: dbPath, delegate: delegate, notificator: notificator, taskRegistrator: taskRegistrator) + getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReceivedInvocations.append((domain: domain, dbPath: dbPath, delegate: delegate, notificator: notificator, taskRegistrator: taskRegistrator)) + return try getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorClosure.map({ try $0(domain, dbPath, delegate, notificator, taskRegistrator) }) ?? getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue } } diff --git a/CryptomatorFileProviderTests/Mocks/SessionTaskRegistratorMock.swift b/CryptomatorFileProviderTests/Mocks/SessionTaskRegistratorMock.swift new file mode 100644 index 000000000..a295e984f --- /dev/null +++ b/CryptomatorFileProviderTests/Mocks/SessionTaskRegistratorMock.swift @@ -0,0 +1,34 @@ +// +// SessionTaskRegistratorMock.swift +// CryptomatorFileProviderTests +// +// Created by Philipp Schmid on 11.01.23. +// Copyright © 2023 Skymatic GmbH. All rights reserved. +// + +import CryptomatorFileProvider +import FileProvider +import Foundation + +// swiftlint:disable all +final class SessionTaskRegistratorMock: SessionTaskRegistrator { + // MARK: - register + + var registerForItemWithIdentifierCompletionHandlerCallsCount = 0 + var registerForItemWithIdentifierCompletionHandlerCalled: Bool { + registerForItemWithIdentifierCompletionHandlerCallsCount > 0 + } + + var registerForItemWithIdentifierCompletionHandlerReceivedArguments: (task: SessionTask, identifier: NSFileProviderItemIdentifier, completion: (Error?) -> Void)? + var registerForItemWithIdentifierCompletionHandlerReceivedInvocations: [(task: SessionTask, identifier: NSFileProviderItemIdentifier, completion: (Error?) -> Void)] = [] + var registerForItemWithIdentifierCompletionHandlerClosure: ((SessionTask, NSFileProviderItemIdentifier, @escaping (Error?) -> Void) -> Void)? + + func register(_ task: SessionTask, forItemWithIdentifier identifier: NSFileProviderItemIdentifier, completionHandler completion: @escaping (Error?) -> Void) { + registerForItemWithIdentifierCompletionHandlerCallsCount += 1 + registerForItemWithIdentifierCompletionHandlerReceivedArguments = (task: task, identifier: identifier, completion: completion) + registerForItemWithIdentifierCompletionHandlerReceivedInvocations.append((task: task, identifier: identifier, completion: completion)) + registerForItemWithIdentifierCompletionHandlerClosure?(task, identifier, completion) + } +} + +// swiftlint:enable all diff --git a/CryptomatorFileProviderTests/Mocks/UploadTaskManagerMock.swift b/CryptomatorFileProviderTests/Mocks/UploadTaskManagerMock.swift index 5f9f5efe6..c73ee111f 100644 --- a/CryptomatorFileProviderTests/Mocks/UploadTaskManagerMock.swift +++ b/CryptomatorFileProviderTests/Mocks/UploadTaskManagerMock.swift @@ -148,25 +148,25 @@ final class UploadTaskManagerMock: UploadTaskManager { // MARK: - getTask - var getTaskForThrowableError: Error? - var getTaskForCallsCount = 0 - var getTaskForCalled: Bool { - getTaskForCallsCount > 0 + var getTaskForOnURLSessionTaskCreationThrowableError: Error? + var getTaskForOnURLSessionTaskCreationCallsCount = 0 + var getTaskForOnURLSessionTaskCreationCalled: Bool { + getTaskForOnURLSessionTaskCreationCallsCount > 0 } - var getTaskForReceivedUploadTask: UploadTaskRecord? - var getTaskForReceivedInvocations: [UploadTaskRecord] = [] - var getTaskForReturnValue: UploadTask! - var getTaskForClosure: ((UploadTaskRecord) throws -> UploadTask)? + var getTaskForOnURLSessionTaskCreationReceivedArguments: (uploadTask: UploadTaskRecord, onURLSessionTaskCreation: URLSessionTaskCreationClosure?)? + var getTaskForOnURLSessionTaskCreationReceivedInvocations: [(uploadTask: UploadTaskRecord, onURLSessionTaskCreation: URLSessionTaskCreationClosure?)] = [] + var getTaskForOnURLSessionTaskCreationReturnValue: UploadTask! + var getTaskForOnURLSessionTaskCreationClosure: ((UploadTaskRecord, URLSessionTaskCreationClosure?) throws -> UploadTask)? - func getTask(for uploadTask: UploadTaskRecord) throws -> UploadTask { - if let error = getTaskForThrowableError { + func getTask(for uploadTask: UploadTaskRecord, onURLSessionTaskCreation: URLSessionTaskCreationClosure?) throws -> UploadTask { + if let error = getTaskForOnURLSessionTaskCreationThrowableError { throw error } - getTaskForCallsCount += 1 - getTaskForReceivedUploadTask = uploadTask - getTaskForReceivedInvocations.append(uploadTask) - return try getTaskForClosure.map({ try $0(uploadTask) }) ?? getTaskForReturnValue + getTaskForOnURLSessionTaskCreationCallsCount += 1 + getTaskForOnURLSessionTaskCreationReceivedArguments = (uploadTask: uploadTask, onURLSessionTaskCreation: onURLSessionTaskCreation) + getTaskForOnURLSessionTaskCreationReceivedInvocations.append((uploadTask: uploadTask, onURLSessionTaskCreation: onURLSessionTaskCreation)) + return try getTaskForOnURLSessionTaskCreationClosure.map({ try $0(uploadTask, onURLSessionTaskCreation) }) ?? getTaskForOnURLSessionTaskCreationReturnValue } } diff --git a/CryptomatorFileProviderTests/ServiceSource/UploadRetryingServiceSourceTests.swift b/CryptomatorFileProviderTests/ServiceSource/UploadRetryingServiceSourceTests.swift index 1809ba9fa..8a0f1bc73 100644 --- a/CryptomatorFileProviderTests/ServiceSource/UploadRetryingServiceSourceTests.swift +++ b/CryptomatorFileProviderTests/ServiceSource/UploadRetryingServiceSourceTests.swift @@ -29,7 +29,8 @@ class UploadRetryingServiceSourceTests: XCTestCase { dbPath: dbPath, delegate: urlProviderMock, adapterManager: adapterProvidingMock, - progressManager: progressManagerMock) + progressManager: progressManagerMock, + taskRegistrator: SessionTaskRegistratorMock()) } override func tearDownWithError() throws { @@ -38,7 +39,7 @@ class UploadRetryingServiceSourceTests: XCTestCase { func testRetryUpload() throws { let adapterMock = FileProviderAdapterTypeMock() - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue = adapterMock let expectation = XCTestExpectation() let itemIdentifiers = [NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 2), NSFileProviderItemIdentifier(domainIdentifier: .test, itemID: 3)] diff --git a/CryptomatorFileProviderTests/WorkingSetEnumerationTests.swift b/CryptomatorFileProviderTests/WorkingSetEnumerationTests.swift index 6d5595c74..b312e5b7c 100644 --- a/CryptomatorFileProviderTests/WorkingSetEnumerationTests.swift +++ b/CryptomatorFileProviderTests/WorkingSetEnumerationTests.swift @@ -20,7 +20,7 @@ class WorkingSetEnumerationTests: FileProviderEnumeratorTestCase { override func setUpWithError() throws { try super.setUpWithError() lastKnownSyncAnchor = try createSyncAnchor(from: .distantPast, locked: false) - enumerator = FileProviderEnumerator(enumeratedItemIdentifier: .workingSet, notificator: FileProviderNotificator(manager: EnumerationSignalingMock()), domain: domain, dbPath: dbPath, localURLProvider: localURLProviderMock, adapterProvider: adapterProvidingMock) + enumerator = FileProviderEnumerator(enumeratedItemIdentifier: .workingSet, notificator: FileProviderNotificator(manager: EnumerationSignalingMock()), domain: domain, dbPath: dbPath, localURLProvider: localURLProviderMock, adapterProvider: adapterProvidingMock, taskRegistrator: taskRegistratorMock) setupEnumerateItemsObserver() setupEnumerateChangesObserver() @@ -53,7 +53,7 @@ class WorkingSetEnumerationTests: FileProviderEnumeratorTestCase { let enumerator = createFullyMockedEnumerator(for: .workingSet) let lastUnlockedDate = Date() adapterMock.lastUnlockedDate = lastUnlockedDate - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue = adapterMock notificatorMock.getItemIdentifiersToDeleteFromWorkingSetReturnValue = deleteItemIdentifiers notificatorMock.popUpdateWorkingSetItemsReturnValue = items @@ -74,7 +74,7 @@ class WorkingSetEnumerationTests: FileProviderEnumeratorTestCase { let enumerator = createFullyMockedEnumerator(for: .workingSet) let lastUnlockedDate = Date.distantPast adapterMock.lastUnlockedDate = lastUnlockedDate - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue = adapterMock notificatorMock.getItemIdentifiersToDeleteFromWorkingSetReturnValue = deleteItemIdentifiers notificatorMock.popUpdateWorkingSetItemsReturnValue = items @@ -94,7 +94,7 @@ class WorkingSetEnumerationTests: FileProviderEnumeratorTestCase { let expectation = XCTestExpectation() let enumerator = createFullyMockedEnumerator(for: .workingSet) adapterMock.lastUnlockedDate = Date() - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorReturnValue = adapterMock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorReturnValue = adapterMock changeObserverMock.finishEnumeratingWithErrorClosure = { error in XCTAssertEqual(NSFileProviderError(.syncAnchorExpired), error as? NSFileProviderError) expectation.fulfill() @@ -116,7 +116,7 @@ class WorkingSetEnumerationTests: FileProviderEnumeratorTestCase { } private func simulateLockedVault() { - adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorThrowableError = UnlockMonitorError.defaultLock + adapterProvidingMock.getAdapterForDomainDbPathDelegateNotificatorTaskRegistratorThrowableError = UnlockMonitorError.defaultLock } private func assertWorkingSetDidNotChange() { diff --git a/CryptomatorTests/CreateNewFolderViewModelTests.swift b/CryptomatorTests/CreateNewFolderViewModelTests.swift index eb8ad9514..a3735f7ad 100644 --- a/CryptomatorTests/CreateNewFolderViewModelTests.swift +++ b/CryptomatorTests/CreateNewFolderViewModelTests.swift @@ -74,11 +74,11 @@ private class CloudProviderMockOld: CloudProvider { return Promise(CloudProviderError.noInternetConnection) } - func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise { + func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { return Promise(CloudProviderError.noInternetConnection) } - func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise { + func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise { return Promise(CloudProviderError.noInternetConnection) } diff --git a/FileProviderExtension/FileProviderExtension.swift b/FileProviderExtension/FileProviderExtension.swift index d707e9761..6dd763a55 100644 --- a/FileProviderExtension/FileProviderExtension.swift +++ b/FileProviderExtension/FileProviderExtension.swift @@ -218,12 +218,12 @@ class FileProviderExtension: NSFileProviderExtension { #else // TODO: Change error handling here DDLogDebug("FPExt: enumerator(for: \(containerItemIdentifier)) called") - guard let domain = domain, let dbPath = dbPath, let notificator = notificator, let localURLProvider = localURLProvider else { + guard let domain = domain, let dbPath = dbPath, let notificator = notificator, let localURLProvider = localURLProvider, let manager = NSFileProviderManager(for: domain) else { // no domain ==> no installed vault DDLogError("enumerator(for: \(containerItemIdentifier)) failed as the extension is not initialized") throw NSFileProviderError(.notAuthenticated) } - return FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier, notificator: notificator, domain: domain, dbPath: dbPath, localURLProvider: localURLProvider) + return FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier, notificator: notificator, domain: domain, dbPath: dbPath, localURLProvider: localURLProvider, taskRegistrator: manager) #endif } @@ -259,10 +259,10 @@ class FileProviderExtension: NSFileProviderExtension { dbPath: dbPath, delegate: LocalURLProvider(domain: snapshotDomain))) #else - if let domain = domain, let localURLProvider = localURLProvider, let dbPath = dbPath, let notificator = notificator { - serviceSources.append(VaultUnlockingServiceSource(domain: domain, notificator: notificator, dbPath: dbPath, delegate: localURLProvider)) - serviceSources.append(UploadRetryingServiceSource(domain: domain, notificator: notificator, dbPath: dbPath, delegate: localURLProvider)) - serviceSources.append(FileImportingServiceSource(domain: domain, notificator: notificator, dbPath: dbPath, delegate: localURLProvider)) + if let domain = domain, let localURLProvider = localURLProvider, let dbPath = dbPath, let notificator = notificator, let manager = NSFileProviderManager(for: domain) { + serviceSources.append(VaultUnlockingServiceSource(domain: domain, notificator: notificator, dbPath: dbPath, delegate: localURLProvider, taskRegistrator: manager)) + serviceSources.append(UploadRetryingServiceSource(domain: domain, notificator: notificator, dbPath: dbPath, delegate: localURLProvider, taskRegistrator: manager)) + serviceSources.append(FileImportingServiceSource(domain: domain, notificator: notificator, dbPath: dbPath, delegate: localURLProvider, taskRegistrator: manager)) } #endif let cacheManagingServiceSource = CacheManagingServiceSource(notificator: notificator) @@ -294,10 +294,14 @@ class FileProviderExtension: NSFileProviderExtension { } private func getAdapter() throws -> FileProviderAdapterType { - guard let domain = domain, let dbPath = dbPath, let notificator = notificator, let localURLProvider = localURLProvider else { + guard let domain = domain, let dbPath = dbPath, let notificator = notificator, let localURLProvider = localURLProvider, let manager = NSFileProviderManager(for: domain) else { throw FileProviderDecoratorSetupError.domainIsNil } - return try FileProviderAdapterManager.shared.getAdapter(forDomain: domain, dbPath: dbPath, delegate: localURLProvider, notificator: notificator) + return try FileProviderAdapterManager.shared.getAdapter(forDomain: domain, + dbPath: dbPath, + delegate: localURLProvider, + notificator: notificator, + taskRegistrator: manager) } func getAdapterWithWrappedError() throws -> FileProviderAdapterType {