Skip to content

Commit

Permalink
Merge pull request #33 from cryptomator/feature/unique-identifier-bg-…
Browse files Browse the repository at this point in the history
…session

Allow the injection of background URLSession identifier
  • Loading branch information
tobihagemann committed Apr 16, 2024
2 parents 0fd3321 + edb2c5f commit 243d39e
Show file tree
Hide file tree
Showing 31 changed files with 787 additions and 1,758 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ jobs:
run: |
./Scripts/process.sh
exit $?
- name: Select Xcode 15.2
run: sudo xcode-select -s /Applications/Xcode_15.2.app
- name: Select Xcode 15.3
run: sudo xcode-select -s /Applications/Xcode_15.3.app
- name: Build
run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild clean build-for-testing -scheme 'CryptomatorCloudAccess' -destination "name=$DEVICE" -derivedDataPath $DERIVED_DATA_PATH -enableCodeCoverage YES | xcpretty
- name: Test
Expand Down
8 changes: 8 additions & 0 deletions CryptomatorCloudAccess.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
4A7C214D245305BB00DE81E6 /* CloudItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7C214C245305BB00DE81E6 /* CloudItemType.swift */; };
4A8B872F287D7E77002D676E /* CocoaLumberjackSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 4A8B872E287D7E77002D676E /* CocoaLumberjackSwift */; };
4A8B8734287D8036002D676E /* CloudAccessDDLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A8B8733287D8036002D676E /* CloudAccessDDLog.swift */; };
4ABE9AA32BCAC8FE00675D74 /* Promise+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABE9AA22BCAC8FE00675D74 /* Promise+Async.swift */; };
4ABE9AA62BCACD1300675D74 /* XCTAssertThrowsError+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABE9AA42BCACB9100675D74 /* XCTAssertThrowsError+Async.swift */; };
4AC75F9028607B20002731FE /* CustomAWSEndpointRegionNameStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC75F8F28607B20002731FE /* CustomAWSEndpointRegionNameStorage.swift */; };
4AC75F9A2861A425002731FE /* VaultFormat7S3IntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC75F992861A425002731FE /* VaultFormat7S3IntegrationTests.swift */; };
4AC75F9C2861A6DE002731FE /* VaultFormat6S3IntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC75F9B2861A6DE002731FE /* VaultFormat6S3IntegrationTests.swift */; };
Expand Down Expand Up @@ -256,6 +258,8 @@
4A77390C286DB5A00006B3C3 /* AWSS3TransferUtility+ForegroundSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AWSS3TransferUtility+ForegroundSession.swift"; sourceTree = "<group>"; };
4A7C214C245305BB00DE81E6 /* CloudItemType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudItemType.swift; sourceTree = "<group>"; };
4A8B8733287D8036002D676E /* CloudAccessDDLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudAccessDDLog.swift; sourceTree = "<group>"; };
4ABE9AA22BCAC8FE00675D74 /* Promise+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Async.swift"; sourceTree = "<group>"; };
4ABE9AA42BCACB9100675D74 /* XCTAssertThrowsError+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTAssertThrowsError+Async.swift"; sourceTree = "<group>"; };
4AC75F8F28607B20002731FE /* CustomAWSEndpointRegionNameStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAWSEndpointRegionNameStorage.swift; sourceTree = "<group>"; };
4AC75F992861A425002731FE /* VaultFormat7S3IntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultFormat7S3IntegrationTests.swift; sourceTree = "<group>"; };
4AC75F9B2861A6DE002731FE /* VaultFormat6S3IntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultFormat6S3IntegrationTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -465,6 +469,7 @@
7402B48227D2625E00BE9F8B /* PCloud */,
4A2745FA2847887F00E70D5F /* S3 */,
748420C124B8A1F800D84E58 /* WebDAV */,
4ABE9AA42BCACB9100675D74 /* XCTAssertThrowsError+Async.swift */,
);
path = CryptomatorCloudAccessTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -566,6 +571,7 @@
isa = PBXGroup;
children = (
4A765CA22878596000794440 /* DirectoryContentCache.swift */,
4ABE9AA22BCAC8FE00675D74 /* Promise+Async.swift */,
);
path = Common;
sourceTree = "<group>";
Expand Down Expand Up @@ -1233,6 +1239,7 @@
9E969456249B8493000DB743 /* VaultFormat7ShortenedNameCache.swift in Sources */,
7471BDAE24865B6F000D05FC /* LocalFileSystemProvider.swift in Sources */,
7484608729795421009933D8 /* VaultConfigHelper.swift in Sources */,
4ABE9AA32BCAC8FE00675D74 /* Promise+Async.swift in Sources */,
74073D1927C9406000A86C9A /* Task+Promises.swift in Sources */,
4A1A1194262EC46E00DAF62F /* OneDriveIdentifierCache.swift in Sources */,
746F091327BC0DA2003FCD9F /* PCloudCredential.swift in Sources */,
Expand Down Expand Up @@ -1313,6 +1320,7 @@
4A741FF5287C98F300489C23 /* DirectoryContentCacheMock.swift in Sources */,
7494505F24BC5C3300149816 /* PropfindResponseParserTests.swift in Sources */,
4A6D2CD62643EE83006C5574 /* VaultProviderFactoryTests.swift in Sources */,
4ABE9AA62BCACD1300675D74 /* XCTAssertThrowsError+Async.swift in Sources */,
4A30DAF62636D79C004B5B4E /* OneDriveCloudProviderTests.swift in Sources */,
9EE62A10247D54E90089DAF7 /* CloudProvider+ConvenienceTests.swift in Sources */,
74E32BEA283E575B005D8A54 /* VaultFormat8CryptorMock.swift in Sources */,
Expand Down
14 changes: 14 additions & 0 deletions Sources/CryptomatorCloudAccess/Common/Promise+Async.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation
import Promises

