Skip to content

Commit

Permalink
Merge branch 'release/1.7.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
tobihagemann committed Jan 6, 2023
2 parents 5c9e38e + 0b3342f commit e30cb03
Show file tree
Hide file tree
Showing 23 changed files with 170 additions and 82 deletions.
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,8 +33,8 @@ The core package contains several protocols, structs, and enums that build the f
```swift
func fetchItemMetadata(at cloudPath: CloudPath) -> Promise<CloudItemMetadata>
func fetchItemList(forFolderAt cloudPath: CloudPath, withPageToken pageToken: String?) -> Promise<CloudItemList>
func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise<Void>
func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise<CloudItemMetadata>
func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise<Void>
func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise<CloudItemMetadata>
func createFolder(at cloudPath: CloudPath) -> Promise<Void>
func deleteFile(at cloudPath: CloudPath) -> Promise<Void>
func deleteFolder(at cloudPath: CloudPath) -> Promise<Void>
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions Sources/CryptomatorCloudAccess/API/CloudProvider+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ public extension CloudProvider {
}
}

/**
Convenience wrapper for `downloadFile()` that ignores the underlying task.
*/
func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise<Void> {
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<CloudItemMetadata> {
uploadFile(from: localURL, to: cloudPath, replaceExisting: replaceExisting, onTaskCreation: nil)
}

/**
Convenience wrapper for `createFolder()` that also satisfies if the item is present.
*/
Expand Down
6 changes: 4 additions & 2 deletions Sources/CryptomatorCloudAccess/API/CloudProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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<Void>
func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise<Void>

/**
Uploads a file.
Expand All @@ -64,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`.
Expand All @@ -76,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<CloudItemMetadata>
func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise<CloudItemMetadata>

