From d3adab2b6f0f93e9e0c35c51405674a9bc168755 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 3 Jan 2023 15:47:39 +0100 Subject: [PATCH 01/15] Add background URLSession support to PCloudCloudProvider --- .../PCloud/PCloudCloudProvider.swift | 30 +++++------ .../PCloud/PCloudCredential.swift | 51 ++++++++++++++++++- .../VaultFormat6PCloudIntegrationTests.swift | 4 +- .../VaultFormat7PCloudIntegrationTests.swift | 5 +- .../PCloudCloudProviderIntegrationTests.swift | 10 ++-- 5 files changed, 79 insertions(+), 21 deletions(-) diff --git a/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift b/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift index d3a626b..cd15742 100644 --- a/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift @@ -11,12 +11,12 @@ import PCloudSDKSwift import Promises public class PCloudCloudProvider: CloudProvider { - private let credential: PCloudCredential private let identifierCache: PCloudIdentifierCache + private let client: PCloudClient - public init(credential: PCloudCredential) throws { - self.credential = credential + public init(client: PCloudClient) throws { self.identifierCache = try PCloudIdentifierCache() + self.client = client } public func fetchItemMetadata(at cloudPath: CloudPath) -> Promise { @@ -134,7 +134,7 @@ public class PCloudCloudProvider: CloudProvider { private func fetchFileMetadata(for item: PCloudItem) -> Promise { assert(item.itemType == .file) CloudAccessDDLogDebug("PCloudCloudProvider: fetchFileMetadata(for: \(item.identifier)) called") - return credential.client.getFileMetadata(item.identifier).execute().then { metadata -> CloudItemMetadata in + return client.getFileMetadata(item.identifier).execute().then { metadata -> CloudItemMetadata in CloudAccessDDLogDebug("PCloudCloudProvider: fetchFileMetadata(for: \(item.identifier)) received metadata: \(metadata)") try self.identifierCache.addOrUpdate(item) return self.convertToCloudItemMetadata(metadata, at: item.cloudPath) @@ -154,7 +154,7 @@ public class PCloudCloudProvider: CloudProvider { private func fetchFolderMetadata(for item: PCloudItem) -> Promise { assert(item.itemType == .folder) CloudAccessDDLogDebug("PCloudCloudProvider: fetchFolderMetadata(for: \(item.identifier)) called") - return credential.client.listFolder(item.identifier, recursively: false).execute().then { metadata -> CloudItemMetadata in + return client.listFolder(item.identifier, recursively: false).execute().then { metadata -> CloudItemMetadata in CloudAccessDDLogDebug("PCloudCloudProvider: fetchFolderMetadata(for: \(item.identifier)) received metadata: \(metadata)") try self.identifierCache.addOrUpdate(item) return self.convertToCloudItemMetadata(metadata, at: item.cloudPath) @@ -178,7 +178,7 @@ public class PCloudCloudProvider: CloudProvider { CloudAccessDDLogDebug("PCloudCloudProvider: fetchItemList(for: \(item.identifier)) failed with error: \(error)") return Promise(error) } - return credential.client.listFolder(item.identifier, recursively: false).execute().then { metadata -> CloudItemList in + return client.listFolder(item.identifier, recursively: false).execute().then { metadata -> CloudItemList in CloudAccessDDLogDebug("PCloudCloudProvider: fetchItemList(for: \(item.identifier)) received metadata: \(metadata)") for content in metadata.contents { guard let name = content.fileMetadata?.name ?? content.folderMetadata?.name else { @@ -220,7 +220,7 @@ public class PCloudCloudProvider: CloudProvider { CloudAccessDDLogDebug("PCloudCloudProvider: getFileLink(for: \(item.identifier)) failed with error: \(error)") return Promise(error) } - return credential.client.getFileLink(forFile: item.identifier).execute().then { metadata -> FileLink.Metadata in + return client.getFileLink(forFile: item.identifier).execute().then { metadata -> FileLink.Metadata in CloudAccessDDLogDebug("PCloudCloudProvider: getFileLink(for: \(item.identifier)) received metadata: \(metadata)") if let first = metadata.first { return first @@ -242,7 +242,7 @@ public class PCloudCloudProvider: CloudProvider { private func downloadFileLink(_ fileLink: FileLink.Metadata, to localURL: URL, with progress: Progress) -> Promise { CloudAccessDDLogDebug("PCloudCloudProvider: downloadFileLink(\(fileLink), to: \(localURL)) called") - let task = credential.client.downloadFile(from: fileLink.address, downloadTag: fileLink.downloadTag, to: { tmpURL in + let task = client.downloadFile(from: fileLink.address, downloadTag: fileLink.downloadTag, to: { tmpURL in try FileManager.default.moveItem(at: tmpURL, to: localURL) return localURL }) @@ -262,7 +262,7 @@ public class PCloudCloudProvider: CloudProvider { private func uploadFile(for parentItem: PCloudItem, from localURL: URL, to cloudPath: CloudPath) -> Promise { CloudAccessDDLogDebug("PCloudCloudProvider: uploadFile(for: \(parentItem.identifier), from: \(localURL), to: \(cloudPath.path)) called") let progress = Progress(totalUnitCount: -1) - let task = credential.client.upload(fromFileAt: localURL, toFolder: parentItem.identifier, asFileNamed: cloudPath.lastPathComponent) + let task = client.upload(fromFileAt: localURL, toFolder: parentItem.identifier, asFileNamed: cloudPath.lastPathComponent) task.addProgressBlock { numberOfBytesSent, totalNumberOfBytesToSend in progress.totalUnitCount = totalNumberOfBytesToSend progress.completedUnitCount = numberOfBytesSent @@ -287,7 +287,7 @@ public class PCloudCloudProvider: CloudProvider { private func createFolder(for parentItem: PCloudItem, with name: String) -> Promise { CloudAccessDDLogDebug("PCloudCloudProvider: createFolder(for: \(parentItem.identifier), with: \(name)) called") - return credential.client.createFolder(named: name, inFolder: parentItem.identifier).execute().then { metadata -> Void in + return client.createFolder(named: name, inFolder: parentItem.identifier).execute().then { metadata -> Void in CloudAccessDDLogDebug("PCloudCloudProvider: createFolder(for: \(parentItem.identifier), with: \(name)) received metadata: \(metadata)") let cloudPath = parentItem.cloudPath.appendingPathComponent(name) let item = PCloudItem(cloudPath: cloudPath, metadata: metadata) @@ -309,7 +309,7 @@ public class PCloudCloudProvider: CloudProvider { private func deleteFile(for item: PCloudItem) -> Promise { CloudAccessDDLogDebug("PCloudCloudProvider: deleteFile(for: \(item.identifier)) called") - return credential.client.deleteFile(item.identifier).execute().then { metadata -> Void in + return client.deleteFile(item.identifier).execute().then { metadata -> Void in CloudAccessDDLogDebug("PCloudCloudProvider: deleteFile(for: \(item.identifier)) received metadata: \(metadata)") try self.identifierCache.invalidate(item) }.recover { error -> Void in @@ -327,7 +327,7 @@ public class PCloudCloudProvider: CloudProvider { private func deleteFolder(for item: PCloudItem) -> Promise { CloudAccessDDLogDebug("PCloudCloudProvider: deleteFolder(for: \(item.identifier)) called") - return credential.client.deleteFolderRecursively(item.identifier).execute().then { metadata -> Void in + return client.deleteFolderRecursively(item.identifier).execute().then { metadata -> Void in CloudAccessDDLogDebug("PCloudCloudProvider: deleteFolder(for: \(item.identifier)) received metadata: \(metadata)") try self.identifierCache.invalidate(item) }.recover { error -> Void in @@ -345,7 +345,7 @@ public class PCloudCloudProvider: CloudProvider { private func moveFile(from sourceItem: PCloudItem, toParent targetParentItem: PCloudItem, targetCloudPath: CloudPath) -> Promise { CloudAccessDDLogDebug("PCloudCloudProvider: moveFile(from: \(sourceItem.identifier), toParent: \(targetParentItem.identifier), targetCloudPath: \(targetCloudPath.path)) called") - return credential.client.moveFile(sourceItem.identifier, toFolder: targetParentItem.identifier, newName: targetCloudPath.lastPathComponent).execute().then { metadata -> Void in + return client.moveFile(sourceItem.identifier, toFolder: targetParentItem.identifier, newName: targetCloudPath.lastPathComponent).execute().then { metadata -> Void in CloudAccessDDLogDebug("PCloudCloudProvider: moveFile(from: \(sourceItem.identifier), toParent: \(targetParentItem.identifier), targetCloudPath: \(targetCloudPath.path)) received metadata: \(metadata)") try self.identifierCache.invalidate(sourceItem) let targetItem = PCloudItem(cloudPath: targetCloudPath, metadata: metadata) @@ -367,7 +367,7 @@ public class PCloudCloudProvider: CloudProvider { private func moveFolder(from sourceItem: PCloudItem, toParent targetParentItem: PCloudItem, targetCloudPath: CloudPath) -> Promise { CloudAccessDDLogDebug("PCloudCloudProvider: moveFolder(from: \(sourceItem.identifier), toParent: \(targetParentItem.identifier), targetCloudPath: \(targetCloudPath.path)) called") - return credential.client.moveFolder(sourceItem.identifier, toFolder: targetParentItem.identifier, newName: targetCloudPath.lastPathComponent).execute().then { metadata -> Void in + return client.moveFolder(sourceItem.identifier, toFolder: targetParentItem.identifier, newName: targetCloudPath.lastPathComponent).execute().then { metadata -> Void in CloudAccessDDLogDebug("PCloudCloudProvider: moveFolder(from: \(sourceItem.identifier), toParent: \(targetParentItem.identifier), targetCloudPath: \(targetCloudPath.path)) received metadata: \(metadata)") try self.identifierCache.invalidate(sourceItem) let targetItem = PCloudItem(cloudPath: targetCloudPath, metadata: metadata) @@ -435,7 +435,7 @@ public class PCloudCloudProvider: CloudProvider { private func getPCloudItem(for name: String, withParentItem parentItem: PCloudItem) -> Promise { CloudAccessDDLogDebug("PCloudCloudProvider: getPCloudItem(for: \(name), withParentItem: \(parentItem.identifier)) called") - return credential.client.listFolder(parentItem.identifier, recursively: false).execute().then { metadata -> PCloudItem in + return client.listFolder(parentItem.identifier, recursively: false).execute().then { metadata -> PCloudItem in CloudAccessDDLogDebug("PCloudCloudProvider: getPCloudItem(for: \(name), withParentItem: \(parentItem.identifier)) received metadata: \(metadata)") guard let content = metadata.contents.first(where: { $0.fileMetadata?.name == name || $0.folderMetadata?.name == name }) else { throw CloudProviderError.itemNotFound diff --git a/Sources/CryptomatorCloudAccess/PCloud/PCloudCredential.swift b/Sources/CryptomatorCloudAccess/PCloud/PCloudCredential.swift index 0063a17..b87c0f3 100644 --- a/Sources/CryptomatorCloudAccess/PCloud/PCloudCredential.swift +++ b/Sources/CryptomatorCloudAccess/PCloud/PCloudCredential.swift @@ -11,12 +11,13 @@ import PCloudSDKSwift import Promises public class PCloudCredential { - public let client: PCloudClient public let user: OAuth.User public var userID: String { return String(user.id) } + private let client: PCloudClient + public init(user: OAuth.User) { self.user = user self.client = PCloud.createClient(with: user) @@ -28,3 +29,51 @@ public class PCloudCredential { } } } + +extension PCloud { + /// Creates a pCloud client with a background `URLSession`. Does not update the `sharedClient` property. You are responsible for storing it and keeping it alive. Use if + /// you want a more direct control over the lifetime of the `PCloudClient` object. Multiple clients can exist simultaneously. + /// + /// - parameter user: A `OAuth.User` value obtained from the keychain or the OAuth flow. + /// - parameter sharedContainerIdentifier: To create a URL session for use by an app extension, set this property to a valid identifier for a container shared between the app extension and its containing app + /// - returns: An instance of a `PCloudClient` ready to take requests. + public static func createBackgroundClient(with user: OAuth.User, sharedContainerIdentifier: String? = nil) -> PCloudClient { + let bundleId = Bundle.main.bundleIdentifier ?? "" + return createBackgroundClient(withAccessToken: user.token, apiHostName: user.httpAPIHostName, sessionIdentifier: "pCloud - \(user.id) - \(bundleId)", sharedContainerIdentifier: sharedContainerIdentifier) + } + + private static func createBackgroundClient(withAccessToken accessToken: String, apiHostName: String, sessionIdentifier: String, sharedContainerIdentifier: String?) -> PCloudClient { + let authenticator = OAuthAccessTokenBasedAuthenticator(accessToken: accessToken) + let eventHub = URLSessionEventHub() + var configuration = URLSessionConfiguration.background(withIdentifier: sessionIdentifier) + configuration.sharedContainerIdentifier = sharedContainerIdentifier + let session = URLSession(configuration: configuration, delegate: eventHub, delegateQueue: nil) + let foregroundSession = URLSession(configuration: .default, delegate: eventHub, delegateQueue: nil) + + // The event hub is expected to be kept in memory by the operation builder blocks. + + let callOperationBuilder = URLSessionBasedNetworkOperationUtilities.createCallOperationBuilder(with: .https, + session: foregroundSession, + delegate: eventHub) + + let uploadOperationBuilder = URLSessionBasedNetworkOperationUtilities.createUploadOperationBuilder(with: .https, + session: session, + delegate: eventHub) + + let downloadOperationBuilder = URLSessionBasedNetworkOperationUtilities.createDownloadOperationBuilder(with: session, delegate: eventHub) + + let callTaskBuilder = PCloudAPICallTaskBuilder(hostProvider: apiHostName, + authenticator: authenticator, + operationBuilder: callOperationBuilder) + + let uploadTaskBuilder = PCloudAPIUploadTaskBuilder(hostProvider: apiHostName, + authenticator: authenticator, + operationBuilder: uploadOperationBuilder) + + let downloadTaskBuilder = PCloudAPIDownloadTaskBuilder(hostProvider: apiHostName, + authenticator: authenticator, + operationBuilder: downloadOperationBuilder) + + return PCloudClient(callTaskBuilder: callTaskBuilder, uploadTaskBuilder: uploadTaskBuilder, downloadTaskBuilder: downloadTaskBuilder) + } +} diff --git a/Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat6/VaultFormat6PCloudIntegrationTests.swift b/Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat6/VaultFormat6PCloudIntegrationTests.swift index 927774d..d75e037 100644 --- a/Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat6/VaultFormat6PCloudIntegrationTests.swift +++ b/Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat6/VaultFormat6PCloudIntegrationTests.swift @@ -6,6 +6,7 @@ // Copyright © 2022 Skymatic GmbH. All rights reserved. // +import PCloudSDKSwift import XCTest #if canImport(CryptomatorCloudAccessCore) @testable import CryptomatorCloudAccessCore @@ -20,8 +21,9 @@ class VaultFormat6PCloudIntegrationTests: CloudAccessIntegrationTest { } private static let credential = PCloudCredentialMock() + private static let client = PCloud.createClient(with: credential.user) // swiftlint:disable:next force_try - private static let cloudProvider = try! PCloudCloudProvider(credential: credential) + private static let cloudProvider = try! PCloudCloudProvider(client: client) private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6") override class func setUp() { diff --git a/Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat7/VaultFormat7PCloudIntegrationTests.swift b/Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat7/VaultFormat7PCloudIntegrationTests.swift index 174fac6..78b3c6a 100644 --- a/Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat7/VaultFormat7PCloudIntegrationTests.swift +++ b/Tests/CryptomatorCloudAccessIntegrationTests/CryptoDecorator/VaultFormat7/VaultFormat7PCloudIntegrationTests.swift @@ -6,6 +6,7 @@ // Copyright © 2022 Skymatic GmbH. All rights reserved. // +import PCloudSDKSwift import XCTest #if canImport(CryptomatorCloudAccessCore) @testable import CryptomatorCloudAccessCore @@ -20,8 +21,10 @@ class VaultFormat7PCloudIntegrationTests: CloudAccessIntegrationTest { } private static let credential = PCloudCredentialMock() + private static let client = PCloud.createClient(with: credential.user) + // swiftlint:disable:next force_try - private static let cloudProvider = try! PCloudCloudProvider(credential: credential) + private static let cloudProvider = try! PCloudCloudProvider(client: client) private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat7") override class func setUp() { diff --git a/Tests/CryptomatorCloudAccessIntegrationTests/PCloud/PCloudCloudProviderIntegrationTests.swift b/Tests/CryptomatorCloudAccessIntegrationTests/PCloud/PCloudCloudProviderIntegrationTests.swift index 5658d8a..3fe7bb7 100644 --- a/Tests/CryptomatorCloudAccessIntegrationTests/PCloud/PCloudCloudProviderIntegrationTests.swift +++ b/Tests/CryptomatorCloudAccessIntegrationTests/PCloud/PCloudCloudProviderIntegrationTests.swift @@ -11,6 +11,7 @@ import CryptomatorCloudAccessCore #else import CryptomatorCloudAccess #endif +import PCloudSDKSwift import Promises import XCTest @@ -24,20 +25,23 @@ class PCloudCloudProviderIntegrationTests: CloudAccessIntegrationTestWithAuthent override class func setUp() { integrationTestParentCloudPath = CloudPath("/iOS-IntegrationTests-Plain") let credential = PCloudCredentialMock() + let client = PCloud.createClient(with: credential.user) // swiftlint:disable:next force_try - setUpProvider = try! PCloudCloudProvider(credential: credential) + setUpProvider = try! PCloudCloudProvider(client: client) super.setUp() } override func setUpWithError() throws { try super.setUpWithError() - provider = try PCloudCloudProvider(credential: credential) + let client = PCloud.createClient(with: credential.user) + provider = try PCloudCloudProvider(client: client) } override func deauthenticate() -> Promise { let invalidCredential = PCloudInvalidCredentialMock() + let client = PCloud.createClient(with: invalidCredential.user) // swiftlint:disable:next force_try - provider = try! PCloudCloudProvider(credential: invalidCredential) + provider = try! PCloudCloudProvider(client: client) return Promise(()) } From a1143a498794c6bb111a4478f74f397fee6cabb6 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 3 Jan 2023 16:05:54 +0100 Subject: [PATCH 02/15] Minor cleanup [ci skip] --- .../PCloud/PCloudCloudProvider.swift | 4 +- .../PCloud/PCloudCredential.swift | 42 +++++++------------ 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift b/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift index cd15742..21a5ce0 100644 --- a/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift @@ -11,12 +11,12 @@ import PCloudSDKSwift import Promises public class PCloudCloudProvider: CloudProvider { - private let identifierCache: PCloudIdentifierCache private let client: PCloudClient + private let identifierCache: PCloudIdentifierCache public init(client: PCloudClient) throws { - self.identifierCache = try PCloudIdentifierCache() self.client = client + self.identifierCache = try PCloudIdentifierCache() } public func fetchItemMetadata(at cloudPath: CloudPath) -> Promise { diff --git a/Sources/CryptomatorCloudAccess/PCloud/PCloudCredential.swift b/Sources/CryptomatorCloudAccess/PCloud/PCloudCredential.swift index b87c0f3..94d72f7 100644 --- a/Sources/CryptomatorCloudAccess/PCloud/PCloudCredential.swift +++ b/Sources/CryptomatorCloudAccess/PCloud/PCloudCredential.swift @@ -31,12 +31,15 @@ public class PCloudCredential { } extension PCloud { - /// Creates a pCloud client with a background `URLSession`. Does not update the `sharedClient` property. You are responsible for storing it and keeping it alive. Use if - /// you want a more direct control over the lifetime of the `PCloudClient` object. Multiple clients can exist simultaneously. - /// - /// - parameter user: A `OAuth.User` value obtained from the keychain or the OAuth flow. - /// - parameter sharedContainerIdentifier: To create a URL session for use by an app extension, set this property to a valid identifier for a container shared between the app extension and its containing app - /// - returns: An instance of a `PCloudClient` ready to take requests. + /** + Creates a pCloud client with a background `URLSession`. + + Does not update the `sharedClient` property. You are responsible for storing it and keeping it alive. Use if you want a more direct control over the lifetime of the `PCloudClient` object. Multiple clients can exist simultaneously. + + - Parameter user: A `OAuth.User` value obtained from the keychain or the OAuth flow. + - Parameter sharedContainerIdentifier: To create a URL session for use by an app extension, set this property to a valid identifier for a container shared between the app extension and its containing app. + - Returns: An instance of a `PCloudClient` ready to take requests. + */ public static func createBackgroundClient(with user: OAuth.User, sharedContainerIdentifier: String? = nil) -> PCloudClient { let bundleId = Bundle.main.bundleIdentifier ?? "" return createBackgroundClient(withAccessToken: user.token, apiHostName: user.httpAPIHostName, sessionIdentifier: "pCloud - \(user.id) - \(bundleId)", sharedContainerIdentifier: sharedContainerIdentifier) @@ -51,29 +54,12 @@ extension PCloud { let foregroundSession = URLSession(configuration: .default, delegate: eventHub, delegateQueue: nil) // The event hub is expected to be kept in memory by the operation builder blocks. - - let callOperationBuilder = URLSessionBasedNetworkOperationUtilities.createCallOperationBuilder(with: .https, - session: foregroundSession, - delegate: eventHub) - - let uploadOperationBuilder = URLSessionBasedNetworkOperationUtilities.createUploadOperationBuilder(with: .https, - session: session, - delegate: eventHub) - + let callOperationBuilder = URLSessionBasedNetworkOperationUtilities.createCallOperationBuilder(with: .https, session: foregroundSession, delegate: eventHub) + let uploadOperationBuilder = URLSessionBasedNetworkOperationUtilities.createUploadOperationBuilder(with: .https, session: session, delegate: eventHub) let downloadOperationBuilder = URLSessionBasedNetworkOperationUtilities.createDownloadOperationBuilder(with: session, delegate: eventHub) - - let callTaskBuilder = PCloudAPICallTaskBuilder(hostProvider: apiHostName, - authenticator: authenticator, - operationBuilder: callOperationBuilder) - - let uploadTaskBuilder = PCloudAPIUploadTaskBuilder(hostProvider: apiHostName, - authenticator: authenticator, - operationBuilder: uploadOperationBuilder) - - let downloadTaskBuilder = PCloudAPIDownloadTaskBuilder(hostProvider: apiHostName, - authenticator: authenticator, - operationBuilder: downloadOperationBuilder) - + let callTaskBuilder = PCloudAPICallTaskBuilder(hostProvider: apiHostName, authenticator: authenticator, operationBuilder: callOperationBuilder) + let uploadTaskBuilder = PCloudAPIUploadTaskBuilder(hostProvider: apiHostName, authenticator: authenticator, operationBuilder: uploadOperationBuilder) + let downloadTaskBuilder = PCloudAPIDownloadTaskBuilder(hostProvider: apiHostName, authenticator: authenticator, operationBuilder: downloadOperationBuilder) return PCloudClient(callTaskBuilder: callTaskBuilder, uploadTaskBuilder: uploadTaskBuilder, downloadTaskBuilder: downloadTaskBuilder) } } From cc3a6b1faf2878b9c1ebe65cd401b912ac012b20 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 3 Jan 2023 16:30:26 +0100 Subject: [PATCH 03/15] Update CloudProvider API --- .../API/CloudProvider+Convenience.swift | 4 ++++ Sources/CryptomatorCloudAccess/API/CloudProvider.swift | 3 ++- .../Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift | 2 +- .../VaultFormat6ShorteningProviderDecorator.swift | 2 +- .../Crypto/VaultFormat7/VaultFormat7ProviderDecorator.swift | 4 ++-- .../VaultFormat7ShorteningProviderDecorator.swift | 6 +++--- .../Dropbox/DropboxCloudProvider.swift | 2 +- .../GoogleDrive/GoogleDriveCloudProvider.swift | 2 +- .../LocalFileSystem/LocalFileSystemProvider.swift | 2 +- .../OneDrive/OneDriveCloudProvider.swift | 2 +- .../CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift | 2 +- Sources/CryptomatorCloudAccess/S3/S3CloudProvider.swift | 2 +- Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift | 2 +- 13 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift b/Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift index 2693e79..3d9e6fe 100644 --- a/Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift +++ b/Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift @@ -81,4 +81,8 @@ public extension CloudProvider { } } } + + func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise { + downloadFile(from: cloudPath, to: localURL, onTaskCreation: nil) + } } diff --git a/Sources/CryptomatorCloudAccess/API/CloudProvider.swift b/Sources/CryptomatorCloudAccess/API/CloudProvider.swift index 090c8ec..43a3866 100644 --- a/Sources/CryptomatorCloudAccess/API/CloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/API/CloudProvider.swift @@ -45,6 +45,7 @@ public protocol CloudProvider { - Parameter cloudPath: The cloud path of the file to download. - Parameter localURL: The local URL of the desired download location. + - Parameter onTaskCreation: Gets called once the provider created the underlying `URLSessionDownloadTask`. This can be called with `nil` if the provider does not provide a `URLSessionDownloadTask`. - Precondition: `localURL` must be a file URL. - Postcondition: The file is stored at `localURL`. - Returns: Empty promise. If the download fails, promise is rejected with: @@ -54,7 +55,7 @@ public protocol CloudProvider { - `CloudProviderError.unauthorized` if the request lacks valid authentication credentials. - `CloudProviderError.noInternetConnection` if there is no internet connection to handle the request. */ - func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise + func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise /** Uploads a file. diff --git a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift index 3d1ec67..a059750 100644 --- a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift +++ b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift @@ -73,7 +73,7 @@ class VaultFormat6ProviderDecorator: CloudProvider { } } - func downloadFile(from cleartextCloudPath: CloudPath, to cleartextLocalURL: URL) -> Promise { + func downloadFile(from cleartextCloudPath: CloudPath, to cleartextLocalURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { precondition(cleartextLocalURL.isFileURL) let overallProgress = Progress(totalUnitCount: 5) let ciphertextLocalURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) diff --git a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift index c26a9dd..e04abb2 100644 --- a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift +++ b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift @@ -59,7 +59,7 @@ class VaultFormat6ShorteningProviderDecorator: CloudProvider { } } - func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise { + func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { precondition(localURL.isFileURL) let shortened = shortenedNameCache.getShortenedPath(cloudPath) return delegate.downloadFile(from: shortened.cloudPath, to: localURL) diff --git a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ProviderDecorator.swift b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ProviderDecorator.swift index e508116..61667bc 100644 --- a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ProviderDecorator.swift +++ b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ProviderDecorator.swift @@ -70,13 +70,13 @@ class VaultFormat7ProviderDecorator: CloudProvider { } } - func downloadFile(from cleartextCloudPath: CloudPath, to cleartextLocalURL: URL) -> Promise { + func downloadFile(from cleartextCloudPath: CloudPath, to cleartextLocalURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { precondition(cleartextLocalURL.isFileURL) let overallProgress = Progress(totalUnitCount: 5) let ciphertextLocalURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) return getC9RPath(cleartextCloudPath).then { ciphertextCloudPath in overallProgress.becomeCurrent(withPendingUnitCount: 4) - let downloadFilePromise = self.delegate.downloadFile(from: ciphertextCloudPath, to: ciphertextLocalURL) + let downloadFilePromise = self.delegate.downloadFile(from: ciphertextCloudPath, to: ciphertextLocalURL, onTaskCreation: onTaskCreation) overallProgress.resignCurrent() return downloadFilePromise }.then { diff --git a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ShorteningProviderDecorator.swift b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ShorteningProviderDecorator.swift index 43eabe8..c50d8a1 100644 --- a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ShorteningProviderDecorator.swift +++ b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ShorteningProviderDecorator.swift @@ -75,14 +75,14 @@ class VaultFormat7ShorteningProviderDecorator: CloudProvider { } } - func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise { + func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { precondition(localURL.isFileURL) let shortened = shortenedNameCache.getShortenedPath(cloudPath) if shortened.pointsToC9S { let contentsFilePath = shortened.cloudPath.appendingContentsFileComponent() - return delegate.downloadFile(from: contentsFilePath, to: localURL) + return delegate.downloadFile(from: contentsFilePath, to: localURL, onTaskCreation: onTaskCreation) } else { - return delegate.downloadFile(from: shortened.cloudPath, to: localURL) + return delegate.downloadFile(from: shortened.cloudPath, to: localURL, onTaskCreation: onTaskCreation) } } diff --git a/Sources/CryptomatorCloudAccess/Dropbox/DropboxCloudProvider.swift b/Sources/CryptomatorCloudAccess/Dropbox/DropboxCloudProvider.swift index 9fdd2ae..9210369 100644 --- a/Sources/CryptomatorCloudAccess/Dropbox/DropboxCloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/Dropbox/DropboxCloudProvider.swift @@ -63,7 +63,7 @@ public class DropboxCloudProvider: CloudProvider { }, condition: shouldRetryForError) } - 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) guard let authorizedClient = credential.authorizedClient else { return Promise(CloudProviderError.unauthorized) diff --git a/Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift b/Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift index d322d91..1b7850a 100644 --- a/Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift @@ -99,7 +99,7 @@ public class GoogleDriveCloudProvider: 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) if FileManager.default.fileExists(atPath: localURL.path) { return Promise(CloudProviderError.itemAlreadyExists) diff --git a/Sources/CryptomatorCloudAccess/LocalFileSystem/LocalFileSystemProvider.swift b/Sources/CryptomatorCloudAccess/LocalFileSystem/LocalFileSystemProvider.swift index 85e2463..54f716c 100644 --- a/Sources/CryptomatorCloudAccess/LocalFileSystem/LocalFileSystemProvider.swift +++ b/Sources/CryptomatorCloudAccess/LocalFileSystem/LocalFileSystemProvider.swift @@ -178,7 +178,7 @@ public class LocalFileSystemProvider: CloudProvider { return Promise(CloudItemList(items: cacheResponse.elements, nextPageToken: cacheResponse.nextPageToken)) } - 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) let url = rootURL.appendingPathComponent(cloudPath) let shouldStopAccessing = url.startAccessingSecurityScopedResource() diff --git a/Sources/CryptomatorCloudAccess/OneDrive/OneDriveCloudProvider.swift b/Sources/CryptomatorCloudAccess/OneDrive/OneDriveCloudProvider.swift index 7a4a9df..8410b9f 100644 --- a/Sources/CryptomatorCloudAccess/OneDrive/OneDriveCloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/OneDrive/OneDriveCloudProvider.swift @@ -59,7 +59,7 @@ public class OneDriveCloudProvider: 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) if FileManager.default.fileExists(atPath: localURL.path) { return Promise(CloudProviderError.itemAlreadyExists) diff --git a/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift b/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift index d3a626b..7c83ac7 100644 --- a/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift @@ -34,7 +34,7 @@ public class PCloudCloudProvider: 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) if FileManager.default.fileExists(atPath: localURL.path) { return Promise(CloudProviderError.itemAlreadyExists) diff --git a/Sources/CryptomatorCloudAccess/S3/S3CloudProvider.swift b/Sources/CryptomatorCloudAccess/S3/S3CloudProvider.swift index c39b0c2..0bce8c4 100644 --- a/Sources/CryptomatorCloudAccess/S3/S3CloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/S3/S3CloudProvider.swift @@ -113,7 +113,7 @@ public class S3CloudProvider: CloudProvider { } } - public func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise { + public func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { CloudAccessDDLogDebug("S3CloudProvider: downloadFile(from: \(cloudPath.path), to: \(localURL)) called") if FileManager.default.fileExists(atPath: localURL.path) { return Promise(CloudProviderError.itemAlreadyExists) diff --git a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift index bb0738f..8b99398 100644 --- a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift +++ b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift @@ -107,7 +107,7 @@ public class WebDAVProvider: CloudProvider { return CloudItemList(items: response.elements, nextPageToken: response.nextPageToken) } - 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) guard let url = URL(cloudPath: cloudPath, relativeTo: client.baseURL) else { return Promise(WebDAVProviderError.resolvingURLFailed) From 3d6779736a9263dd9102bd333ae1513556b93ec0 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 3 Jan 2023 16:32:40 +0100 Subject: [PATCH 04/15] Return DownloadTask for WebDAVProvider --- Sources/CryptomatorCloudAccess/WebDAV/WebDAVClient.swift | 6 +++--- Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift | 2 +- Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVClient.swift b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVClient.swift index dc52e93..8441465 100644 --- a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVClient.swift +++ b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVClient.swift @@ -69,7 +69,7 @@ public class WebDAVClient { request.httpBody = """ \(propfindPropElementsAsXML(with: propertyNames)) """.data(using: .utf8) - return webDAVSession.performDownloadTask(with: request, to: localURL) + return webDAVSession.performDownloadTask(with: request, to: localURL, onTaskCreation: nil) } public func PROPFIND(url: URL, depth: PropfindDepth, propertyNames: [String]? = nil) -> Promise<(HTTPURLResponse, Data?)> { @@ -83,10 +83,10 @@ public class WebDAVClient { return webDAVSession.performDataTask(with: request) } - public func GET(from url: URL, to localURL: URL) -> Promise { + public func GET(from url: URL, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { var request = URLRequest(url: url) request.httpMethod = "GET" - return webDAVSession.performDownloadTask(with: request, to: localURL) + return webDAVSession.performDownloadTask(with: request, to: localURL, onTaskCreation: onTaskCreation) } public func PUT(url: URL, fileURL: URL) -> Promise<(HTTPURLResponse, Data?)> { diff --git a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift index 8b99398..d5cc114 100644 --- a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift +++ b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift @@ -120,7 +120,7 @@ public class WebDAVProvider: CloudProvider { throw CloudProviderError.itemTypeMismatch } progress.becomeCurrent(withPendingUnitCount: 1) - let getPromise = self.client.GET(from: url, to: localURL) + let getPromise = self.client.GET(from: url, to: localURL, onTaskCreation: onTaskCreation) progress.resignCurrent() return getPromise }.then { _ -> Void in diff --git a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift index 2116e69..4b61903 100644 --- a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift +++ b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift @@ -217,10 +217,11 @@ class WebDAVSession { } } - func performDownloadTask(with request: URLRequest, to localURL: URL) -> Promise { + func performDownloadTask(with request: URLRequest, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { HTTPDebugLogger.logRequest(request) let progress = Progress(totalUnitCount: 1) let task = urlSession.downloadTask(with: request) + onTaskCreation?(task) progress.addChild(task.progress, withPendingUnitCount: 1) let pendingPromise = Promise.pending() let webDAVDownloadTask = WebDAVDownloadTask(promise: pendingPromise, localURL: localURL) From a2d59082043e1c6cdbb34b0542f9bbcff9e072d3 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 3 Jan 2023 16:32:59 +0100 Subject: [PATCH 05/15] Return DownloadTask for GoogleDriveCloudProvider --- .../GoogleDriveCloudProvider.swift | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift b/Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift index 1b7850a..52c5745 100644 --- a/Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift @@ -107,7 +107,7 @@ public class GoogleDriveCloudProvider: CloudProvider { let progress = Progress(totalUnitCount: 1) return resolvePath(forItemAt: cloudPath).then { item -> Promise in progress.becomeCurrent(withPendingUnitCount: 1) - let downloadPromise = self.downloadFile(for: item, to: localURL) + let downloadPromise = self.downloadFile(for: item, to: localURL, onTaskCreation: onTaskCreation) progress.resignCurrent() return downloadPromise } @@ -233,7 +233,7 @@ public class GoogleDriveCloudProvider: CloudProvider { } } - private func downloadFile(for item: GoogleDriveItem, to localURL: URL) -> Promise { + private func downloadFile(for item: GoogleDriveItem, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { CloudAccessDDLogDebug("GoogleDriveCloudProvider: downloadFile(for: \(item.identifier), to: \(localURL)) called") guard item.itemType == .file || (item.itemType == .symlink && item.shortcut?.targetItemType == .file) else { return Promise(CloudProviderError.itemTypeMismatch) @@ -247,7 +247,13 @@ public class GoogleDriveCloudProvider: CloudProvider { progress.totalUnitCount = totalBytesExpectedToWrite // Unnecessary to set several times progress.completedUnitCount = totalBytesWritten } - return executeFetcher(fetcher) + return executeFetcher(fetcher) { sessionTask in + guard let downloadTask = sessionTask as? URLSessionDownloadTask else { + CloudAccessDDLogDebug("GoogleDriveCloudProvider: executeFetcher returned an unexpected URLSessionTask") + return + } + onTaskCreation?(downloadTask) + } } private func uploadFile(for parentItem: GoogleDriveItem, from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise { @@ -552,7 +558,8 @@ public class GoogleDriveCloudProvider: CloudProvider { } } - private func executeFetcher(_ fetcher: GTMSessionFetcher) -> Promise { + private func executeFetcher(_ fetcher: GTMSessionFetcher, onTaskCreation: @escaping (URLSessionTask?) -> Void) -> Promise { + var kvoToken: NSKeyValueObservation? return Promise { fulfill, reject in self.runningFetchers.append(fetcher) fetcher.beginFetch { _, error in @@ -570,6 +577,14 @@ public class GoogleDriveCloudProvider: CloudProvider { } fulfill(()) } + kvoToken = fetcher.observe(\.sessionTask, options: [.new], changeHandler: { _, change in + guard let newValue = change.newValue, let sessionTask = newValue else { + return + } + onTaskCreation(sessionTask) + }) + }.always { + kvoToken?.invalidate() } } From d08826044ef8e359e445a2758b690c4e9a12876e Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 3 Jan 2023 16:49:35 +0100 Subject: [PATCH 06/15] Update CloudProviderMock --- .../CryptomatorCloudAccessTests/Crypto/CloudProviderMock.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CryptomatorCloudAccessTests/Crypto/CloudProviderMock.swift b/Tests/CryptomatorCloudAccessTests/Crypto/CloudProviderMock.swift index 26c0125..9fc44ee 100644 --- a/Tests/CryptomatorCloudAccessTests/Crypto/CloudProviderMock.swift +++ b/Tests/CryptomatorCloudAccessTests/Crypto/CloudProviderMock.swift @@ -51,7 +51,7 @@ public class CloudProviderMock: 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) if let data = files[cloudPath.path] { do { From 0c69347b2e1810c8a9b1047ecc337c97ead30184 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 3 Jan 2023 17:04:54 +0100 Subject: [PATCH 07/15] Pass onTaskCreation closure to delegate for vault format 6 decorators --- .../Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift | 2 +- .../VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift index a059750..2dc2328 100644 --- a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift +++ b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift @@ -80,7 +80,7 @@ class VaultFormat6ProviderDecorator: CloudProvider { return getParentDirId(cleartextCloudPath).then { parentDirId in let fileCiphertextPath = try self.getFileCiphertextPath(cleartextCloudPath, parentDirId: parentDirId) overallProgress.becomeCurrent(withPendingUnitCount: 4) - let downloadFilePromise = self.delegate.downloadFile(from: fileCiphertextPath, to: ciphertextLocalURL).recover { error -> Promise in + let downloadFilePromise = self.delegate.downloadFile(from: fileCiphertextPath, to: ciphertextLocalURL, onTaskCreation: onTaskCreation).recover { error -> Promise in guard case CloudProviderError.itemNotFound = error else { return Promise(error) } diff --git a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift index e04abb2..fefb252 100644 --- a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift +++ b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift @@ -62,7 +62,7 @@ class VaultFormat6ShorteningProviderDecorator: CloudProvider { func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { precondition(localURL.isFileURL) let shortened = shortenedNameCache.getShortenedPath(cloudPath) - return delegate.downloadFile(from: shortened.cloudPath, to: localURL) + return delegate.downloadFile(from: shortened.cloudPath, to: localURL, onTaskCreation: onTaskCreation) } func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise { From b7a231a4b20ed5996c02b7d56c1434468aaab758 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 3 Jan 2023 17:07:21 +0100 Subject: [PATCH 08/15] Update CloudProviderConvenienceMock --- .../API/CloudProvider+ConvenienceTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CryptomatorCloudAccessTests/API/CloudProvider+ConvenienceTests.swift b/Tests/CryptomatorCloudAccessTests/API/CloudProvider+ConvenienceTests.swift index 3134c13..ade16ec 100644 --- a/Tests/CryptomatorCloudAccessTests/API/CloudProvider+ConvenienceTests.swift +++ b/Tests/CryptomatorCloudAccessTests/API/CloudProvider+ConvenienceTests.swift @@ -204,7 +204,7 @@ private class ConvenienceCloudProviderMock: CloudProvider { } } - 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) } From 32c8987f83203f582b8d4372c9c98d4a5025b545 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 3 Jan 2023 17:07:37 +0100 Subject: [PATCH 09/15] Update WebDAVClientMock --- .../CryptomatorCloudAccessTests/WebDAV/WebDAVClientMock.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/CryptomatorCloudAccessTests/WebDAV/WebDAVClientMock.swift b/Tests/CryptomatorCloudAccessTests/WebDAV/WebDAVClientMock.swift index fd4e360..918ed69 100644 --- a/Tests/CryptomatorCloudAccessTests/WebDAV/WebDAVClientMock.swift +++ b/Tests/CryptomatorCloudAccessTests/WebDAV/WebDAVClientMock.swift @@ -53,9 +53,9 @@ class WebDAVClientMock: WebDAVClient { return super.PROPFIND(url: url, depth: depth, to: localURL, propertyNames: propertyNames) } - override func GET(from url: URL, to localURL: URL) -> Promise { + override func GET(from url: URL, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise { getRequests.append(url.relativePath) - return super.GET(from: url, to: localURL) + return super.GET(from: url, to: localURL, onTaskCreation: onTaskCreation) } override func PUT(url: URL, fileURL: URL) -> Promise<(HTTPURLResponse, Data?)> { From dade4d1a778a30bc18a2744a5b819cda4b65e312 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 3 Jan 2023 17:59:23 +0100 Subject: [PATCH 10/15] Added docs [ci skip] --- .../API/CloudProvider+Convenience.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift b/Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift index 3d9e6fe..67ec4b5 100644 --- a/Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift +++ b/Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift @@ -24,6 +24,13 @@ public extension CloudProvider { } } + /** + Convenience wrapper for `downloadFile()` that ignores the underlying task. + */ + func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise { + downloadFile(from: cloudPath, to: localURL, onTaskCreation: nil) + } + /** Convenience wrapper for `createFolder()` that also satisfies if the item is present. */ @@ -81,8 +88,4 @@ public extension CloudProvider { } } } - - func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise { - downloadFile(from: cloudPath, to: localURL, onTaskCreation: nil) - } } From 6744473e08ac705eed51573ebe727d03aa764cbc Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Wed, 4 Jan 2023 21:51:28 +0100 Subject: [PATCH 11/15] Update CloudProvider API to support upload progress reporting via URLSessionUploadTask --- .../API/CloudProvider+Convenience.swift | 7 +++++++ Sources/CryptomatorCloudAccess/API/CloudProvider.swift | 3 ++- .../VaultFormat6/VaultFormat6ProviderDecorator.swift | 4 ++-- .../VaultFormat6ShorteningProviderDecorator.swift | 6 +++--- .../VaultFormat7/VaultFormat7ProviderDecorator.swift | 4 ++-- .../VaultFormat7ShorteningProviderDecorator.swift | 6 +++--- .../Dropbox/DropboxCloudProvider.swift | 2 +- .../GoogleDrive/GoogleDriveCloudProvider.swift | 2 +- .../LocalFileSystem/LocalFileSystemProvider.swift | 2 +- .../OneDrive/OneDriveCloudProvider.swift | 2 +- .../PCloud/PCloudCloudProvider.swift | 2 +- Sources/CryptomatorCloudAccess/S3/S3CloudProvider.swift | 2 +- Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift | 2 +- .../API/CloudProvider+ConvenienceTests.swift | 2 +- .../Crypto/CloudProviderMock.swift | 2 +- 15 files changed, 28 insertions(+), 20 deletions(-) diff --git a/Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift b/Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift index 67ec4b5..ff37f22 100644 --- a/Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift +++ b/Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift @@ -31,6 +31,13 @@ public extension CloudProvider { downloadFile(from: cloudPath, to: localURL, onTaskCreation: nil) } + /** + Convenience wrapper for `uploadFile()` that ignores the underlying task. + */ + func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise { + uploadFile(from: localURL, to: cloudPath, replaceExisting: replaceExisting, onTaskCreation: nil) + } + /** Convenience wrapper for `createFolder()` that also satisfies if the item is present. */ diff --git a/Sources/CryptomatorCloudAccess/API/CloudProvider.swift b/Sources/CryptomatorCloudAccess/API/CloudProvider.swift index 43a3866..499b167 100644 --- a/Sources/CryptomatorCloudAccess/API/CloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/API/CloudProvider.swift @@ -65,6 +65,7 @@ public protocol CloudProvider { - Parameter localURL: The local URL of the file to upload. - Parameter cloudPath: The cloud path of the desired upload location. - Parameter replaceExisting: If true, overwrite the existing file at `cloudPath`. + - Parameter onTaskCreation: Gets called once the provider created the underlying `URLSessionUploadTask`. This can be called with `nil` if the provider does not provide a `URLSessionUploadTask`. - Precondition: `localURL` must be a file URL. - Postcondition: The file is stored at `cloudPath`. - Postcondition: The `size` property of the returned metadata is set to the size of the uploaded file in the cloud and must not be `nil`. @@ -77,7 +78,7 @@ public protocol CloudProvider { - `CloudProviderError.unauthorized` if the request lacks valid authentication credentials. - `CloudProviderError.noInternetConnection` if there is no internet connection to handle the request. */ - 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 /** Creates a folder. diff --git a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift index 2dc2328..f957a7f 100644 --- a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift +++ b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ProviderDecorator.swift @@ -107,7 +107,7 @@ class VaultFormat6ProviderDecorator: CloudProvider { } } - func uploadFile(from cleartextLocalURL: URL, to cleartextCloudPath: CloudPath, replaceExisting: Bool) -> Promise { + func uploadFile(from cleartextLocalURL: URL, to cleartextCloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise { precondition(cleartextLocalURL.isFileURL) let overallProgress = Progress(totalUnitCount: 5) let ciphertextLocalURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) @@ -129,7 +129,7 @@ class VaultFormat6ProviderDecorator: CloudProvider { try self.cryptor.encryptContent(from: cleartextLocalURL, to: ciphertextLocalURL) overallProgress.resignCurrent() overallProgress.becomeCurrent(withPendingUnitCount: 4) - let uploadFilePromise = self.delegate.uploadFile(from: ciphertextLocalURL, to: fileCiphertextPath, replaceExisting: replaceExisting) + let uploadFilePromise = self.delegate.uploadFile(from: ciphertextLocalURL, to: fileCiphertextPath, replaceExisting: replaceExisting, onTaskCreation: onTaskCreation) overallProgress.resignCurrent() return uploadFilePromise }.recover { error -> CloudItemMetadata in diff --git a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift index fefb252..bfc92db 100644 --- a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift +++ b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShorteningProviderDecorator.swift @@ -65,17 +65,17 @@ class VaultFormat6ShorteningProviderDecorator: CloudProvider { return delegate.downloadFile(from: shortened.cloudPath, to: localURL, onTaskCreation: onTaskCreation) } - 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 { precondition(localURL.isFileURL) let shortened = shortenedNameCache.getShortenedPath(cloudPath) if shortened.pointsToLNG { return uploadNameFile(shortened.cloudPath.lastPathComponent, originalName: cloudPath.lastPathComponent).then { - return self.delegate.uploadFile(from: localURL, to: shortened.cloudPath, replaceExisting: replaceExisting) + return self.delegate.uploadFile(from: localURL, to: shortened.cloudPath, replaceExisting: replaceExisting, onTaskCreation: onTaskCreation) }.then { shortenedMetadata in return self.getOriginalMetadata(shortenedMetadata) } } else { - return delegate.uploadFile(from: localURL, to: shortened.cloudPath, replaceExisting: replaceExisting) + return delegate.uploadFile(from: localURL, to: shortened.cloudPath, replaceExisting: replaceExisting, onTaskCreation: onTaskCreation) } } diff --git a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ProviderDecorator.swift b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ProviderDecorator.swift index 61667bc..62a16aa 100644 --- a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ProviderDecorator.swift +++ b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ProviderDecorator.swift @@ -91,7 +91,7 @@ class VaultFormat7ProviderDecorator: CloudProvider { } } - func uploadFile(from cleartextLocalURL: URL, to cleartextCloudPath: CloudPath, replaceExisting: Bool) -> Promise { + func uploadFile(from cleartextLocalURL: URL, to cleartextCloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise { precondition(cleartextLocalURL.isFileURL) let overallProgress = Progress(totalUnitCount: 5) let ciphertextLocalURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false) @@ -103,7 +103,7 @@ class VaultFormat7ProviderDecorator: CloudProvider { try self.cryptor.encryptContent(from: cleartextLocalURL, to: ciphertextLocalURL) overallProgress.resignCurrent() overallProgress.becomeCurrent(withPendingUnitCount: 4) - let uploadFilePromise = self.delegate.uploadFile(from: ciphertextLocalURL, to: ciphertextCloudPath, replaceExisting: replaceExisting) + let uploadFilePromise = self.delegate.uploadFile(from: ciphertextLocalURL, to: ciphertextCloudPath, replaceExisting: replaceExisting, onTaskCreation: onTaskCreation) overallProgress.resignCurrent() return uploadFilePromise }.recover { error -> CloudItemMetadata in diff --git a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ShorteningProviderDecorator.swift b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ShorteningProviderDecorator.swift index c50d8a1..3f174f9 100644 --- a/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ShorteningProviderDecorator.swift +++ b/Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ShorteningProviderDecorator.swift @@ -86,20 +86,20 @@ class VaultFormat7ShorteningProviderDecorator: CloudProvider { } } - 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 { precondition(localURL.isFileURL) let shortened = shortenedNameCache.getShortenedPath(cloudPath) if shortened.pointsToC9S, let c9sDir = shortened.c9sDir { return createC9SFolderAndUploadNameFile(c9sDir).then { () -> Promise in let contentsFilePath = shortened.cloudPath.appendingContentsFileComponent() - return self.delegate.uploadFile(from: localURL, to: contentsFilePath, replaceExisting: replaceExisting) + return self.delegate.uploadFile(from: localURL, to: contentsFilePath, replaceExisting: replaceExisting, onTaskCreation: onTaskCreation) }.then { _ in return self.delegate.fetchItemMetadata(at: shortened.cloudPath) }.then { shortenedMetadata in return self.getOriginalMetadata(shortenedMetadata) } } else { - return delegate.uploadFile(from: localURL, to: shortened.cloudPath, replaceExisting: replaceExisting) + return delegate.uploadFile(from: localURL, to: shortened.cloudPath, replaceExisting: replaceExisting, onTaskCreation: onTaskCreation) } } diff --git a/Sources/CryptomatorCloudAccess/Dropbox/DropboxCloudProvider.swift b/Sources/CryptomatorCloudAccess/Dropbox/DropboxCloudProvider.swift index 9210369..7198534 100644 --- a/Sources/CryptomatorCloudAccess/Dropbox/DropboxCloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/Dropbox/DropboxCloudProvider.swift @@ -80,7 +80,7 @@ public class DropboxCloudProvider: CloudProvider { /** - Warning: This function is not atomic, because the existence of the parent folder is checked first, otherwise Dropbox creates the missing folders automatically. */ - 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) guard let authorizedClient = credential.authorizedClient else { return Promise(CloudProviderError.unauthorized) diff --git a/Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift b/Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift index 52c5745..12d8e7c 100644 --- a/Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift @@ -113,7 +113,7 @@ public class GoogleDriveCloudProvider: 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) var isDirectory: ObjCBool = false let fileExists = FileManager.default.fileExists(atPath: localURL.path, isDirectory: &isDirectory) diff --git a/Sources/CryptomatorCloudAccess/LocalFileSystem/LocalFileSystemProvider.swift b/Sources/CryptomatorCloudAccess/LocalFileSystem/LocalFileSystemProvider.swift index 54f716c..05fe56d 100644 --- a/Sources/CryptomatorCloudAccess/LocalFileSystem/LocalFileSystemProvider.swift +++ b/Sources/CryptomatorCloudAccess/LocalFileSystem/LocalFileSystemProvider.swift @@ -216,7 +216,7 @@ public class LocalFileSystemProvider: CloudProvider { return promise } - 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) let url = rootURL.appendingPathComponent(cloudPath) let shouldStopAccessing = url.startAccessingSecurityScopedResource() diff --git a/Sources/CryptomatorCloudAccess/OneDrive/OneDriveCloudProvider.swift b/Sources/CryptomatorCloudAccess/OneDrive/OneDriveCloudProvider.swift index 8410b9f..2398948 100644 --- a/Sources/CryptomatorCloudAccess/OneDrive/OneDriveCloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/OneDrive/OneDriveCloudProvider.swift @@ -69,7 +69,7 @@ public class OneDriveCloudProvider: 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) let attributes: [FileAttributeKey: Any] do { diff --git a/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift b/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift index 5034cf4..51eb499 100644 --- a/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/PCloud/PCloudCloudProvider.swift @@ -44,7 +44,7 @@ public class PCloudCloudProvider: 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) var isDirectory: ObjCBool = false let fileExists = FileManager.default.fileExists(atPath: localURL.path, isDirectory: &isDirectory) diff --git a/Sources/CryptomatorCloudAccess/S3/S3CloudProvider.swift b/Sources/CryptomatorCloudAccess/S3/S3CloudProvider.swift index 0bce8c4..db637ca 100644 --- a/Sources/CryptomatorCloudAccess/S3/S3CloudProvider.swift +++ b/Sources/CryptomatorCloudAccess/S3/S3CloudProvider.swift @@ -129,7 +129,7 @@ public class S3CloudProvider: 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 { CloudAccessDDLogDebug("S3CloudProvider: uploadFile(from: \(localURL), to: \(cloudPath.path), replaceExisting: \(replaceExisting)) called") var isDirectory: ObjCBool = false let fileExists = FileManager.default.fileExists(atPath: localURL.path, isDirectory: &isDirectory) diff --git a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift index d5cc114..29a604a 100644 --- a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift +++ b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift @@ -142,7 +142,7 @@ public class WebDAVProvider: CloudProvider { } // swiftlint:disable:next cyclomatic_complexity - 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) guard let url = URL(cloudPath: cloudPath, relativeTo: client.baseURL) else { return Promise(WebDAVProviderError.resolvingURLFailed) diff --git a/Tests/CryptomatorCloudAccessTests/API/CloudProvider+ConvenienceTests.swift b/Tests/CryptomatorCloudAccessTests/API/CloudProvider+ConvenienceTests.swift index ade16ec..8f1d660 100644 --- a/Tests/CryptomatorCloudAccessTests/API/CloudProvider+ConvenienceTests.swift +++ b/Tests/CryptomatorCloudAccessTests/API/CloudProvider+ConvenienceTests.swift @@ -208,7 +208,7 @@ private class ConvenienceCloudProviderMock: CloudProvider { 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/Tests/CryptomatorCloudAccessTests/Crypto/CloudProviderMock.swift b/Tests/CryptomatorCloudAccessTests/Crypto/CloudProviderMock.swift index 9fc44ee..031a439 100644 --- a/Tests/CryptomatorCloudAccessTests/Crypto/CloudProviderMock.swift +++ b/Tests/CryptomatorCloudAccessTests/Crypto/CloudProviderMock.swift @@ -65,7 +65,7 @@ public class CloudProviderMock: 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) do { let data = try Data(contentsOf: localURL) From d9c191f24bcdce037384e99ef93ad11f660d2542 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Wed, 4 Jan 2023 21:51:52 +0100 Subject: [PATCH 12/15] Return UploadTask for WebDAVProvider --- Sources/CryptomatorCloudAccess/WebDAV/WebDAVClient.swift | 4 ++-- Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift | 2 +- Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift | 3 ++- .../CryptomatorCloudAccessTests/WebDAV/WebDAVClientMock.swift | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVClient.swift b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVClient.swift index 8441465..0c0ad77 100644 --- a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVClient.swift +++ b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVClient.swift @@ -89,10 +89,10 @@ public class WebDAVClient { return webDAVSession.performDownloadTask(with: request, to: localURL, onTaskCreation: onTaskCreation) } - public func PUT(url: URL, fileURL: URL) -> Promise<(HTTPURLResponse, Data?)> { + public func PUT(url: URL, fileURL: URL, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise<(HTTPURLResponse, Data?)> { var request = URLRequest(url: url) request.httpMethod = "PUT" - return webDAVSession.performUploadTask(with: request, fromFile: fileURL) + return webDAVSession.performUploadTask(with: request, fromFile: fileURL, onTaskCreation: onTaskCreation) } public func MKCOL(url: URL) -> Promise<(HTTPURLResponse, Data?)> { diff --git a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift index 29a604a..b78a75c 100644 --- a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift +++ b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVProvider.swift @@ -163,7 +163,7 @@ public class WebDAVProvider: CloudProvider { } }.then { _ -> Promise<(HTTPURLResponse, Data?)> in progress.becomeCurrent(withPendingUnitCount: 1) - let putPromise = self.client.PUT(url: url, fileURL: localURL) + let putPromise = self.client.PUT(url: url, fileURL: localURL, onTaskCreation: onTaskCreation) progress.resignCurrent() return putPromise }.recover { error -> Promise<(HTTPURLResponse, Data?)> in diff --git a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift index 4b61903..ca937a5 100644 --- a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift +++ b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift @@ -233,7 +233,7 @@ class WebDAVSession { } } - func performUploadTask(with request: URLRequest, fromFile fileURL: URL) -> Promise<(HTTPURLResponse, Data?)> { + func performUploadTask(with request: URLRequest, fromFile fileURL: URL, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise<(HTTPURLResponse, Data?)> { HTTPDebugLogger.logRequest(request) let progress = Progress(totalUnitCount: 1) let task = urlSession.uploadTask(with: request, fromFile: fileURL) @@ -241,6 +241,7 @@ class WebDAVSession { let pendingPromise = Promise<(HTTPURLResponse, Data?)>.pending() let webDAVDataTask = WebDAVDataTask(promise: pendingPromise) delegate?.addRunningDataTask(key: task, value: webDAVDataTask) + onTaskCreation?(task) task.resume() return pendingPromise.then { response, data -> Promise<(HTTPURLResponse, Data?)> in HTTPDebugLogger.logResponse(response, with: data, or: nil) diff --git a/Tests/CryptomatorCloudAccessTests/WebDAV/WebDAVClientMock.swift b/Tests/CryptomatorCloudAccessTests/WebDAV/WebDAVClientMock.swift index 918ed69..eed2f44 100644 --- a/Tests/CryptomatorCloudAccessTests/WebDAV/WebDAVClientMock.swift +++ b/Tests/CryptomatorCloudAccessTests/WebDAV/WebDAVClientMock.swift @@ -58,9 +58,9 @@ class WebDAVClientMock: WebDAVClient { return super.GET(from: url, to: localURL, onTaskCreation: onTaskCreation) } - override func PUT(url: URL, fileURL: URL) -> Promise<(HTTPURLResponse, Data?)> { + override func PUT(url: URL, fileURL: URL, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise<(HTTPURLResponse, Data?)> { putRequests.append(url.relativePath) - return super.PUT(url: url, fileURL: fileURL) + return super.PUT(url: url, fileURL: fileURL, onTaskCreation: onTaskCreation) } override func MKCOL(url: URL) -> Promise<(HTTPURLResponse, Data?)> { From 2a3662c0ca6341215fd5c1e9c045e7fd2352dd14 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Thu, 5 Jan 2023 18:32:17 +0100 Subject: [PATCH 13/15] Call onTaskCreation immediately after creating the upload task --- Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift index ca937a5..17db9b1 100644 --- a/Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift +++ b/Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift @@ -237,11 +237,11 @@ class WebDAVSession { HTTPDebugLogger.logRequest(request) let progress = Progress(totalUnitCount: 1) let task = urlSession.uploadTask(with: request, fromFile: fileURL) + onTaskCreation?(task) progress.addChild(task.progress, withPendingUnitCount: 1) let pendingPromise = Promise<(HTTPURLResponse, Data?)>.pending() let webDAVDataTask = WebDAVDataTask(promise: pendingPromise) delegate?.addRunningDataTask(key: task, value: webDAVDataTask) - onTaskCreation?(task) task.resume() return pendingPromise.then { response, data -> Promise<(HTTPURLResponse, Data?)> in HTTPDebugLogger.logResponse(response, with: data, or: nil) From 92dc95bfc8af91b86c974f4675c8504690191e0f Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Fri, 6 Jan 2023 18:13:09 +0100 Subject: [PATCH 14/15] Updated README according to changed API [ci skip] --- README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4f5243e..53a5c46 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ The core package contains several protocols, structs, and enums that build the f ```swift func fetchItemMetadata(at cloudPath: CloudPath) -> Promise func fetchItemList(forFolderAt cloudPath: CloudPath, withPageToken pageToken: String?) -> Promise -func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise -func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise +func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise +func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise func createFolder(at cloudPath: CloudPath) -> Promise func deleteFile(at cloudPath: CloudPath) -> Promise func deleteFolder(at cloudPath: CloudPath) -> Promise @@ -197,10 +197,21 @@ PCloudAuthenticator.authenticate(from: viewController).then { credential in } ``` -You can then use the credential to create a pCloud provider: +You can then use the credential to create a pCloud provider. + +Create a pCloud provider with a pCloud client: + +```swift +let client = PCloud.createClient(with: credential.user) +let provider = PCloudCloudProvider(client: client) +``` + +Create a pCloud provider with a pCloud client using a background URLSession: ```swift -let provider = PCloudCloudProvider(credential: credential) +let sharedContainerIdentifier = ... // optional: only needed if you want to create a `PCloudCloudProvider` in an app extension +let client = PCloud.createBackgroundClient(with: credential.user, sharedContainerIdentifier: sharedContainerIdentifier) +let provider = PCloudCloudProvider(client: client) ``` ### S3 From 0b3342f8669936bfc8c3956bbdd1af779a841747 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Fri, 6 Jan 2023 18:15:34 +0100 Subject: [PATCH 15/15] Updated README [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53a5c46..c68d5d8 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The API is implemented once for each cloud. It also forms the foundation for dec You can use [Swift Package Manager](https://swift.org/package-manager/ "Swift Package Manager"). ```swift -.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")) ``` ## Usage