extension Promise {
func async() async throws -> Value {
try await withCheckedThrowingContinuation { continuation in
then { value in
continuation.resume(returning: value)
}.catch { error in
continuation.resume(throwing: error)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,27 @@ public class GoogleDriveCloudProvider: CloudProvider {
private var runningFetchers: [GTMSessionFetcher]
private let maxPageSize: Int

public init(credential: GoogleDriveCredential, useBackgroundSession: Bool = false, maxPageSize: Int = 1000) throws {
init(credential: GoogleDriveCredential, maxPageSize: Int = .max, urlSessionConfiguration: URLSessionConfiguration) throws {
self.driveService = credential.driveService
self.identifierCache = try GoogleDriveIdentifierCache()
self.runningTickets = [GTLRServiceTicket]()
self.runningFetchers = [GTMSessionFetcher]()
let maxAllowedItemLimit = 1000
self.maxPageSize = min(max(1, maxPageSize), maxAllowedItemLimit)
try setupDriveService(credential: credential, useBackgroundSession: useBackgroundSession)
try setupDriveService(credential: credential, configuration: urlSessionConfiguration)
}

private func setupDriveService(credential: GoogleDriveCredential, useBackgroundSession: Bool) throws {
public convenience init(credential: GoogleDriveCredential, maxPageSize: Int = .max) throws {
try self.init(credential: credential, maxPageSize: maxPageSize, urlSessionConfiguration: .default)
}

public static func withBackgroundSession(credential: GoogleDriveCredential, maxPageSize: Int = .max, sessionIdentifier: String, sharedContainerIdentifier: String? = nil) throws -> GoogleDriveCloudProvider {
let configuration = URLSessionConfiguration.background(withIdentifier: sessionIdentifier)
configuration.sharedContainerIdentifier = sharedContainerIdentifier
return try .init(credential: credential, maxPageSize: maxPageSize, urlSessionConfiguration: configuration)
}

private func setupDriveService(credential: GoogleDriveCredential, configuration: URLSessionConfiguration) throws {
driveService.serviceUploadChunkSize = GoogleDriveCloudProvider.maximumUploadFetcherChunkSize
driveService.isRetryEnabled = true
driveService.retryBlock = { _, suggestedWillRetry, fetchError in
Expand All @@ -52,20 +62,11 @@ public class GoogleDriveCloudProvider: CloudProvider {
return suggestedWillRetry
}

let configuration: URLSessionConfiguration
if useBackgroundSession {
driveService.fetcherService.configurationBlock = { _, configuration in
configuration.sharedContainerIdentifier = GoogleDriveSetup.constants.sharedContainerIdentifier
}
let bundleId = Bundle.main.bundleIdentifier ?? ""
let accountId = try credential.getAccountID()
configuration = URLSessionConfiguration.background(withIdentifier: "Crytomator-GoogleDriveSession-\(accountId)-\(bundleId)")
configuration.sharedContainerIdentifier = GoogleDriveSetup.constants.sharedContainerIdentifier
} else {
configuration = URLSessionConfiguration.default
driveService.fetcherService.configurationBlock = { _, configurationToConfigure in
configurationToConfigure.sharedContainerIdentifier = configuration.sharedContainerIdentifier
}

driveService.fetcherService.configuration = configuration

driveService.fetcherService.isRetryEnabled = true
driveService.fetcherService.retryBlock = { suggestedWillRetry, error, response in
if let error = error as NSError? {
Expand Down
25 changes: 11 additions & 14 deletions Sources/CryptomatorCloudAccess/OneDrive/OneDriveCloudProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,26 @@ public class OneDriveCloudProvider: CloudProvider {
private let tmpDirURL: URL
private let maxPageSize: Int

public init(credential: OneDriveCredential, useBackgroundSession: Bool = false, maxPageSize: Int = .max) throws {
let urlSessionConfiguration = OneDriveCloudProvider.createURLSessionConfiguration(credential: credential, useBackgroundSession: useBackgroundSession)
init(credential: OneDriveCredential, maxPageSize: Int = .max, urlSessionConfiguration: URLSessionConfiguration) throws {
self.client = MSClientFactory.createHTTPClient(with: credential.authProvider, andSessionConfiguration: urlSessionConfiguration)
self.identifierCache = try OneDriveIdentifierCache()
self.tmpDirURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
self.maxPageSize = min(max(1, maxPageSize), 1000)
try FileManager.default.createDirectory(at: tmpDirURL, withIntermediateDirectories: true)
}

deinit {
try? FileManager.default.removeItem(at: tmpDirURL)
public convenience init(credential: OneDriveCredential, maxPageSize: Int = .max) throws {
try self.init(credential: credential, maxPageSize: maxPageSize, urlSessionConfiguration: .default)
}

static func createURLSessionConfiguration(credential: OneDriveCredential, useBackgroundSession: Bool) -> URLSessionConfiguration {
let configuration: URLSessionConfiguration
if useBackgroundSession {
let bundleId = Bundle.main.bundleIdentifier ?? ""
configuration = URLSessionConfiguration.background(withIdentifier: "CloudAccess-OneDriveSession-\(credential.identifier)-\(bundleId)")
configuration.sharedContainerIdentifier = OneDriveSetup.sharedContainerIdentifier
} else {
configuration = URLSessionConfiguration.default
}
return configuration
public static func withBackgroundSession(credential: OneDriveCredential, maxPageSize: Int = .max, identifier: String) throws -> OneDriveCloudProvider {
let configuration = URLSessionConfiguration.background(withIdentifier: identifier)
configuration.sharedContainerIdentifier = OneDriveSetup.sharedContainerIdentifier
return try OneDriveCloudProvider(credential: credential, maxPageSize: maxPageSize, urlSessionConfiguration: configuration)
}

deinit {
try? FileManager.default.removeItem(at: tmpDirURL)
}

public func fetchItemMetadata(at cloudPath: CloudPath) -> Promise<CloudItemMetadata> {
Expand Down
6 changes: 3 additions & 3 deletions Sources/CryptomatorCloudAccess/PCloud/PCloudCredential.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ extension PCloud {
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 sessionIdentifier: The unique identifier for the `URLSessionConfiguration` object.
- 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)
public static func createBackgroundClient(with user: OAuth.User, sessionIdentifier: String, sharedContainerIdentifier: String? = nil) -> PCloudClient {
return createBackgroundClient(withAccessToken: user.token, apiHostName: user.httpAPIHostName, sessionIdentifier: sessionIdentifier, sharedContainerIdentifier: sharedContainerIdentifier)
}

private static func createBackgroundClient(withAccessToken accessToken: String, apiHostName: String, sessionIdentifier: String, sharedContainerIdentifier: String?) -> PCloudClient {
Expand Down
4 changes: 2 additions & 2 deletions Sources/CryptomatorCloudAccess/WebDAV/WebDAVClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public class WebDAVClient {
If the `WebDAVClient` is used in an app extension, set the `sharedContainerIdentifier` to a valid identifier for a container that will be shared between the app and the extension.
*/
public static func withBackgroundSession(credential: WebDAVCredential, sharedContainerIdentifier: String? = nil) -> WebDAVClient {
public static func withBackgroundSession(credential: WebDAVCredential, sessionIdentifier: String, sharedContainerIdentifier: String? = nil) -> WebDAVClient {
let urlSessionDelegate = WebDAVClientURLSessionDelegate(credential: credential)
let session = WebDAVSession.createBackgroundSession(with: urlSessionDelegate, sharedContainerIdentifier: sharedContainerIdentifier)
let session = WebDAVSession.createBackgroundSession(with: urlSessionDelegate, sessionIdentifier: sessionIdentifier, sharedContainerIdentifier: sharedContainerIdentifier)
return WebDAVClient(credential: credential, session: session)
}

Expand Down
5 changes: 2 additions & 3 deletions Sources/CryptomatorCloudAccess/WebDAV/WebDAVSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,8 @@ class WebDAVSession {
To avoid collisions in the `URLSession` Identifier between multiple targets (e.g. main app and app extension), the `BundleID` is used in addition to the Credential UID.
*/
static func createBackgroundSession(with delegate: WebDAVClientURLSessionDelegate, sharedContainerIdentifier: String? = nil) -> WebDAVSession {
let bundleId = Bundle.main.bundleIdentifier ?? ""
let configuration = URLSessionConfiguration.background(withIdentifier: "CloudAccessWebDAVSession_\(delegate.credential.identifier)_\(bundleId)")
static func createBackgroundSession(with delegate: WebDAVClientURLSessionDelegate, sessionIdentifier: String, sharedContainerIdentifier: String? = nil) -> WebDAVSession {
let configuration = URLSessionConfiguration.background(withIdentifier: sessionIdentifier)
configuration.sharedContainerIdentifier = sharedContainerIdentifier
configuration.httpCookieStorage = HTTPCookieStorage()
configuration.urlCredentialStorage = nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class VaultFormat6GoogleDriveIntegrationTests: CloudAccessIntegrationTest {

private static let credential = GoogleDriveAuthenticatorMock.generateAuthorizedCredential(withRefreshToken: IntegrationTestSecrets.googleDriveRefreshToken, tokenUID: "IntegrationTest")
// swiftlint:disable:next force_try
private static let cloudProvider = try! GoogleDriveCloudProvider(credential: credential, useBackgroundSession: false)
private static let cloudProvider = try! GoogleDriveCloudProvider(credential: credential)
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6")

override class func setUp() {
Expand All @@ -45,7 +45,7 @@ class VaultFormat6GoogleDriveIntegrationTests: CloudAccessIntegrationTest {
override func setUpWithError() throws {
try super.setUpWithError()
let credential = GoogleDriveAuthenticatorMock.generateAuthorizedCredential(withRefreshToken: IntegrationTestSecrets.googleDriveRefreshToken, tokenUID: UUID().uuidString)
let cloudProvider = try GoogleDriveCloudProvider(credential: credential, useBackgroundSession: false)
let cloudProvider = try GoogleDriveCloudProvider(credential: credential)
let setUpPromise = DecoratorFactory.createFromExistingVaultFormat6(delegate: cloudProvider, vaultPath: VaultFormat6GoogleDriveIntegrationTests.vaultPath, password: "IntegrationTest").then { decorator in
self.provider = decorator
}
Expand All @@ -60,7 +60,6 @@ class VaultFormat6GoogleDriveIntegrationTests: CloudAccessIntegrationTest {
override func createLimitedCloudProvider() throws -> CloudProvider {
let credential = GoogleDriveAuthenticatorMock.generateAuthorizedCredential(withRefreshToken: IntegrationTestSecrets.googleDriveRefreshToken, tokenUID: UUID().uuidString)
let cloudProvider = try GoogleDriveCloudProvider(credential: credential,
useBackgroundSession: false,
maxPageSize: maxPageSizeForLimitedCloudProvider)
let setUpPromise = DecoratorFactory.createFromExistingVaultFormat6(delegate: cloudProvider, vaultPath: VaultFormat6GoogleDriveIntegrationTests.vaultPath, password: "IntegrationTest").then { decorator in
self.provider = decorator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class VaultFormat6OneDriveIntegrationTests: CloudAccessIntegrationTest {
// swiftlint:disable:next force_try
private static let credential = try! OneDriveCredentialMock()
// swiftlint:disable:next force_try
private static let cloudProvider = try! OneDriveCloudProvider(credential: credential, useBackgroundSession: false)
private static let cloudProvider = try! OneDriveCloudProvider(credential: credential)
private static let vaultPath = CloudPath("/iOS-IntegrationTests-VaultFormat6")

override class func setUp() {
Expand Down Expand Up @@ -58,7 +58,6 @@ class VaultFormat6OneDriveIntegrationTests: CloudAccessIntegrationTest {

override func createLimitedCloudProvider() throws -> CloudProvider {
let limitedDelegate = try OneDriveCloudProvider(credential: VaultFormat6OneDriveIntegrationTests.credential,
useBackgroundSession: false,
maxPageSize: maxPageSizeForLimitedCloudProvider)
let setUpPromise = DecoratorFactory.createFromExistingVaultFormat6(delegate: limitedDelegate, vaultPath: VaultFormat6OneDriveIntegrationTests.vaultPath, password: "IntegrationTest").then { decorator in
self.provider = decorator
Expand Down
Loading

0 comments on commit 243d39e

Please sign in to comment.