/**
Creates a folder.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ class VaultFormat6ProviderDecorator: CloudProvider {
}
}

func downloadFile(from cleartextCloudPath: CloudPath, to cleartextLocalURL: URL) -> Promise<Void> {
func downloadFile(from cleartextCloudPath: CloudPath, to cleartextLocalURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise<Void> {
precondition(cleartextLocalURL.isFileURL)
let overallProgress = Progress(totalUnitCount: 5)
let ciphertextLocalURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false)
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<Void> in
let downloadFilePromise = self.delegate.downloadFile(from: fileCiphertextPath, to: ciphertextLocalURL, onTaskCreation: onTaskCreation).recover { error -> Promise<Void> in
guard case CloudProviderError.itemNotFound = error else {
return Promise(error)
}
Expand All @@ -107,7 +107,7 @@ class VaultFormat6ProviderDecorator: CloudProvider {
}
}

func uploadFile(from cleartextLocalURL: URL, to cleartextCloudPath: CloudPath, replaceExisting: Bool) -> Promise<CloudItemMetadata> {
func uploadFile(from cleartextLocalURL: URL, to cleartextCloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise<CloudItemMetadata> {
precondition(cleartextLocalURL.isFileURL)
let overallProgress = Progress(totalUnitCount: 5)
let ciphertextLocalURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,23 @@ class VaultFormat6ShorteningProviderDecorator: CloudProvider {
}
}

func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise<Void> {
func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise<Void> {
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<CloudItemMetadata> {
func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise<CloudItemMetadata> {
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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ class VaultFormat7ProviderDecorator: CloudProvider {
}
}

func downloadFile(from cleartextCloudPath: CloudPath, to cleartextLocalURL: URL) -> Promise<Void> {
func downloadFile(from cleartextCloudPath: CloudPath, to cleartextLocalURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise<Void> {
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 {
Expand All @@ -91,7 +91,7 @@ class VaultFormat7ProviderDecorator: CloudProvider {
}
}

func uploadFile(from cleartextLocalURL: URL, to cleartextCloudPath: CloudPath, replaceExisting: Bool) -> Promise<CloudItemMetadata> {
func uploadFile(from cleartextLocalURL: URL, to cleartextCloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise<CloudItemMetadata> {
precondition(cleartextLocalURL.isFileURL)
let overallProgress = Progress(totalUnitCount: 5)
let ciphertextLocalURL = tmpDirURL.appendingPathComponent(UUID().uuidString, isDirectory: false)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,31 +75,31 @@ class VaultFormat7ShorteningProviderDecorator: CloudProvider {
}
}

func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise<Void> {
func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise<Void> {
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)
}
}

func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise<CloudItemMetadata> {
func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise<CloudItemMetadata> {
precondition(localURL.isFileURL)
let shortened = shortenedNameCache.getShortenedPath(cloudPath)
if shortened.pointsToC9S, let c9sDir = shortened.c9sDir {
return createC9SFolderAndUploadNameFile(c9sDir).then { () -> Promise<CloudItemMetadata> 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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public class DropboxCloudProvider: CloudProvider {
}, condition: shouldRetryForError)
}

public func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise<Void> {
public func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise<Void> {
precondition(localURL.isFileURL)
guard let authorizedClient = credential.authorizedClient else {
return Promise(CloudProviderError.unauthorized)
Expand All @@ -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<CloudItemMetadata> {
public func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise<CloudItemMetadata> {
precondition(localURL.isFileURL)
guard let authorizedClient = credential.authorizedClient else {
return Promise(CloudProviderError.unauthorized)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,21 @@ public class GoogleDriveCloudProvider: CloudProvider {
}
}

public func downloadFile(from cloudPath: CloudPath, to localURL: URL) -> Promise<Void> {
public func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise<Void> {
precondition(localURL.isFileURL)
if FileManager.default.fileExists(atPath: localURL.path) {
return Promise(CloudProviderError.itemAlreadyExists)
}
let progress = Progress(totalUnitCount: 1)
return resolvePath(forItemAt: cloudPath).then { item -> Promise<Void> 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
}
}

public func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise<CloudItemMetadata> {
public func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise<CloudItemMetadata> {
precondition(localURL.isFileURL)
var isDirectory: ObjCBool = false
let fileExists = FileManager.default.fileExists(atPath: localURL.path, isDirectory: &isDirectory)
Expand Down Expand Up @@ -233,7 +233,7 @@ public class GoogleDriveCloudProvider: CloudProvider {
}
}

private func downloadFile(for item: GoogleDriveItem, to localURL: URL) -> Promise<Void> {
private func downloadFile(for item: GoogleDriveItem, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise<Void> {
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)
Expand All @@ -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<CloudItemMetadata> {
Expand Down Expand Up @@ -552,7 +558,8 @@ public class GoogleDriveCloudProvider: CloudProvider {
}
}

private func executeFetcher(_ fetcher: GTMSessionFetcher) -> Promise<Void> {
private func executeFetcher(_ fetcher: GTMSessionFetcher, onTaskCreation: @escaping (URLSessionTask?) -> Void) -> Promise<Void> {
var kvoToken: NSKeyValueObservation?
return Promise<Void> { fulfill, reject in
self.runningFetchers.append(fetcher)
fetcher.beginFetch { _, error in
Expand All @@ -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()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Void> {
public func downloadFile(from cloudPath: CloudPath, to localURL: URL, onTaskCreation: ((URLSessionDownloadTask?) -> Void)?) -> Promise<Void> {
precondition(localURL.isFileURL)
let url = rootURL.appendingPathComponent(cloudPath)
let shouldStopAccessing = url.startAccessingSecurityScopedResource()
Expand Down Expand Up @@ -216,7 +216,7 @@ public class LocalFileSystemProvider: CloudProvider {
return promise
}

public func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool) -> Promise<CloudItemMetadata> {
public func uploadFile(from localURL: URL, to cloudPath: CloudPath, replaceExisting: Bool, onTaskCreation: ((URLSessionUploadTask?) -> Void)?) -> Promise<CloudItemMetadata> {
precondition(localURL.isFileURL)
let url = rootURL.appendingPathComponent(cloudPath)
let shouldStopAccessing = url.startAccessingSecurityScopedResource()
Expand Down
Loading

0 comments on commit e30cb03

Please sign in to comment.