Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b2a7800f7c | |||
| 96c102d156 | |||
| 8aedd8e72a | |||
| 5b55debe8a | |||
| 0fa062a946 | |||
| 879f86c1f9 | |||
| 80d5f02bbd | |||
| b8a7721b2f | |||
| 44b4784cd3 | |||
| 7d9e2247f2 | |||
| 0ace562442 | |||
| 63d831ef90 | |||
| d6b91348a3 | |||
| fd9d4c1ab4 | |||
| 41e266c2a9 | |||
| 6127f4a7d9 | |||
| faca943beb | |||
| 9345436fa2 | |||
| 6228d88d41 | |||
| 5b395915a9 | |||
| e4f12a502b |
+2
-1
@@ -75,6 +75,7 @@ deploy:
|
||||
file: $FRAMEWORK_NAME.zip
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: amosavian/$PROJECTNAME
|
||||
# repo: amosavian/$PROJECTNAME
|
||||
repo: amosavian/FileProvider
|
||||
tags: true
|
||||
condition: "$CARTHAGEDEPLOY = YES"
|
||||
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
|
||||
#
|
||||
|
||||
s.name = "FilesProvider"
|
||||
s.version = "0.18.0"
|
||||
s.version = "0.19.0"
|
||||
s.summary = "FileManager replacement for Local and Remote (WebDAV/FTP/Dropbox/OneDrive/SMB2) files on iOS and macOS."
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
|
||||
@@ -621,7 +621,7 @@
|
||||
799396601D48B7BF00086753 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.18.0;
|
||||
BUNDLE_VERSION_STRING = 0.19.0;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -653,7 +653,7 @@
|
||||
799396611D48B7BF00086753 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.18.0;
|
||||
BUNDLE_VERSION_STRING = 0.19.0;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FileProvider"
|
||||
)
|
||||
name: "FilesProvider"
|
||||
)
|
||||
|
||||
@@ -2,21 +2,28 @@
|
||||
|
||||
>This Swift library provide a swifty way to deal with local and remote files and directories in a unified way.
|
||||
|
||||
<center>
|
||||
|
||||
[![Swift Version][swift-image]][swift-url]
|
||||
[![Platform][platform-image]](#)
|
||||
[![License][license-image]][license-url]
|
||||
|
||||
[![Release versin][release-image]][release-url]
|
||||
[][cocoapods]
|
||||
[![Carthage compatible][carthage-image]](https://github.com/Carthage/Carthage)
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![Codebeat Badge][codebeat-image]][codebeat-url]
|
||||
[![Cocoapods Docs][docs-image]][docs-url]
|
||||
|
||||
[![Release version][release-image]][release-url]
|
||||
[][cocoapods]
|
||||
[![Carthage compatible][carthage-image]](https://github.com/Carthage/Carthage)
|
||||
[![Cocoapods Downloads][cocoapods-downloads]][cocoapods]
|
||||
[![Cocoapods Apps][cocoapods-apps]][cocoapods]
|
||||
|
||||
Old Cocoapods repo stats:
|
||||
[![Cocoapods Downloads][cocoapods-downloads-old]][cocoapods-old]
|
||||
[![Cocoapods Apps][cocoapods-apps-old]][cocoapods-old]
|
||||
|
||||
</center>
|
||||
|
||||
<!---
|
||||
[![Cocoapods Doc][docs-image]][docs-url]
|
||||
[](https://codecov.io/gh/amosavian/FileProvider)
|
||||
--->
|
||||
|
||||
@@ -30,7 +37,6 @@ All functions do async calls and it wont block your main thread.
|
||||
- [x] **CloudFileProvider** A wrapper around app's ubiquitous container API of iCloud Drive.
|
||||
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission standard, supported by some cloud services like `ownCloud`, `Box.com` and `Yandex.disk`.
|
||||
- [x] **FTPFileProvider** While deprecated in 1990s due to serious security concerns, it's still in use on some Web hosts.
|
||||
* Active mode is not implemented yet.
|
||||
- [x] **DropboxFileProvider** A wrapper around Dropbox Web API.
|
||||
* For now it has limitation in uploading files up to 150MB.
|
||||
- [x] **OneDriveFileProvider** A wrapper around OneDrive REST API, works with `onedrive.com` and compatible (business) servers.
|
||||
@@ -39,7 +45,7 @@ All functions do async calls and it wont block your main thread.
|
||||
- [ ] **AmazonS3FileProvider** Amazon storage backend. Used by many sites.
|
||||
- [ ] **SMBFileProvider** SMB2/3 introduced in 2006, which is a file and printer sharing protocol originated from Microsoft Windows and now is replacing AFP protocol on macOS.
|
||||
* Data types and some basic functions are implemented but *main interface is not implemented yet!*.
|
||||
* SMB1/CIFS is deprecated and very tricky to be implemented due to strict memory allignment in Swift.
|
||||
* SMBv1/CIFS is insecure, deprecated and kinda tricky to be implemented due to strict memory allignment in Swift.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -51,7 +57,7 @@ Legacy version is available in swift-2 branch.
|
||||
|
||||
## Installation
|
||||
|
||||
###Important: this library has been renamed to avoid conflict in iOS 11, macOS 10.13 and Xcode 9.0. Please read issue [#53](https://github.com/amosavian/FileProvider/issues/53) to find more.
|
||||
### Important: this library has been renamed to avoid conflict in iOS 11, macOS 10.13 and Xcode 9.0. Please read issue [#53](https://github.com/amosavian/FileProvider/issues/53) to find more.
|
||||
|
||||
|
||||
### Cocoapods / Carthage / Swift Package Manager
|
||||
@@ -240,10 +246,10 @@ To get list of files in a directory:
|
||||
documentsProvider.contentsOfDirectory(path: "/", completionHandler: {
|
||||
contents, error in
|
||||
for file in contents {
|
||||
print("Name: \(attributes.name)")
|
||||
print("Size: \(attributes.size)")
|
||||
print("Creation Date: \(attributes.creationDate)")
|
||||
print("Modification Date: \(attributes.modifiedDate)")
|
||||
print("Name: \(file.name)")
|
||||
print("Size: \(file.size)")
|
||||
print("Creation Date: \(file.creationDate)")
|
||||
print("Modification Date: \(file.modifiedDate)")
|
||||
}
|
||||
})
|
||||
```
|
||||
@@ -260,15 +266,6 @@ func storageProperties(completionHandler: { total, used in
|
||||
|
||||
* if this function is unavailable on provider or an error has been occurred, total space will be reported `-1` and used space `0`
|
||||
|
||||
### Change current directory
|
||||
|
||||
```swift
|
||||
documentsProvider.currentPath = "/New Folder"
|
||||
// now path is ~/Documents/New Folder
|
||||
```
|
||||
|
||||
You can then pass "" (empty string) to `contentsOfDirectory` method to list files in current directory.
|
||||
|
||||
### Creating File and Folders
|
||||
|
||||
Creating new directory:
|
||||
@@ -307,8 +304,6 @@ documentsProvider.moveItem(path: "new folder/old.txt", to: "new.txt", overwrite:
|
||||
documentsProvider.removeItem(path: "new.txt", completionHandler: nil)
|
||||
```
|
||||
|
||||
***Caution:*** This method will delete directories with all it's contents recursively except for FTP providers that don't support `SITE RMDIR` command, this will be fixed later.
|
||||
|
||||
### Fetching Contents of File
|
||||
|
||||
There is two method for this purpose, one of them loads entire file into `Data` and another can load a portion of file.
|
||||
@@ -340,7 +335,7 @@ let data = "What's up Newyork!".data(encoding: .utf8)
|
||||
documentsProvider.writeContents(path: "old.txt", content: data, atomically: true, completionHandler: nil)
|
||||
```
|
||||
|
||||
### Copying Files to and From Local URL
|
||||
### Copying Files to and From Local Storage
|
||||
|
||||
There are two methods to download and upload files between provider's and local storage. These methods use `URLSessionDownloadTask` and `URLSessionUploadTask` classes and allows to use background session and provide progress via delegate.
|
||||
|
||||
@@ -361,6 +356,12 @@ documentsProvider.copyItem(path: "/download/image.jpg", toLocalURL: fileURL, ove
|
||||
* It's safe only to assume these methods **won't** handle directories to upload/download recursively. If you need, you can list directories, create directories on target and copy files using these methods.
|
||||
* FTP provider allows developer to either use apple implemented `URLSessionDownloadTask` or custom implemented method based on stream task via `useAppleImplementation` property. FTP protocol is not supported by background session.
|
||||
|
||||
### Operation Progress
|
||||
|
||||
Creating/Copying/Deleting/Searching functions return a `(NS)Progress`. It provides operation type, progress and a `.cancel()` method which allows you to cancel operation in midst. You can check `cancellable` property to check either you can cancel operation via this object or not.
|
||||
|
||||
- **Note:** Progress reporting is not supported by native `(NS)FileManager` so `LocalFileProvider`.
|
||||
|
||||
### Undo Operations
|
||||
|
||||
Providers conform to `FileProviderUndoable` can perform undo for **some** operations like moving/renaming, copying and creating (file or folder). **Now, only `LocalFileProvider` supports this feature.** To implement:
|
||||
@@ -404,12 +405,6 @@ class ViewController: UIViewController
|
||||
}
|
||||
```
|
||||
|
||||
### Operation Handle
|
||||
|
||||
Creating/Copying/Deleting functions return a `OperationHandle` for remote operations. It provides operation type, progress and a `.cancel()` method which allows you to cancel operation in midst.
|
||||
|
||||
It's not supported by native `(NS)FileManager` so `LocalFileProvider`, but this functionality will be added to future `PosixFileProvider` class.
|
||||
|
||||
### File Coordination
|
||||
|
||||
`LocalFileProvider` and its descendents has a `isCoordinating` property. By setting this, provider will use `NSFileCoordinating` class to do all file operations. It's mandatory for iCloud, while recommended when using shared container or anywhere that simultaneous operations on a file/folder is common.
|
||||
@@ -441,9 +436,10 @@ To check either file thumbnail is supported or not and fetch thumbnail, use (and
|
||||
|
||||
```swift
|
||||
let path = "/newImage.jpg"
|
||||
let thumbSize = CGSize(width: 64, height: 64)
|
||||
let thumbSize = CGSize(width: 64, height: 64) // or nil which renders to default dimension of provider
|
||||
if documentsProvider.thumbnailOfFileSupported(path: path {
|
||||
documentsProvider.thumbnailOfFile(path: file.path, dimension: thumbSize, completionHandler: { (image, error) in
|
||||
// Interacting with UI must be placed in main thread
|
||||
DispatchQueue.main.async {
|
||||
self.previewImage.image = image
|
||||
}
|
||||
@@ -495,10 +491,11 @@ Distributed under the MIT license. See `LICENSE` for more information.
|
||||
|
||||
[https://github.com/amosavian/](https://github.com/amosavian/)
|
||||
|
||||
[cocoapods]: https://cocoapods.org/pods/FileProvider
|
||||
[cocoapods]: https://cocoapods.org/pods/FilesProvider
|
||||
[cocoapods-old]: https://cocoapods.org/pods/FileProvider
|
||||
[swift-image]: https://img.shields.io/badge/swift-3.0,%203.1-orange.svg
|
||||
[swift-url]: https://swift.org/
|
||||
[platform-image]: https://img.shields.io/cocoapods/p/FileProvider.svg
|
||||
[platform-image]: https://img.shields.io/cocoapods/p/FilesProvider.svg
|
||||
[license-image]: https://img.shields.io/github/license/amosavian/FileProvider.svg
|
||||
[license-url]: LICENSE
|
||||
[codebeat-image]: https://codebeat.co/badges/7b359f48-78eb-4647-ab22-56262a827517
|
||||
@@ -508,7 +505,9 @@ Distributed under the MIT license. See `LICENSE` for more information.
|
||||
[release-url]: https://github.com/amosavian/FileProvider/releases
|
||||
[release-image]: https://img.shields.io/github/release/amosavian/FileProvider.svg
|
||||
[carthage-image]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg
|
||||
[cocoapods-downloads]: https://img.shields.io/cocoapods/dt/FileProvider.svg
|
||||
[cocoapods-apps]: https://img.shields.io/cocoapods/at/FileProvider.svg
|
||||
[docs-image]: https://img.shields.io/cocoapods/metrics/doc-percent/FileProvider.svg
|
||||
[docs-url]: http://cocoadocs.org/docsets/FileProvider/
|
||||
[cocoapods-downloads-old]: https://img.shields.io/cocoapods/dt/FileProvider.svg
|
||||
[cocoapods-apps-old]: https://img.shields.io/cocoapods/at/FileProvider.svg
|
||||
[cocoapods-downloads]: https://img.shields.io/cocoapods/dt/FilesProvider.svg
|
||||
[cocoapods-apps]: https://img.shields.io/cocoapods/at/FilesProvider.svg
|
||||
[docs-image]: https://img.shields.io/cocoapods/metrics/doc-percent/FilesProvider.svg
|
||||
[docs-url]: http://cocoadocs.org/docsets/FilesProvider/
|
||||
+111
-117
@@ -243,13 +243,13 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
- Note: For now only it's limited to file names. `query` parameter may take `NSPredicate` format in near future.
|
||||
|
||||
- Parameters:
|
||||
- path: location of directory to start search
|
||||
- recursive: Searching subdirectories of path
|
||||
- query: Simple string of file name to be search (for now).
|
||||
- foundItemHandler: Closure which is called when a file is found
|
||||
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
|
||||
- path: location of directory to start search
|
||||
- recursive: Searching subdirectories of path
|
||||
- query: Simple string of file name to be search (for now).
|
||||
- foundItemHandler: Closure which is called when a file is found
|
||||
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
|
||||
*/
|
||||
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
|
||||
|
||||
let mapDict: [String: String] = ["url": NSMetadataItemURLKey, "name": NSMetadataItemFSNameKey, "path": NSMetadataItemPathKey, "filesize": NSMetadataItemFSSizeKey, "modifiedDate": NSMetadataItemFSContentChangeDateKey, "creationDate": NSMetadataItemFSCreationDateKey, "contentType": NSMetadataItemContentTypeKey]
|
||||
|
||||
@@ -282,6 +282,8 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
}
|
||||
}
|
||||
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
|
||||
dispatch_queue.async {
|
||||
let pathURL = self.url(of: path)
|
||||
let mdquery = NSMetadataQuery()
|
||||
@@ -290,6 +292,10 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
|
||||
var lastReportedCount = 0
|
||||
|
||||
progress.cancellationHandler = { [weak mdquery] in
|
||||
mdquery?.stop()
|
||||
}
|
||||
|
||||
if let foundItemHandler = foundItemHandler {
|
||||
var updateObserver: NSObjectProtocol?
|
||||
|
||||
@@ -314,6 +320,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
}
|
||||
}
|
||||
lastReportedCount = mdquery.resultCount
|
||||
progress.totalUnitCount = Int64(lastReportedCount)
|
||||
|
||||
mdquery.enableUpdates()
|
||||
})
|
||||
@@ -346,12 +353,14 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
contents.append(file)
|
||||
}
|
||||
}
|
||||
progress.completedUnitCount = Int64(contents.count)
|
||||
self.dispatch_queue.async {
|
||||
completionHandler(contents, nil)
|
||||
}
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
if !mdquery.start() {
|
||||
self.dispatch_queue.async {
|
||||
completionHandler([], self.throwError(path, code: CocoaError.fileReadNoPermission))
|
||||
@@ -359,6 +368,8 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return progress
|
||||
}
|
||||
|
||||
open override func isReachable(completionHandler: @escaping (Bool) -> Void) {
|
||||
@@ -375,10 +386,10 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
- folder: Directory name.
|
||||
- at: Parent path of new directory.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `CloudFileProvider`.
|
||||
- Returns: A `Progress` object to get progress or cancel progress. Doesn't work on `CloudFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
open override func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open override func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return super.create(folder: folderName, at: atPath, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@@ -392,10 +403,10 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
- to: destination path of file or directory, including file/directory name.
|
||||
- overwrite: Destination file should be overwritten if file is already exists. **Default** is `false`.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `CloudFileProvider`.
|
||||
- Returns: A `Progress` object to get progress or cancel progress. Doesn't work on `CloudFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
open override func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open override func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return super.moveItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@@ -409,10 +420,10 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
- to: destination path of file or directory, including file/directory name.
|
||||
- overwrite: Destination file should be overwritten if file is already exists. **Default** is `false`.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `CloudFileProvider`.
|
||||
- Returns: A `Progress` object to get progress or cancel progress. Doesn't work on `CloudFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
open override func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open override func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return super.copyItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@@ -426,11 +437,10 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
- Parameters:
|
||||
- path: file or directory path.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `CloudFileProvider`.
|
||||
|
||||
- Returns: A `Progress` object to get progress or cancel progress. Doesn't work on `CloudFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
open override func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open override func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return super.removeItem(path: path, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@@ -443,13 +453,18 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
- to: destination path of file, including file/directory name.
|
||||
- overwrite: Destination file should be overwritten if file is already exists. **Default** is `false`.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress.
|
||||
- Returns: A `Progress` object to get progress or cancel progress.
|
||||
*/
|
||||
@discardableResult
|
||||
open override func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open override func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
// TODO: Make use of overwrite parameter
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
let operationHandle = CloudOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.isCancellable = false
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
monitorFile(path: toPath, opType: opType, progress: progress)
|
||||
operation_queue.addOperation {
|
||||
let tempFolder: URL
|
||||
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) {
|
||||
@@ -477,7 +492,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
})
|
||||
}
|
||||
}
|
||||
return operationHandle
|
||||
return progress
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -488,12 +503,17 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
- path: original file or directory path.
|
||||
- toLocalURL: destination local url of file, including file/directory name.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress.
|
||||
- Returns: A `Progress` object to get progress or cancel progress.
|
||||
*/
|
||||
@discardableResult
|
||||
open override func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open override func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.copy(source: path, destination: toLocalURL.absoluteString)
|
||||
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.isCancellable = false
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
monitorFile(path: path, opType: opType, progress: progress)
|
||||
do {
|
||||
try self.opFileManager.startDownloadingUbiquitousItem(at: self.url(of: path))
|
||||
} catch let e {
|
||||
@@ -503,8 +523,8 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
})
|
||||
return nil
|
||||
}
|
||||
let handle = super.copyItem(path: path, toLocalURL: toLocalURL, completionHandler: completionHandler)
|
||||
return handle
|
||||
let _ = super.copyItem(path: path, toLocalURL: toLocalURL, completionHandler: completionHandler)
|
||||
return progress
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -516,11 +536,18 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
- completionHandler: a closure with result of file contents or error.
|
||||
`contents`: contents of file in a `Data` object.
|
||||
`error`: Error returned by system.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress.
|
||||
- Returns: A `Progress` object to get progress or cancel progress.
|
||||
*/
|
||||
@discardableResult
|
||||
open override func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
return super.contents(path: path, completionHandler: completionHandler)
|
||||
open override func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
|
||||
let operation = FileOperationType.fetch(path: path)
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
monitorFile(path: path, opType: operation, progress: progress)
|
||||
_ = super.contents(path: path, completionHandler: completionHandler)
|
||||
return progress
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -534,11 +561,18 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
- completionHandler: a closure with result of file contents or error.
|
||||
`contents`: contents of file in a `Data` object.
|
||||
`error`: Error returned by system.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress.
|
||||
- Returns: A `Progress` object to get progress or cancel progress.
|
||||
*/
|
||||
@discardableResult
|
||||
open override func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
return super.contents(path: path, offset: offset, length: length, completionHandler: completionHandler)
|
||||
open override func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
|
||||
let operation = FileOperationType.fetch(path: path)
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
monitorFile(path: path, opType: operation, progress: progress)
|
||||
_ = super.contents(path: path, offset: offset, length: length, completionHandler: completionHandler)
|
||||
return progress
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -550,11 +584,18 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
- overwrite: Destination file should be overwritten if file is already exists. Default is `false`.
|
||||
- atomically: data will be written to a temporary file before writing to final location. Default is `false`.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: A `Progress` object to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
open override func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
return super.writeContents(path: path, contents: data, atomically: atomically, overwrite: overwrite, completionHandler: completionHandler)
|
||||
open override func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let operation = FileOperationType.fetch(path: path)
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
monitorFile(path: path, opType: operation, progress: progress)
|
||||
_ = super.writeContents(path: path, contents: data, atomically: atomically, overwrite: overwrite, completionHandler: completionHandler)
|
||||
return progress
|
||||
}
|
||||
|
||||
fileprivate var monitors = [String: (NSMetadataQuery, NSObjectProtocol)]()
|
||||
@@ -639,32 +680,40 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
return file
|
||||
}
|
||||
|
||||
func monitorFile(path: String, opType: FileOperationType) {
|
||||
func monitorFile(path: String, opType: FileOperationType, progress: Progress?) {
|
||||
dispatch_queue.async {
|
||||
let pathURL = self.url(of: path)
|
||||
let size = pathURL.fileSize
|
||||
progress?.totalUnitCount = size > 0 ? size : 0
|
||||
let query = NSMetadataQuery()
|
||||
query.predicate = NSPredicate(format: "%K LIKE %@", NSMetadataItemPathKey, pathURL.path)
|
||||
query.valueListAttributes = [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataUbiquitousItemPercentDownloadedKey, NSMetadataUbiquitousItemPercentUploadedKey, NSMetadataItemFSSizeKey]
|
||||
query.searchScopes = [self.scope.rawValue]
|
||||
var updateObserver: NSObjectProtocol?
|
||||
updateObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
|
||||
updateObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidUpdate, object: query, queue: nil, using: { (notification) in
|
||||
query.disableUpdates()
|
||||
|
||||
guard let item = (query.results as? [NSMetadataItem])?.first else {
|
||||
return
|
||||
}
|
||||
|
||||
if progress?.totalUnitCount == 0, let size = item.value(forAttribute: NSMetadataItemFSSizeKey) as? Int64 {
|
||||
progress?.totalUnitCount = size
|
||||
}
|
||||
let downloaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? Double ?? 0
|
||||
let uploaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentUploadedKey) as? Double ?? 0
|
||||
if (downloaded == 0 || downloaded == 100) && (uploaded > 0 && uploaded < 100) {
|
||||
progress?.completedUnitCount = Int64(uploaded / 100 * Double(progress?.totalUnitCount ?? 0))
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(uploaded / 100))
|
||||
}
|
||||
} else if (uploaded == 0 || uploaded == 100) && (downloaded > 0 && downloaded < 100) {
|
||||
progress?.completedUnitCount = Int64(downloaded / 100 * Double(progress?.totalUnitCount ?? 0))
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(downloaded / 100))
|
||||
}
|
||||
} else if uploaded == 100 || downloaded == 100 {
|
||||
progress?.completedUnitCount = progress?.totalUnitCount ?? 0
|
||||
query.stop()
|
||||
NotificationCenter.default.removeObserver(updateObserver!)
|
||||
DispatchQueue.main.async {
|
||||
@@ -676,6 +725,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
progress?.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
query.start()
|
||||
}
|
||||
}
|
||||
@@ -745,92 +795,36 @@ public enum UbiquitousScope: RawRepresentable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get progress of CloudFileProvider operations
|
||||
open class CloudOperationHandle: OperationHandle {
|
||||
/// Url of file which operation is doing on
|
||||
public let baseURL: URL?
|
||||
/// Type of operation
|
||||
public let operationType: FileOperationType
|
||||
/*
|
||||
func getMetadataItem(url: URL) -> NSMetadataItem? {
|
||||
let query = NSMetadataQuery()
|
||||
query.predicate = NSPredicate(format: "(%K LIKE %@)", NSMetadataItemPathKey, url.path)
|
||||
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope, NSMetadataQueryUbiquitousDataScope]
|
||||
|
||||
init (operationType: FileOperationType, baseURL: URL?) {
|
||||
self.baseURL = baseURL
|
||||
self.operationType = operationType
|
||||
}
|
||||
var item: NSMetadataItem?
|
||||
|
||||
private var sourceURL: URL? {
|
||||
guard let source = operationType.source, let baseURL = baseURL else { return nil }
|
||||
return source.hasPrefix("file://") ? URL(fileURLWithPath: source) : baseURL.appendingPathComponent(source)
|
||||
}
|
||||
|
||||
private var destURL: URL? {
|
||||
guard let dest = operationType.destination, let baseURL = baseURL else { return nil }
|
||||
return dest.hasPrefix("file://") ? URL(fileURLWithPath: dest) : baseURL.appendingPathComponent(dest)
|
||||
}
|
||||
|
||||
open var bytesSoFar: Int64 {
|
||||
assert(!Thread.isMainThread, "Don't run \(#function) method on main thread")
|
||||
|
||||
guard let url = destURL ?? sourceURL, let item = CloudOperationHandle.getMetadataItem(url: url) else { return 0 }
|
||||
let downloaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? Double ?? 0
|
||||
let uploaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentUploadedKey) as? Double ?? 0
|
||||
guard let size = item.value(forAttribute: NSMetadataItemFSSizeKey) as? Int64 else { return -1 }
|
||||
if (downloaded == 0 || downloaded == 100) && (uploaded > 0 && uploaded < 100) {
|
||||
return Int64(uploaded * (Double(size) / 100))
|
||||
} else if (uploaded == 0 || uploaded == 100) && (downloaded > 0 && downloaded < 100) {
|
||||
return Int64(downloaded * (Double(size) / 100))
|
||||
} else if uploaded == 100 || downloaded == 100 {
|
||||
return size
|
||||
let group = DispatchGroup()
|
||||
group.enter()
|
||||
var finishObserver: NSObjectProtocol?
|
||||
finishObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
|
||||
defer {
|
||||
query.stop()
|
||||
group.leave()
|
||||
NotificationCenter.default.removeObserver(finishObserver!)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
open var totalBytes: Int64 {
|
||||
assert(!Thread.isMainThread, "Don't run \(#function) method on main thread")
|
||||
guard let url = destURL ?? sourceURL, let item = CloudOperationHandle.getMetadataItem(url: url) else { return -1 }
|
||||
return item.value(forAttribute: NSMetadataItemFSSizeKey) as? Int64 ?? -1
|
||||
}
|
||||
|
||||
open var inProgress: Bool {
|
||||
guard let url = destURL ?? sourceURL, let item = CloudOperationHandle.getMetadataItem(url: url) else { return false }
|
||||
let downloadStatus = item.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String ?? NSMetadataUbiquitousItemDownloadingStatusNotDownloaded
|
||||
let isUploading = item.value(forAttribute: NSMetadataUbiquitousItemIsUploadingKey) as? Bool ?? false
|
||||
return downloadStatus == NSMetadataUbiquitousItemDownloadingStatusCurrent || isUploading
|
||||
}
|
||||
|
||||
/// Not usable in local provider
|
||||
open func cancel() -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
fileprivate static func getMetadataItem(url: URL) -> NSMetadataItem? {
|
||||
let query = NSMetadataQuery()
|
||||
query.predicate = NSPredicate(format: "(%K LIKE %@)", NSMetadataItemPathKey, url.path)
|
||||
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope, NSMetadataQueryUbiquitousDataScope]
|
||||
|
||||
var item: NSMetadataItem?
|
||||
|
||||
let group = DispatchGroup()
|
||||
group.enter()
|
||||
var finishObserver: NSObjectProtocol?
|
||||
finishObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
|
||||
defer {
|
||||
query.stop()
|
||||
group.leave()
|
||||
NotificationCenter.default.removeObserver(finishObserver!)
|
||||
}
|
||||
|
||||
if query.resultCount > 0 {
|
||||
item = query.result(at: 0) as? NSMetadataItem
|
||||
}
|
||||
|
||||
query.disableUpdates()
|
||||
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
query.start()
|
||||
if query.resultCount > 0 {
|
||||
item = query.result(at: 0) as? NSMetadataItem
|
||||
}
|
||||
_ = group.wait(timeout: .now() + 30)
|
||||
return item
|
||||
|
||||
query.disableUpdates()
|
||||
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
query.start()
|
||||
}
|
||||
_ = group.wait(timeout: .now() + 30)
|
||||
return item
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -44,7 +44,7 @@ open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
public var validatingCache: Bool
|
||||
|
||||
fileprivate var _session: URLSession?
|
||||
fileprivate var sessionDelegate: SessionDelegate?
|
||||
internal fileprivate(set) var sessionDelegate: SessionDelegate?
|
||||
public var session: URLSession {
|
||||
get {
|
||||
if _session == nil {
|
||||
@@ -153,7 +153,8 @@ open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
list(path) { (contents, cursor, error) in
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
list(path, progress: progress) { (contents, cursor, error) in
|
||||
completionHandler(contents, error)
|
||||
}
|
||||
}
|
||||
@@ -198,12 +199,13 @@ open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
var foundFiles = [DropboxFileObject]()
|
||||
if let queryStr = query.findValue(forKey: "name", operator: .beginsWith) as? String {
|
||||
// Dropbox only support searching for file names begin with query in non-enterprise accounts.
|
||||
// We will use it if there is a `name BEGINSWITH[c] "query"` in predicate, then filter to form final result.
|
||||
search(path, query: queryStr, foundItem: { (file) in
|
||||
search(path, query: queryStr, progress: progress, foundItem: { (file) in
|
||||
if query.evaluate(with: file.mapPredicate()) {
|
||||
foundFiles.append(file)
|
||||
foundItemHandler?(file)
|
||||
@@ -214,7 +216,7 @@ open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
} else {
|
||||
// Dropbox doesn't support searching attributes natively. The workaround is to fallback to listing all files
|
||||
// and filter it locally. It may have a network burden in case there is many files in Dropbox, so please use it concisely.
|
||||
list(path, recursive: true, progressHandler: { (files, _, error) in
|
||||
list(path, recursive: true, progress: progress, progressHandler: { (files, _, error) in
|
||||
for file in files where query.evaluate(with: file.mapPredicate()) {
|
||||
foundItemHandler?(file)
|
||||
}
|
||||
@@ -223,6 +225,7 @@ open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
completionHandler(predicatedFiles, error)
|
||||
})
|
||||
}
|
||||
return progress
|
||||
}
|
||||
|
||||
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
|
||||
@@ -235,27 +238,33 @@ open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
extension DropboxFileProvider: FileProviderOperations {
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
|
||||
return doOperation(.create(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return doOperation(.remove(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
fileprivate func doOperation(_ operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
fileprivate func doOperation(_ operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let progress = Progress(totalUnitCount: 1)
|
||||
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
let url: String
|
||||
guard let sourcePath = operation.source else { return nil }
|
||||
let destPath = operation.destination
|
||||
@@ -288,15 +297,24 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
if let response = response as? HTTPURLResponse, response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
|
||||
serverError = FileProviderDropboxError(code: code, path: sourcePath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
if serverError == nil && error == nil {
|
||||
progress.completedUnitCount = 1
|
||||
} else {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler?(serverError ?? error)
|
||||
self.delegateNotify(operation, error: serverError ?? error)
|
||||
})
|
||||
task.taskDescription = operation.json
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
return progress
|
||||
}
|
||||
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
// check file is not a folder
|
||||
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
|
||||
dispatch_queue.async {
|
||||
@@ -312,22 +330,36 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
return upload_simple(toPath, localFile: localFile, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.copy(source: path, destination: destURL.absoluteString)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let url = URL(string: "files/download", relativeTo: contentURL)!
|
||||
|
||||
var progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
request.set(dropboxArgKey: ["path": path as NSString])
|
||||
let task = session.downloadTask(with: request)
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = completionHandler
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler?(error)
|
||||
}
|
||||
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
|
||||
guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: (task.response as? HTTPURLResponse)?.statusCode ?? -1)
|
||||
let errorData : Data? = nil //Data(contentsOf:cacheURL) // TODO: Figure out how to get error response data for the error description
|
||||
let serverError : FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil
|
||||
if serverError != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler?(serverError)
|
||||
return
|
||||
}
|
||||
@@ -339,13 +371,19 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
}
|
||||
}
|
||||
task.taskDescription = opType.json
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
return progress
|
||||
}
|
||||
}
|
||||
|
||||
extension DropboxFileProvider: FileProviderReadWrite {
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
|
||||
if length == 0 || offset < 0 {
|
||||
dispatch_queue.async {
|
||||
completionHandler(Data(), nil)
|
||||
@@ -355,19 +393,31 @@ extension DropboxFileProvider: FileProviderReadWrite {
|
||||
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
let url = URL(string: "files/download", relativeTo: contentURL)!
|
||||
|
||||
var progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
request.set(rangeWithOffset: offset, length: length)
|
||||
request.set(dropboxArgKey: ["path": correctPath(path)! as NSString])
|
||||
let task = session.downloadTask(with: request)
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler(nil, error)
|
||||
}
|
||||
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
|
||||
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
|
||||
guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: (task.response as? HTTPURLResponse)?.statusCode ?? -1)
|
||||
let errorData : Data? = nil //Data(contentsOf:cacheURL) // TODO: Figure out how to get error response data for the error description
|
||||
let serverError : FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil
|
||||
if serverError != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler(nil, serverError)
|
||||
return
|
||||
}
|
||||
@@ -379,11 +429,17 @@ extension DropboxFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
}
|
||||
task.taskDescription = opType.json
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
return progress
|
||||
}
|
||||
|
||||
public func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
|
||||
+41
-10
@@ -71,7 +71,11 @@ public final class DropboxFileObject: FileObject {
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
internal extension DropboxFileProvider {
|
||||
func list(_ path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, session: URLSession? = nil, progressHandler: ((_ contents: [FileObject], _ nextCursor: String?, _ error: Error?) -> Void)? = nil, completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
|
||||
|
||||
|
||||
func list(_ path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, session: URLSession? = nil, progress: Progress, progressHandler: ((_ contents: [FileObject], _ nextCursor: String?, _ error: Error?) -> Void)? = nil, completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
|
||||
if progress.isCancelled { return }
|
||||
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: URL
|
||||
if let cursor = cursor {
|
||||
@@ -99,13 +103,14 @@ internal extension DropboxFileProvider {
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = DropboxFileObject(json: entry) {
|
||||
files.append(file)
|
||||
progress.totalUnitCount = Int64(files.count)
|
||||
}
|
||||
}
|
||||
let ncursor = json["cursor"] as? String
|
||||
let hasmore = (json["has_more"] as? NSNumber)?.boolValue ?? false
|
||||
if hasmore {
|
||||
if hasmore && !progress.isCancelled {
|
||||
progressHandler?(files, ncursor, responseError ?? error)
|
||||
self.list(path, cursor: ncursor, prevContents: prevContents + files, completionHandler: completionHandler)
|
||||
self.list(path, cursor: ncursor, prevContents: prevContents + files, progress: progress, completionHandler: completionHandler)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -113,11 +118,15 @@ internal extension DropboxFileProvider {
|
||||
progressHandler?(files, nil, responseError ?? error)
|
||||
completionHandler(prevContents + files, nil, responseError ?? error)
|
||||
})
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.taskDescription = FileOperationType.fetch(path: path).json
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, data: Data? = nil, localFile: URL? = nil, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
func upload_simple(_ targetPath: String, data: Data? = nil, localFile: URL? = nil, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let size = data?.count ?? Int((try? localFile?.resourceValues(forKeys: [.fileSizeKey]))??.fileSize ?? -1)
|
||||
if size > 150 * 1024 * 1024 {
|
||||
let error = FileProviderDropboxError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
@@ -125,6 +134,13 @@ internal extension DropboxFileProvider {
|
||||
self.delegateNotify(.create(path: targetPath), error: error)
|
||||
return nil
|
||||
}
|
||||
|
||||
var progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
progress.totalUnitCount = Int64(size)
|
||||
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: URL
|
||||
url = URL(string: "files/upload", relativeTo: contentURL)!
|
||||
@@ -151,15 +167,25 @@ internal extension DropboxFileProvider {
|
||||
// We can't fetch server result from delegate!
|
||||
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: nil)
|
||||
}
|
||||
if !(responseError == nil && error == nil) {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self?.delegateNotify(.create(path: targetPath), error: responseError ?? error)
|
||||
self?.delegateNotify(.modify(path: targetPath), error: responseError ?? error)
|
||||
}
|
||||
task.taskDescription = operation.json
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: &progress)
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
return progress
|
||||
}
|
||||
|
||||
func search(_ startPath: String = "", query: String, start: Int = 0, maxResultPerPage: Int = 25, maxResults: Int = -1, foundItem:@escaping ((_ file: DropboxFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
|
||||
func search(_ startPath: String = "", query: String, start: Int = 0, maxResultPerPage: Int = 25, maxResults: Int = -1, progress: Progress, foundItem:@escaping ((_ file: DropboxFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
|
||||
if progress.isCancelled { return }
|
||||
|
||||
let url = URL(string: "files/search", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
@@ -180,12 +206,13 @@ internal extension DropboxFileProvider {
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = DropboxFileObject(json: entry) {
|
||||
foundItem(file)
|
||||
progress.completedUnitCount += 1
|
||||
}
|
||||
}
|
||||
let rstart = json["start"] as? Int
|
||||
let hasmore = (json["more"] as? NSNumber)?.boolValue ?? false
|
||||
if hasmore, let rstart = rstart {
|
||||
self.search(startPath, query: query, start: rstart + entries.count, maxResultPerPage: maxResultPerPage, foundItem: foundItem, completionHandler: completionHandler)
|
||||
if hasmore && !progress.isCancelled, let rstart = rstart {
|
||||
self.search(startPath, query: query, start: rstart + entries.count, maxResultPerPage: maxResultPerPage, progress: progress, foundItem: foundItem, completionHandler: completionHandler)
|
||||
} else {
|
||||
completionHandler(responseError ?? error)
|
||||
}
|
||||
@@ -193,7 +220,11 @@ internal extension DropboxFileProvider {
|
||||
}
|
||||
}
|
||||
completionHandler(responseError ?? error)
|
||||
})
|
||||
})
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,26 +331,27 @@ public struct LocalFileInformationGenerator {
|
||||
return newKey.capitalized
|
||||
}
|
||||
|
||||
if FileManager.default.fileExists(atPath: fileURL.path) {
|
||||
let playerItem = AVPlayerItem(url: fileURL)
|
||||
let metadataList = playerItem.asset.commonMetadata
|
||||
for item in metadataList {
|
||||
#if swift(>=4.0)
|
||||
guard FileManager.default.fileExists(atPath: fileURL.path) else {
|
||||
return (dic, keys)
|
||||
}
|
||||
let playerItem = AVPlayerItem(url: fileURL)
|
||||
let metadataList = playerItem.asset.commonMetadata
|
||||
for item in metadataList {
|
||||
#if swift(>=4.0)
|
||||
let commonKey = item.commonKey?.rawValue
|
||||
#else
|
||||
#else
|
||||
let commonKey = item.commonKey
|
||||
#endif
|
||||
if let description = makeDescription(commonKey) {
|
||||
if let value = item.stringValue {
|
||||
keys.append(description)
|
||||
dic[description] = value
|
||||
}
|
||||
#endif
|
||||
if let description = makeDescription(commonKey) {
|
||||
if let value = item.stringValue {
|
||||
keys.append(description)
|
||||
dic[description] = value
|
||||
}
|
||||
}
|
||||
if let ap = try? AVAudioPlayer(contentsOf: fileURL) {
|
||||
add(key: "Duration", value: ap.duration.formatshort)
|
||||
add(key: "Bitrate", value: ap.settings[AVSampleRateKey] as? Int)
|
||||
}
|
||||
}
|
||||
if let ap = try? AVAudioPlayer(contentsOf: fileURL) {
|
||||
add(key: "Duration", value: ap.duration.formatshort)
|
||||
add(key: "Bitrate", value: ap.settings[AVSampleRateKey] as? Int)
|
||||
}
|
||||
return (dic, keys)
|
||||
}
|
||||
@@ -421,21 +422,40 @@ public struct LocalFileInformationGenerator {
|
||||
}
|
||||
|
||||
func getKey(_ key: String, from dict: CGPDFDictionaryRef) -> String? {
|
||||
var cfValue: CGPDFStringRef? = nil
|
||||
if (CGPDFDictionaryGetString(dict, key, &cfValue)), let value = CGPDFStringCopyTextString(cfValue!) {
|
||||
var cfStrValue: CGPDFStringRef?
|
||||
if (CGPDFDictionaryGetString(dict, key, &cfStrValue)), let value = cfStrValue.flatMap({ CGPDFStringCopyTextString($0) }) {
|
||||
return value as String
|
||||
}
|
||||
var cfArrayValue: CGPDFArrayRef?
|
||||
if (CGPDFDictionaryGetArray(dict, key, &cfArrayValue)), let cfArray = cfArrayValue {
|
||||
var array = [String]()
|
||||
for i in 0..<CGPDFArrayGetCount(cfArray) {
|
||||
var cfItemValue: CGPDFStringRef?
|
||||
if CGPDFArrayGetString(cfArray, i, &cfItemValue), let item = cfItemValue.flatMap({ CGPDFStringCopyTextString($0) }) {
|
||||
array.append(item as String)
|
||||
}
|
||||
}
|
||||
return array.joined(separator: ", ")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertDate(_ date: String?) -> Date? {
|
||||
guard let date = date else { return nil }
|
||||
var dateStr = date
|
||||
var dateStr = date.replacingOccurrences(of: "'", with: "")
|
||||
if dateStr.hasPrefix("D:") {
|
||||
dateStr.characters.removeFirst(2)
|
||||
}
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyyMMddHHmmssTZD"
|
||||
dateFormatter.dateFormat = "yyyyMMddHHmmssTZ"
|
||||
if let result = dateFormatter.date(from: dateStr) {
|
||||
return result
|
||||
}
|
||||
dateFormatter.dateFormat = "yyyyMMddHHmmssZZZZZ"
|
||||
if let result = dateFormatter.date(from: dateStr) {
|
||||
return result
|
||||
}
|
||||
dateFormatter.dateFormat = "yyyyMMddHHmmssZ"
|
||||
if let result = dateFormatter.date(from: dateStr) {
|
||||
return result
|
||||
}
|
||||
@@ -446,29 +466,32 @@ public struct LocalFileInformationGenerator {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let provider = CGDataProvider(url: fileURL as CFURL), let reference = CGPDFDocument(provider), let dict = reference.info {
|
||||
add(key: "Title", value: getKey("Title", from: dict))
|
||||
add(key: "Author", value: getKey("Author", from: dict))
|
||||
add(key: "Subject", value: getKey("Subject", from: dict))
|
||||
var majorVersion: Int32 = 0
|
||||
var minorVersion: Int32 = 0
|
||||
reference.getVersion(majorVersion: &majorVersion, minorVersion: &minorVersion)
|
||||
if majorVersion > 0 {
|
||||
add(key: "Version", value: String(majorVersion) + "." + String(minorVersion))
|
||||
}
|
||||
add(key: "Pages", value: reference.numberOfPages)
|
||||
|
||||
if reference.numberOfPages > 0, let pageRef = reference.page(at: 1) {
|
||||
let size = pageRef.getBoxRect(CGPDFBox.mediaBox).size
|
||||
add(key: "Resolution", value: "\(Int(size.width))x\(Int(size.height))")
|
||||
}
|
||||
add(key: "Content creator", value: getKey("Creator", from: dict))
|
||||
add(key: "Creation date", value: convertDate(getKey("CreationDate", from: dict)))
|
||||
add(key: "Modified date", value: convertDate(getKey("ModDate", from: dict)))
|
||||
add(key: "Security", value: reference.isEncrypted ? "Present" : "None")
|
||||
add(key: "Allows printing", value: reference.allowsPrinting ? "Yes" : "No")
|
||||
add(key: "Allows copying", value: reference.allowsCopying ? "Yes" : "No")
|
||||
guard let provider = CGDataProvider(url: fileURL as CFURL), let reference = CGPDFDocument(provider), let dict = reference.info else {
|
||||
return (dic, keys)
|
||||
}
|
||||
add(key: "Title", value: getKey("Title", from: dict))
|
||||
add(key: "Author", value: getKey("Author", from: dict))
|
||||
add(key: "Subject", value: getKey("Subject", from: dict))
|
||||
add(key: "Producer", value: getKey("Producer", from: dict))
|
||||
add(key: "Keywords", value: getKey("Keywords", from: dict))
|
||||
var majorVersion: Int32 = 0
|
||||
var minorVersion: Int32 = 0
|
||||
reference.getVersion(majorVersion: &majorVersion, minorVersion: &minorVersion)
|
||||
if majorVersion > 0 {
|
||||
add(key: "Version", value: String(majorVersion) + "." + String(minorVersion))
|
||||
}
|
||||
add(key: "Pages", value: reference.numberOfPages)
|
||||
|
||||
if reference.numberOfPages > 0, let pageRef = reference.page(at: 1) {
|
||||
let size = pageRef.getBoxRect(CGPDFBox.mediaBox).size
|
||||
add(key: "Resolution", value: "\(Int(size.width))x\(Int(size.height))")
|
||||
}
|
||||
add(key: "Content creator", value: getKey("Creator", from: dict))
|
||||
add(key: "Creation date", value: convertDate(getKey("CreationDate", from: dict)))
|
||||
add(key: "Modified date", value: convertDate(getKey("ModDate", from: dict)))
|
||||
add(key: "Security", value: reference.isEncrypted)
|
||||
add(key: "Allows printing", value: reference.allowsPrinting)
|
||||
add(key: "Allows copying", value: reference.allowsCopying)
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
|
||||
fileprivate var _taskDescription: String?
|
||||
|
||||
/// Force using `URLSessionStreamTask` for iOS 9 and later
|
||||
public var useURLSession = true
|
||||
public let useURLSession: Bool
|
||||
@available(iOS 9.0, OSX 10.11, *)
|
||||
fileprivate static var streamTasks = [Int: URLSessionStreamTask]()
|
||||
|
||||
@@ -121,8 +121,31 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var _countOfBytesSent: Int64 = 0
|
||||
fileprivate var _countOfBytesRecieved: Int64 = 0
|
||||
fileprivate var _countOfBytesSent: Int64 = 0 {
|
||||
willSet {
|
||||
for observer in observers where observer.keyPath == "countOfBytesSent" {
|
||||
observer.observer.observeValue(forKeyPath: observer.keyPath, of: self, change: [.oldKey: _countOfBytesSent, .oldKey: newValue], context: observer.context)
|
||||
}
|
||||
}
|
||||
didSet {
|
||||
for observer in observers where observer.keyPath == "countOfBytesSent" {
|
||||
observer.observer.observeValue(forKeyPath: observer.keyPath, of: self, change: [.oldKey: oldValue, .oldKey: _countOfBytesSent], context: observer.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var _countOfBytesRecieved: Int64 = 0 {
|
||||
willSet {
|
||||
for observer in observers where observer.keyPath == "countOfBytesRecieved" {
|
||||
observer.observer.observeValue(forKeyPath: observer.keyPath, of: self, change: [.oldKey: _countOfBytesRecieved, .oldKey: newValue], context: observer.context)
|
||||
}
|
||||
}
|
||||
didSet {
|
||||
for observer in observers where observer.keyPath == "countOfBytesRecieved" {
|
||||
observer.observer.observeValue(forKeyPath: observer.keyPath, of: self, change: [.oldKey: oldValue, .oldKey: _countOfBytesRecieved], context: observer.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of bytes that the task has sent to the server in the request body.
|
||||
@@ -133,7 +156,7 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
|
||||
* `urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)` delegate method.
|
||||
*/
|
||||
override open var countOfBytesSent: Int64 {
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
if #available(iOS 9.0, macOS 10.11, *) {
|
||||
if self.useURLSession {
|
||||
return _underlyingTask!.countOfBytesSent
|
||||
}
|
||||
@@ -192,6 +215,49 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
|
||||
return Int64(dataReceived.count)
|
||||
}
|
||||
|
||||
var observers: [(keyPath: String, observer: NSObject, context: UnsafeMutableRawPointer?)] = []
|
||||
|
||||
public override func addObserver(_ observer: NSObject, forKeyPath keyPath: String, options: NSKeyValueObservingOptions = [], context: UnsafeMutableRawPointer?) {
|
||||
if #available(iOS 9.0, macOS 10.11, *) {
|
||||
if self.useURLSession {
|
||||
self._underlyingTask?.addObserver(observer, forKeyPath: keyPath, options: options, context: context)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch keyPath {
|
||||
case #keyPath(countOfBytesSent):
|
||||
fallthrough
|
||||
case #keyPath(countOfBytesReceived):
|
||||
fallthrough
|
||||
case #keyPath(countOfBytesExpectedToSend):
|
||||
fallthrough
|
||||
case #keyPath(countOfBytesExpectedToReceive):
|
||||
observers.append((keyPath: keyPath, observer: observer, context: context))
|
||||
default:
|
||||
break
|
||||
}
|
||||
super.addObserver(observer, forKeyPath: keyPath, options: options, context: context)
|
||||
}
|
||||
|
||||
public override func removeObserver(_ observer: NSObject, forKeyPath keyPath: String) {
|
||||
var newObservers: [(keyPath: String, observer: NSObject, context: UnsafeMutableRawPointer?)] = []
|
||||
for observer in observers where observer.keyPath != keyPath {
|
||||
newObservers.append(observer)
|
||||
}
|
||||
self.observers = newObservers
|
||||
super.removeObserver(observer, forKeyPath: keyPath)
|
||||
}
|
||||
|
||||
public override func removeObserver(_ observer: NSObject, forKeyPath keyPath: String, context: UnsafeMutableRawPointer?) {
|
||||
var newObservers: [(keyPath: String, observer: NSObject, context: UnsafeMutableRawPointer?)] = []
|
||||
for observer in observers where observer.keyPath != keyPath || observer.context != context {
|
||||
newObservers.append(observer)
|
||||
}
|
||||
self.observers = newObservers
|
||||
super.removeObserver(observer, forKeyPath: keyPath, context: context)
|
||||
}
|
||||
|
||||
override public init() {
|
||||
fatalError("Use NSURLSession.fpstreamTask() method")
|
||||
}
|
||||
@@ -199,10 +265,11 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
|
||||
fileprivate var host: (hostname: String, port: Int)?
|
||||
fileprivate var service: NetService?
|
||||
|
||||
internal init(session: URLSession, host: String, port: Int) {
|
||||
internal init(session: URLSession, host: String, port: Int, useURLSession: Bool = true) {
|
||||
self._underlyingSession = session
|
||||
self.useURLSession = useURLSession
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
if self.useURLSession {
|
||||
if useURLSession {
|
||||
let task = session.streamTask(withHostName: host, port: port)
|
||||
self._taskIdentifier = task.taskIdentifier
|
||||
FileProviderStreamTask.streamTasks[_taskIdentifier] = task
|
||||
@@ -218,10 +285,11 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
|
||||
self.operation_queue.maxConcurrentOperationCount = 1
|
||||
}
|
||||
|
||||
internal init(session: URLSession, netService: NetService) {
|
||||
internal init(session: URLSession, netService: NetService, useURLSession: Bool = true) {
|
||||
self._underlyingSession = session
|
||||
self.useURLSession = useURLSession
|
||||
if #available(iOS 9.0, OSX 10.11, *) {
|
||||
if self.useURLSession {
|
||||
if useURLSession {
|
||||
let task = session.streamTask(with: netService)
|
||||
self._taskIdentifier = task.taskIdentifier
|
||||
FileProviderStreamTask.streamTasks[_taskIdentifier] = task
|
||||
|
||||
+148
-63
@@ -82,7 +82,8 @@ open class FTPFileProvider: FileProviderBasicRemote {
|
||||
guard (baseURL.scheme ?? "ftp").lowercased().hasPrefix("ftp") else { return nil }
|
||||
guard baseURL.host != nil else { return nil }
|
||||
var urlComponents = URLComponents(url: baseURL, resolvingAgainstBaseURL: true)!
|
||||
urlComponents.port = urlComponents.port ?? 21
|
||||
let defaultPort: Int = baseURL.scheme == "ftps" ? 990 : 21
|
||||
urlComponents.port = urlComponents.port ?? defaultPort
|
||||
urlComponents.scheme = urlComponents.scheme ?? "ftp"
|
||||
|
||||
self.baseURL = (urlComponents.url!.path.hasSuffix("/") ? urlComponents.url! : urlComponents.url!.appendingPathComponent("")).absoluteURL
|
||||
@@ -278,12 +279,14 @@ open class FTPFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
}
|
||||
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
self.recursiveList(path: path, useMLST: true, foundItemsHandler: { items in
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
_ = self.recursiveList(path: path, useMLST: true, foundItemsHandler: { items in
|
||||
if let foundItemHandler = foundItemHandler {
|
||||
for item in items where query.evaluate(with: item.mapPredicate()) {
|
||||
foundItemHandler(item)
|
||||
}
|
||||
progress.totalUnitCount = Int64(items.count)
|
||||
}
|
||||
}, completionHandler: {files, error in
|
||||
if let error = error {
|
||||
@@ -294,6 +297,7 @@ open class FTPFileProvider: FileProviderBasicRemote {
|
||||
let foundFiles = files.filter { query.evaluate(with: $0.mapPredicate()) }
|
||||
completionHandler(foundFiles, nil)
|
||||
})
|
||||
return progress
|
||||
}
|
||||
|
||||
public func url(of path: String?) -> URL {
|
||||
@@ -329,24 +333,24 @@ open class FTPFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
extension FTPFileProvider: FileProviderOperations {
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
|
||||
return doOperation(.create(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return doOperation(.remove(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
fileprivate func doOperation(_ opType: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
fileprivate func doOperation(_ opType: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
@@ -368,7 +372,10 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
default: // modify, fetch
|
||||
return nil
|
||||
}
|
||||
let operationHandle = RemoteOperationHandle(operationType: opType, tasks: [])
|
||||
let progress = Progress(totalUnitCount: 1)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
|
||||
self.ftpLogin(task) { (error) in
|
||||
@@ -411,13 +418,12 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
case .modify:
|
||||
errorCode = URLError.cannotWriteToFile
|
||||
case .copy:
|
||||
let opHandle = self.fallbackCopy(opType, completionHandler: completionHandler) as? RemoteOperationHandle
|
||||
operationHandle.tasks = opHandle?.tasks ?? []
|
||||
self.fallbackCopy(opType, progress: progress, completionHandler: completionHandler)
|
||||
return
|
||||
case .move:
|
||||
errorCode = URLError.cannotMoveFile
|
||||
case .remove:
|
||||
self.fallbackRemove(opType, on: task, completionHandler: completionHandler)
|
||||
self.fallbackRemove(opType, progress: progress, on: task, completionHandler: completionHandler)
|
||||
return
|
||||
case .link:
|
||||
errorCode = URLError.cannotWriteToFile
|
||||
@@ -425,31 +431,37 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
errorCode = URLError.cannotOpenFile
|
||||
}
|
||||
let error = self.throwError(sourcePath, code: errorCode)
|
||||
progress.cancel()
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(opType, error: error)
|
||||
}
|
||||
self.delegateNotify(opType, error: error)
|
||||
return
|
||||
}
|
||||
|
||||
progress.completedUnitCount = progress.totalUnitCount
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(nil)
|
||||
self.delegateNotify(opType, error: nil)
|
||||
}
|
||||
self.delegateNotify(opType, error: nil)
|
||||
})
|
||||
}
|
||||
|
||||
operationHandle.add(task: task)
|
||||
return operationHandle
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
return progress
|
||||
}
|
||||
|
||||
private func fallbackCopy(_ opType: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
guard let sourcePath = opType.source else { return nil }
|
||||
guard let destPath = opType.destination else { return nil }
|
||||
private func fallbackCopy(_ opType: FileOperationType, progress: Progress, completionHandler: SimpleCompletionHandler) {
|
||||
guard let sourcePath = opType.source else { return }
|
||||
guard let destPath = opType.destination else { return }
|
||||
|
||||
let localURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).appendingPathExtension("tmp")
|
||||
let operationHandle = RemoteOperationHandle(operationType: opType, tasks: [])
|
||||
let firstOp = self.copyItem(path: sourcePath, toLocalURL: localURL, completionHandler: { (error) in
|
||||
|
||||
progress.becomeCurrent(withPendingUnitCount: 1)
|
||||
_ = self.copyItem(path: sourcePath, toLocalURL: localURL) { (error) in
|
||||
if let error = error {
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
@@ -458,39 +470,42 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
return
|
||||
}
|
||||
|
||||
let secondOp = self.copyItem(localFile: localURL, to: destPath, completionHandler: { error in
|
||||
progress.becomeCurrent(withPendingUnitCount: 1)
|
||||
_ = self.copyItem(localFile: localURL, to: destPath) { error in
|
||||
completionHandler?(nil)
|
||||
self.delegateNotify(opType, error: nil)
|
||||
}) as? RemoteOperationHandle
|
||||
operationHandle.tasks = secondOp?.tasks ?? []
|
||||
}) as? RemoteOperationHandle
|
||||
operationHandle.tasks = firstOp?.tasks ?? []
|
||||
return operationHandle
|
||||
}
|
||||
progress.resignCurrent()
|
||||
}
|
||||
progress.resignCurrent()
|
||||
return
|
||||
}
|
||||
|
||||
private func fallbackRemove(_ opType: FileOperationType, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
|
||||
private func fallbackRemove(_ opType: FileOperationType, progress: Progress, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
|
||||
guard let sourcePath = opType.source else { return }
|
||||
|
||||
self.execute(command: "SITE RMDIR \(ftpPath(sourcePath))", on: task) { (response, error) in
|
||||
if let error = error {
|
||||
progress.cancel()
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(opType, error: error)
|
||||
}
|
||||
self.delegateNotify(opType, error: error)
|
||||
return
|
||||
}
|
||||
|
||||
guard let response = response else {
|
||||
progress.cancel()
|
||||
let error = self.throwError(sourcePath, code: URLError.badServerResponse)
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(opType, error: error)
|
||||
}
|
||||
self.delegateNotify(opType, error: error)
|
||||
return
|
||||
}
|
||||
|
||||
if response.hasPrefix("50") {
|
||||
self.fallbackRecursiveRemove(opType, on: task, completionHandler: completionHandler)
|
||||
self.fallbackRecursiveRemove(opType, progress: progress, on: task, completionHandler: completionHandler)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -500,15 +515,15 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
}
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(opType, error: error)
|
||||
}
|
||||
self.delegateNotify(opType, error: error)
|
||||
}
|
||||
}
|
||||
|
||||
private func fallbackRecursiveRemove(_ opType: FileOperationType, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
|
||||
private func fallbackRecursiveRemove(_ opType: FileOperationType, progress: Progress, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
|
||||
guard let sourcePath = opType.source else { return }
|
||||
|
||||
self.recursiveList(path: sourcePath, useMLST: true, completionHandler: { (contents, error) in
|
||||
_ = self.recursiveList(path: sourcePath, useMLST: true, completionHandler: { (contents, error) in
|
||||
if let error = error {
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
@@ -517,6 +532,8 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
return
|
||||
}
|
||||
|
||||
let recursiveProgress = Progress(parent: progress, userInfo: nil)
|
||||
recursiveProgress.totalUnitCount = Int64(contents.count)
|
||||
let sortedContents = contents.sorted(by: {
|
||||
$0.path.localizedStandardCompare($1.path) == .orderedDescending
|
||||
})
|
||||
@@ -527,6 +544,7 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
command += "RMD \(self.ftpPath(sourcePath))"
|
||||
|
||||
self.execute(command: command, on: task, completionHandler: { (response, error) in
|
||||
recursiveProgress.completedUnitCount += 1
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(opType, error: error)
|
||||
@@ -536,7 +554,7 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
})
|
||||
}
|
||||
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
// check file is not a folder
|
||||
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
|
||||
dispatch_queue.async {
|
||||
@@ -549,7 +567,11 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let operation = RemoteOperationHandle(operationType: opType, tasks: [])
|
||||
|
||||
let progress = Progress(totalUnitCount: 0)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
|
||||
self.ftpLogin(task) { (error) in
|
||||
@@ -561,13 +583,21 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
return
|
||||
}
|
||||
|
||||
self.ftpStore(task, filePath: self.ftpPath(toPath), fromData: nil, fromFile: localFile, onTask: {
|
||||
operation.add(task: $0)
|
||||
self.ftpStore(task, filePath: self.ftpPath(toPath), fromData: nil, fromFile: localFile, onTask: { task in
|
||||
weak var weakTask = task
|
||||
progress.cancellationHandler = {
|
||||
weakTask?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
}, onProgress: { bytesSent, totalSent, expectedBytes in
|
||||
progress.completedUnitCount = totalSent
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(Double(totalSent) / Double(expectedBytes)))
|
||||
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(progress.fractionCompleted))
|
||||
}
|
||||
}, completionHandler: { (error) in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
self.ftpQuit(task)
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
@@ -576,15 +606,18 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
})
|
||||
}
|
||||
|
||||
return operation
|
||||
return progress
|
||||
}
|
||||
|
||||
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.copy(source: path, destination: destURL.absoluteString)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let operation = RemoteOperationHandle(operationType: opType, tasks: [])
|
||||
var progress = Progress(totalUnitCount: 0)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
if self.useAppleImplementation {
|
||||
self.attributesOfItem(path: path, completionHandler: { (file, error) in
|
||||
@@ -605,6 +638,8 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
return
|
||||
}
|
||||
|
||||
progress.totalUnitCount = file?.size ?? 0
|
||||
|
||||
let task = self.session.downloadTask(with: self.url(of: path))
|
||||
completionHandlersForTasks[self.session.sessionDescription!]?[task.taskIdentifier] = completionHandler
|
||||
downloadCompletionHandlersForTasks[self.session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
|
||||
@@ -615,8 +650,13 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
completionHandler?(e)
|
||||
}
|
||||
}
|
||||
operation.add(task: task)
|
||||
task.taskDescription = opType.json
|
||||
task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
|
||||
task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
})
|
||||
} else {
|
||||
@@ -629,13 +669,21 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
return
|
||||
}
|
||||
|
||||
self.ftpRetrieveFile(task, filePath: self.ftpPath(path), onTask: {
|
||||
operation.add(task: $0)
|
||||
self.ftpRetrieveFile(task, filePath: self.ftpPath(path), onTask: { task in
|
||||
weak var weakTask = task
|
||||
progress.cancellationHandler = {
|
||||
weakTask?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
}, onProgress: { recevied, totalReceived, totalSize in
|
||||
let progress = Double(totalReceived) / Double(totalSize)
|
||||
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(progress))
|
||||
progress.totalUnitCount = totalSize
|
||||
progress.completedUnitCount = totalReceived
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(progress.fractionCompleted))
|
||||
}
|
||||
}) { (tmpurl, error) in
|
||||
if let error = error {
|
||||
progress.cancel()
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(opType, error: error)
|
||||
@@ -653,20 +701,28 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
}
|
||||
}
|
||||
}
|
||||
return operation
|
||||
return progress
|
||||
}
|
||||
}
|
||||
|
||||
extension FTPFileProvider: FileProviderReadWrite {
|
||||
open func contents(path: String, completionHandler: @escaping ((Data?, Error?) -> Void)) -> OperationHandle? {
|
||||
open func contents(path: String, completionHandler: @escaping ((Data?, Error?) -> Void)) -> Progress? {
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.useAppleImplementation {
|
||||
var progress = Progress(totalUnitCount: 0)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
let task = session.downloadTask(with: url(of: path))
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler(nil, error)
|
||||
}
|
||||
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
|
||||
@@ -678,14 +734,20 @@ extension FTPFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
}
|
||||
task.taskDescription = opType.json
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
return progress
|
||||
} else {
|
||||
return self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
if length == 0 || offset < 0 {
|
||||
dispatch_queue.async {
|
||||
@@ -694,7 +756,10 @@ extension FTPFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
let operation = RemoteOperationHandle(operationType: opType, tasks: [])
|
||||
let progress = Progress(totalUnitCount: 0)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
|
||||
self.ftpLogin(task) { (error) in
|
||||
@@ -705,13 +770,21 @@ extension FTPFileProvider: FileProviderReadWrite {
|
||||
return
|
||||
}
|
||||
|
||||
self.ftpRetrieveData(task, filePath: self.ftpPath(path), from: offset, length: length, onTask: {
|
||||
operation.add(task: $0)
|
||||
self.ftpRetrieveData(task, filePath: self.ftpPath(path), from: offset, length: length, onTask: { task in
|
||||
weak var weakTask = task
|
||||
progress.cancellationHandler = {
|
||||
weakTask?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
}, onProgress: { recevied, totalReceived, totalSize in
|
||||
let progress = Double(totalReceived) / Double(totalSize)
|
||||
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(progress))
|
||||
progress.totalUnitCount = totalSize
|
||||
progress.completedUnitCount = totalReceived
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(progress.fractionCompleted))
|
||||
}
|
||||
}) { (data, error) in
|
||||
if let error = error {
|
||||
progress.cancel()
|
||||
self.dispatch_queue.async {
|
||||
completionHandler(nil, error)
|
||||
self.delegateNotify(opType, error: error)
|
||||
@@ -728,16 +801,20 @@ extension FTPFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
}
|
||||
|
||||
return operation
|
||||
return progress
|
||||
}
|
||||
|
||||
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let operation = RemoteOperationHandle(operationType: opType, tasks: [])
|
||||
let progress = Progress(totalUnitCount: Int64(data?.count ?? 0))
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
|
||||
self.ftpLogin(task) { (error) in
|
||||
if let error = error {
|
||||
@@ -749,13 +826,21 @@ extension FTPFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
|
||||
let storeHandler = {
|
||||
self.ftpStore(task, filePath: self.ftpPath(path), fromData: data ?? Data(), fromFile: nil, onTask: {
|
||||
operation.add(task: $0)
|
||||
self.ftpStore(task, filePath: self.ftpPath(path), fromData: data ?? Data(), fromFile: nil, onTask: { task in
|
||||
weak var weakTask = task
|
||||
progress.cancellationHandler = {
|
||||
weakTask?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
}, onProgress: { bytesSent, totalSent, expectedBytes in
|
||||
progress.completedUnitCount = totalSent
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(Double(totalSent) / Double(expectedBytes)))
|
||||
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(progress.fractionCompleted))
|
||||
}
|
||||
}, completionHandler: { (error) in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
self.ftpQuit(task)
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
@@ -775,7 +860,7 @@ extension FTPFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
}
|
||||
|
||||
return operation
|
||||
return progress
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+32
-8
@@ -80,6 +80,13 @@ internal extension FTPFileProvider {
|
||||
task.resume()
|
||||
}
|
||||
|
||||
var isSecure = false
|
||||
// Implicit FTP Connection
|
||||
if self.baseURL?.port == 990 || self.baseURL?.scheme == "ftps" {
|
||||
task.startSecureConnection()
|
||||
isSecure = true
|
||||
}
|
||||
|
||||
let credential = self.credential
|
||||
|
||||
task.readData(ofMinLength: 4, maxLength: 2048, timeout: timeout) { (data, eof, error) in
|
||||
@@ -99,7 +106,7 @@ internal extension FTPFileProvider {
|
||||
return
|
||||
}
|
||||
|
||||
let loginHandle = {
|
||||
let loginHandle: () -> Void = {
|
||||
self.execute(command: "USER \(credential?.user ?? "anonymous")", on: task) { (response, error) in
|
||||
if let error = error {
|
||||
completionHandler(error)
|
||||
@@ -118,7 +125,7 @@ internal extension FTPFileProvider {
|
||||
}
|
||||
|
||||
// needs password
|
||||
if response.hasPrefix("33") {
|
||||
if FileProviderFTPError(message: response).code == 331 {
|
||||
self.execute(command: "PASS \(credential?.password ?? "fileprovider@")", on: task) { (response, error) in
|
||||
if response?.hasPrefix("23") ?? false {
|
||||
completionHandler(nil)
|
||||
@@ -135,7 +142,8 @@ internal extension FTPFileProvider {
|
||||
}
|
||||
}
|
||||
|
||||
if self.baseURL?.scheme == "ftps" || self.baseURL?.port == 990 {
|
||||
if !isSecure && self.baseURL?.scheme == "ftpes" {
|
||||
// Explicit FTP Connection, by upgrading connection to FTP/SSL
|
||||
self.execute(command: "AUTH TLS", on: task, minLength: 0, completionHandler: { (response, error) in
|
||||
if let error = error {
|
||||
completionHandler(error)
|
||||
@@ -144,6 +152,7 @@ internal extension FTPFileProvider {
|
||||
|
||||
if let response = response, response.hasPrefix("23") {
|
||||
task.startSecureConnection()
|
||||
isSecure = true
|
||||
self.execute(command: "PBSZ 0\r\nPROT P", on: task, completionHandler: { (response, error) in
|
||||
if let error = error {
|
||||
completionHandler(error)
|
||||
@@ -152,9 +161,17 @@ internal extension FTPFileProvider {
|
||||
|
||||
loginHandle()
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
} else if isSecure {
|
||||
self.execute(command: "PBSZ 0\r\nPROT P", on: task, completionHandler: { (response, error) in
|
||||
if let error = error {
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
|
||||
loginHandle()
|
||||
})
|
||||
} else {
|
||||
loginHandle()
|
||||
}
|
||||
@@ -219,7 +236,7 @@ internal extension FTPFileProvider {
|
||||
|
||||
let passiveTask = self.session.fpstreamTask(withHostName: host, port: port)
|
||||
passiveTask.resume()
|
||||
if self.baseURL?.scheme == "ftps" || self.baseURL?.port == 990 {
|
||||
if self.baseURL?.scheme == "ftps" || self.baseURL?.scheme == "ftpes" || self.baseURL?.port == 990 {
|
||||
passiveTask.startSecureConnection()
|
||||
}
|
||||
completionHandler(passiveTask, nil)
|
||||
@@ -238,6 +255,7 @@ internal extension FTPFileProvider {
|
||||
if self.baseURL?.scheme == "ftps" || self.baseURL?.port == 990 {
|
||||
activeTask.startSecureConnection()
|
||||
}
|
||||
|
||||
self.execute(command: "PORT \(service.port)", on: task) { (response, error) in
|
||||
if let error = error {
|
||||
activeTask.cancel()
|
||||
@@ -381,8 +399,9 @@ internal extension FTPFileProvider {
|
||||
}
|
||||
}
|
||||
|
||||
func recursiveList(path: String, useMLST: Bool, foundItemsHandler: ((_ contents: [FileObject]) -> Void)? = nil, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
|
||||
let queue = DispatchQueue(label: "test")
|
||||
func recursiveList(path: String, useMLST: Bool, foundItemsHandler: ((_ contents: [FileObject]) -> Void)? = nil, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) -> Progress? {
|
||||
let progress = Progress(totalUnitCount: 0)
|
||||
let queue = DispatchQueue(label: "\(self.type).recursiveList")
|
||||
queue.async {
|
||||
let group = DispatchGroup()
|
||||
var result = [FileObject]()
|
||||
@@ -397,12 +416,14 @@ internal extension FTPFileProvider {
|
||||
}
|
||||
|
||||
result.append(contentsOf: files)
|
||||
progress.completedUnitCount = Int64(files.count)
|
||||
foundItemsHandler?(files)
|
||||
|
||||
let directories: [FileObject] = files.filter { $0.isDirectory }
|
||||
progress.becomeCurrent(withPendingUnitCount: Int64(directories.count))
|
||||
for dir in directories {
|
||||
group.enter()
|
||||
self.recursiveList(path: dir.path, useMLST: useMLST, foundItemsHandler: foundItemsHandler, completionHandler: { (contents, error) in
|
||||
_=self.recursiveList(path: dir.path, useMLST: useMLST, foundItemsHandler: foundItemsHandler, completionHandler: { (contents, error) in
|
||||
success = success && (error == nil)
|
||||
if let error = error {
|
||||
completionHandler([], error)
|
||||
@@ -412,9 +433,11 @@ internal extension FTPFileProvider {
|
||||
|
||||
foundItemsHandler?(files)
|
||||
result.append(contentsOf: contents)
|
||||
|
||||
group.leave()
|
||||
})
|
||||
}
|
||||
progress.resignCurrent()
|
||||
group.leave()
|
||||
})
|
||||
group.wait()
|
||||
@@ -425,6 +448,7 @@ internal extension FTPFileProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
return progress
|
||||
}
|
||||
|
||||
func ftpRetrieveData(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1, onTask: ((_ task: FileProviderStreamTask) -> Void)?, onProgress: ((_ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?, completionHandler: @escaping (_ data: Data?, _ error: Error?) -> Void) {
|
||||
|
||||
+54
-58
@@ -99,7 +99,8 @@ public protocol FileProviderBasic: class, NSSecureCoding {
|
||||
- foundItemHandler: Closure which is called when a file is found
|
||||
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
|
||||
*/
|
||||
func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void))
|
||||
@discardableResult
|
||||
func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress?
|
||||
|
||||
/**
|
||||
Search files inside directory using query asynchronously.
|
||||
@@ -121,8 +122,10 @@ public protocol FileProviderBasic: class, NSSecureCoding {
|
||||
- query: An `NSPredicate` object with keys like `FileObject` members, except `size` which becomes `filesize`.
|
||||
- foundItemHandler: Closure which is called when a file is found
|
||||
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
|
||||
- Returns: An `Progress` to get progress or cancel progress.
|
||||
*/
|
||||
func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void))
|
||||
@discardableResult
|
||||
func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress?
|
||||
|
||||
/**
|
||||
Returns an independent url to access the file. Some providers like `Dropbox` due to their nature.
|
||||
@@ -146,9 +149,9 @@ public protocol FileProviderBasic: class, NSSecureCoding {
|
||||
}
|
||||
|
||||
extension FileProviderBasic {
|
||||
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
|
||||
let predicate = NSPredicate(format: "name BEGINSWITH[c] %@", query)
|
||||
self.searchFiles(path: path, recursive: recursive, query: predicate, foundItemHandler: foundItemHandler, completionHandler: completionHandler)
|
||||
return self.searchFiles(path: path, recursive: recursive, query: predicate, foundItemHandler: foundItemHandler, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
/// The maximum number of queued operations that can execute at the same time.
|
||||
@@ -162,8 +165,6 @@ extension FileProviderBasic {
|
||||
operation_queue.maxConcurrentOperationCount = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// Checking equality of two file provider, regardless of current path queues and delegates.
|
||||
@@ -241,7 +242,7 @@ internal extension FileProviderBasicRemote {
|
||||
return false
|
||||
}
|
||||
|
||||
func runDataTask(with request: URLRequest, operationHandle: RemoteOperationHandle? = nil, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) {
|
||||
func runDataTask(with request: URLRequest, operation: FileOperationType? = nil, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) {
|
||||
let useCache = self.useCache
|
||||
let validatingCache = self.validatingCache
|
||||
dispatch_queue.async {
|
||||
@@ -251,8 +252,7 @@ internal extension FileProviderBasicRemote {
|
||||
}
|
||||
}
|
||||
let task = self.session.dataTask(with: request, completionHandler: completionHandler)
|
||||
task.taskDescription = operationHandle?.operationType.json
|
||||
operationHandle?.add(task: task)
|
||||
task.taskDescription = operation?.json
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
@@ -271,10 +271,10 @@ public protocol FileProviderOperations: FileProviderBasic {
|
||||
- folder: Directory name.
|
||||
- at: Parent path of new directory.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
func create(folder: String, at: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func create(folder: String, at: String, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
|
||||
/**
|
||||
Moves a file or directory from `path` to designated path asynchronously.
|
||||
@@ -285,10 +285,10 @@ public protocol FileProviderOperations: FileProviderBasic {
|
||||
- path: original file or directory path.
|
||||
- to: destination path of file or directory, including file/directory name.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
func moveItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func moveItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
|
||||
/**
|
||||
Moves a file or directory from `path` to designated path asynchronously.
|
||||
@@ -300,10 +300,10 @@ public protocol FileProviderOperations: FileProviderBasic {
|
||||
- to: destination path of file or directory, including file/directory name.
|
||||
- overwrite: Destination file should be overwritten if file is already exists. **Default** is `false`.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
func moveItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func moveItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
|
||||
/**
|
||||
Copies a file or directory from `path` to designated path asynchronously.
|
||||
@@ -314,10 +314,10 @@ public protocol FileProviderOperations: FileProviderBasic {
|
||||
- path: original file or directory path.
|
||||
- to: destination path of file or directory, including file/directory name.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
func copyItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func copyItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
|
||||
/**
|
||||
Copies a file or directory from `path` to designated path asynchronously.
|
||||
@@ -329,10 +329,10 @@ public protocol FileProviderOperations: FileProviderBasic {
|
||||
- to: destination path of file or directory, including file/directory name.
|
||||
- overwrite: Destination file should be overwritten if file is already exists. **Default** is `false`.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
func copyItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func copyItem(path: String, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
|
||||
/**
|
||||
Removes the file or directory at the specified path.
|
||||
@@ -340,11 +340,11 @@ public protocol FileProviderOperations: FileProviderBasic {
|
||||
- Parameters:
|
||||
- path: file or directory path.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
|
||||
*/
|
||||
@discardableResult
|
||||
func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
|
||||
/**
|
||||
Uploads a file from local file url to designated path asynchronously.
|
||||
@@ -356,10 +356,10 @@ public protocol FileProviderOperations: FileProviderBasic {
|
||||
- localFile: a file url to file.
|
||||
- to: destination path of file, including file/directory name.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress.
|
||||
- Returns: An `Progress` to get progress or cancel progress.
|
||||
*/
|
||||
@discardableResult
|
||||
func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
|
||||
/**
|
||||
Uploads a file from local file url to designated path asynchronously.
|
||||
@@ -372,10 +372,10 @@ public protocol FileProviderOperations: FileProviderBasic {
|
||||
- to: destination path of file, including file/directory name.
|
||||
- overwrite: Destination file should be overwritten if file is already exists. **Default** is `false`.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress.
|
||||
- Returns: An `Progress` to get progress or cancel progress.
|
||||
*/
|
||||
@discardableResult
|
||||
func copyItem(localFile: URL, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func copyItem(localFile: URL, to: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
|
||||
/**
|
||||
Download a file from `path` to designated local file url asynchronously.
|
||||
@@ -387,33 +387,33 @@ public protocol FileProviderOperations: FileProviderBasic {
|
||||
- path: original file or directory path.
|
||||
- toLocalURL: destination local url of file, including file/directory name.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
}
|
||||
|
||||
public extension FileProviderOperations {
|
||||
/// *DEPRECATED:* Use Use FileProviderReadWrite.writeContents(path:, data:, completionHandler:) method instead.
|
||||
@available(*, deprecated, message: "Use FileProviderReadWrite.writeContents(path:, data:, completionHandler:) method instead.")
|
||||
@discardableResult
|
||||
public func create(file: String, at: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func create(file: String, at: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let path = (at as NSString).appendingPathComponent(file)
|
||||
return (self as? FileProviderReadWrite)?.writeContents(path: path, contents: data, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func moveItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func moveItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return self.moveItem(path: path, to: to, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func copyItem(localFile: URL, to: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return self.copyItem(localFile: localFile, to: to, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func copyItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func copyItem(path: String, to: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return self.copyItem(path: path, to: to, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
@@ -429,10 +429,10 @@ public protocol FileProviderReadWrite: FileProviderBasic {
|
||||
- completionHandler: a closure with result of file contents or error.
|
||||
- `contents`: contents of file in a `Data` object.
|
||||
- `error`: Error returned by system.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?
|
||||
func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress?
|
||||
|
||||
/**
|
||||
Retreives a `Data` object with a portion contents of the file asynchronously vis contents argument of completion handler.
|
||||
@@ -445,10 +445,10 @@ public protocol FileProviderReadWrite: FileProviderBasic {
|
||||
- completionHandler: a closure with result of file contents or error.
|
||||
- `contents`: contents of file in a `Data` object.
|
||||
- `error`: Error returned by system.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?
|
||||
func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress?
|
||||
|
||||
/**
|
||||
Write the contents of the `Data` to a location asynchronously.
|
||||
@@ -459,10 +459,10 @@ public protocol FileProviderReadWrite: FileProviderBasic {
|
||||
- path: Path of target file.
|
||||
- contents: Data to be written into file, pass nil to create empty file.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
func writeContents(path: String, contents: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func writeContents(path: String, contents: Data?, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
|
||||
/**
|
||||
Write the contents of the `Data` to a location asynchronously.
|
||||
@@ -473,10 +473,10 @@ public protocol FileProviderReadWrite: FileProviderBasic {
|
||||
- contents: Data to be written into file, pass nil to create empty file.
|
||||
- atomically: data will be written to a temporary file before writing to final location. Default is `false`.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
func writeContents(path: String, contents: Data?, atomically: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func writeContents(path: String, contents: Data?, atomically: Bool, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
|
||||
/**
|
||||
Write the contents of the `Data` to a location asynchronously.
|
||||
@@ -487,10 +487,10 @@ public protocol FileProviderReadWrite: FileProviderBasic {
|
||||
- contents: Data to be written into file, pass nil to create empty file.
|
||||
- overwrite: Destination file should be overwritten if file is already exists. Default is `false`.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
func writeContents(path: String, contents: Data?, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func writeContents(path: String, contents: Data?, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
|
||||
/**
|
||||
Write the contents of the `Data` to a location asynchronously.
|
||||
@@ -501,30 +501,30 @@ public protocol FileProviderReadWrite: FileProviderBasic {
|
||||
- overwrite: Destination file should be overwritten if file is already exists. Default is `false`.
|
||||
- atomically: data will be written to a temporary file before writing to final location. Default is `false`.
|
||||
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
- Returns: An `Progress` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
|
||||
*/
|
||||
@discardableResult
|
||||
func writeContents(path: String, contents: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
|
||||
func writeContents(path: String, contents: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress?
|
||||
}
|
||||
|
||||
extension FileProviderReadWrite {
|
||||
@discardableResult
|
||||
public func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle?{
|
||||
public func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
|
||||
return self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func writeContents(path: String, contents: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func writeContents(path: String, contents: Data?, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return self.writeContents(path: path, contents: contents, atomically: false, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func writeContents(path: String, contents: Data?, atomically: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func writeContents(path: String, contents: Data?, atomically: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return self.writeContents(path: path, contents: contents, atomically: atomically, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func writeContents(path: String, contents: Data?, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
public func writeContents(path: String, contents: Data?, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return self.writeContents(path: path, contents: contents, atomically: false, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
@@ -570,7 +570,7 @@ public protocol FileProvideUndoable: FileProviderOperations {
|
||||
var undoManager: UndoManager? { get set }
|
||||
|
||||
/// UndoManager supports undoing this file operation
|
||||
func canUndo(handle: OperationHandle) -> Bool
|
||||
func canUndo(handle: Progress) -> Bool
|
||||
/// UndoManager supports undoing this operation
|
||||
func canUndo(operation: FileOperationType) -> Bool
|
||||
}
|
||||
@@ -580,8 +580,11 @@ public extension FileProvideUndoable {
|
||||
return undoOperation(for: operation) != nil
|
||||
}
|
||||
|
||||
public func canUndo(handle: OperationHandle) -> Bool {
|
||||
return canUndo(operation: handle.operationType)
|
||||
public func canUndo(handle: Progress) -> Bool {
|
||||
if let operationType = handle.userInfo[.fileProvderOperationTypeKey] as? FileOperationType {
|
||||
return canUndo(operation: operationType)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
internal func undoOperation(for operation: FileOperationType) -> FileOperationType? {
|
||||
@@ -1008,6 +1011,7 @@ public enum FileOperationType: CustomStringConvertible {
|
||||
}
|
||||
|
||||
/// Allows to get progress or cancel an in-progress operation, useful for remote providers
|
||||
@available(*, obsoleted: 1.0, message: "Use Progress class class instead.")
|
||||
public protocol OperationHandle {
|
||||
/// Operation supposed to be done on files. Contains file paths as associated value.
|
||||
var operationType: FileOperationType { get }
|
||||
@@ -1028,14 +1032,6 @@ public protocol OperationHandle {
|
||||
func cancel() -> Bool
|
||||
}
|
||||
|
||||
public extension OperationHandle {
|
||||
public var progress: Float {
|
||||
let bytesSoFar = self.bytesSoFar
|
||||
let totalBytes = self.totalBytes
|
||||
return totalBytes > 0 ? Float(Double(bytesSoFar) / Double(totalBytes)) : Float.nan
|
||||
}
|
||||
}
|
||||
|
||||
/// Delegate methods for reporting provider's operation result and progress, when it's ready to update
|
||||
/// user interface.
|
||||
/// All methods are called in main thread to avoids UI bugs.
|
||||
|
||||
Regular → Executable
+26
-3
@@ -50,6 +50,11 @@ public extension URLResourceKey {
|
||||
public static let isEncryptedKey = URLResourceKey(rawValue: "NSURLIsEncryptedKey")
|
||||
}
|
||||
|
||||
public extension ProgressUserInfoKey {
|
||||
public static let fileProvderOperationTypeKey = ProgressUserInfoKey("FilesProviderOperationTypeKey")
|
||||
public static let startingTimeKey = ProgressUserInfoKey("NSProgressstartingTimeKey")
|
||||
}
|
||||
|
||||
internal extension URL {
|
||||
var uw_scheme: String {
|
||||
return self.scheme ?? ""
|
||||
@@ -113,10 +118,28 @@ internal extension URLRequest {
|
||||
self.setValue(contentType.rawValue, forHTTPHeaderField: "Content-Type")
|
||||
}
|
||||
|
||||
mutating func set(dropboxArgKey requestDictionary: [String: AnyObject]) {
|
||||
if let requestJson = String(jsonDictionary: requestDictionary) {
|
||||
self.setValue(requestJson, forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
private func asciiEscape(string:String) -> String {
|
||||
var res = ""
|
||||
for char in string.unicodeScalars {
|
||||
let substring = String(char)
|
||||
if substring.canBeConverted(to: .ascii) {
|
||||
res.append(substring)
|
||||
} else {
|
||||
res = res.appendingFormat("\\u%04x", char.value)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
mutating func set(dropboxArgKey requestDictionary: [String: AnyObject]) {
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: requestDictionary, options: []) else {
|
||||
return
|
||||
}
|
||||
guard var jsonString = String(data: jsonData, encoding: .utf8) else { return }
|
||||
jsonString = self.asciiEscape(string: jsonString)
|
||||
jsonString = jsonString.replacingOccurrences(of: "\\/", with: "/")
|
||||
|
||||
self.setValue(jsonString, forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -173,22 +173,31 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
completionHandler(totalSize, totalSize - freeSize)
|
||||
}
|
||||
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
|
||||
dispatch_queue.async {
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
let iterator = self.fileManager.enumerator(at: self.url(of: path), includingPropertiesForKeys: nil, options: recursive ? [] : [.skipsSubdirectoryDescendants, .skipsPackageDescendants]) { (url, e) -> Bool in
|
||||
completionHandler([], e)
|
||||
return true
|
||||
}
|
||||
var result = [LocalFileObject]()
|
||||
while let fileURL = iterator?.nextObject() as? URL {
|
||||
if progress.isCancelled {
|
||||
break
|
||||
}
|
||||
let path = self.relativePathOf(url: fileURL)
|
||||
if let fileObject = LocalFileObject(fileWithPath: path, relativeTo: self.baseURL), query.evaluate(with: fileObject.mapPredicate()) {
|
||||
result.append(fileObject)
|
||||
progress.completedUnitCount = Int64(result.count)
|
||||
foundItemHandler?(fileObject)
|
||||
}
|
||||
}
|
||||
completionHandler(result, nil)
|
||||
}
|
||||
|
||||
return progress
|
||||
}
|
||||
|
||||
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
|
||||
@@ -200,13 +209,13 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
open weak var fileOperationDelegate : FileOperationDelegate?
|
||||
|
||||
@discardableResult
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.create(path: (atPath as NSString).appendingPathComponent(folderName) + "/")
|
||||
return self.doOperation(opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.move(source: path, destination: toPath)
|
||||
|
||||
if !overwrite && self.fileManager.fileExists(atPath: self.url(of: toPath).path) {
|
||||
@@ -218,7 +227,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.copy(source: path, destination: toPath)
|
||||
|
||||
if !overwrite && self.fileManager.fileExists(atPath: self.url(of: toPath).path) {
|
||||
@@ -232,13 +241,13 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.remove(path: path)
|
||||
return self.doOperation(opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
if !overwrite && self.fileManager.fileExists(atPath: self.url(of: toPath).path) {
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(self.throwError(toPath, code: CocoaError.fileWriteFileExists as FoundationErrorEnum))
|
||||
@@ -250,7 +259,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.copy(source: path, destination: toLocalURL.absoluteString)
|
||||
return self.doOperation(opType, completionHandler: completionHandler)
|
||||
}
|
||||
@@ -263,9 +272,12 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
fileprivate func doOperation(_ opType: FileOperationType, data: Data? = nil, atomically: Bool = false, forUploading: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let localOperationHandle = LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
|
||||
fileprivate func doOperation(_ opType: FileOperationType, data: Data? = nil, atomically: Bool = false, forUploading: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.isCancellable = false
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.receiving, forKey: .fileOperationKindKey)
|
||||
func urlofpath(path: String) -> URL {
|
||||
if path.hasPrefix("file://") {
|
||||
let removedSchemePath = path.replacingOccurrences(of: "file://", with: "", options: .anchored)
|
||||
@@ -299,23 +311,31 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
|
||||
let operationHandler: (URL, URL?) -> Void = { source, dest in
|
||||
do {
|
||||
localOperationHandle.inProgress = true
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
switch opType {
|
||||
case .create:
|
||||
if sourcePath.hasSuffix("/") {
|
||||
progress.totalUnitCount = 1
|
||||
try self.opFileManager.createDirectory(at: source, withIntermediateDirectories: true, attributes: [:])
|
||||
} else {
|
||||
progress.totalUnitCount = Int64(data?.count ?? 0)
|
||||
try data?.write(to: source, options: .atomic)
|
||||
}
|
||||
case .modify:
|
||||
progress.totalUnitCount = Int64(data?.count ?? 0)
|
||||
try data?.write(to: source, options: atomically ? [.atomic] : [])
|
||||
case .copy:
|
||||
guard let dest = dest else { return }
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.copying, forKey: .fileOperationKindKey)
|
||||
progress.totalUnitCount = abs(source.fileSize)
|
||||
try self.opFileManager.copyItem(at: source, to: dest)
|
||||
case .move:
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.copying, forKey: .fileOperationKindKey)
|
||||
guard let dest = dest else { return }
|
||||
progress.totalUnitCount = abs(source.fileSize)
|
||||
try self.opFileManager.moveItem(at: source, to: dest)
|
||||
case.remove:
|
||||
progress.totalUnitCount = abs(source.fileSize)
|
||||
try self.opFileManager.removeItem(at: source)
|
||||
default:
|
||||
return
|
||||
@@ -324,7 +344,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
source.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
|
||||
localOperationHandle.inProgress = false
|
||||
progress.completedUnitCount = progress.totalUnitCount
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(nil)
|
||||
}
|
||||
@@ -335,6 +355,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
if successfulSecurityScopedResourceAccess {
|
||||
source.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
progress.cancel()
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(e)
|
||||
}
|
||||
@@ -376,24 +397,31 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
operationHandler(source, dest)
|
||||
}
|
||||
}
|
||||
return localOperationHandle
|
||||
return progress
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
let localOperationHandle = LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.isCancellable = false
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.receiving, forKey: .fileOperationKindKey)
|
||||
|
||||
let url = self.url(of: path)
|
||||
|
||||
progress.totalUnitCount = url.fileSize
|
||||
|
||||
let operationHandler: (URL) -> Void = { url in
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
do {
|
||||
localOperationHandle.inProgress = true
|
||||
let data = try Data(contentsOf: url)
|
||||
localOperationHandle.inProgress = false
|
||||
progress.completedUnitCount = progress.totalUnitCount
|
||||
self.dispatch_queue.async {
|
||||
completionHandler(data, nil)
|
||||
}
|
||||
} catch let e {
|
||||
progress.cancel()
|
||||
self.dispatch_queue.async {
|
||||
completionHandler(nil, e)
|
||||
}
|
||||
@@ -416,11 +444,11 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
}
|
||||
}
|
||||
|
||||
return localOperationHandle
|
||||
return progress
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
|
||||
if length == 0 || offset < 0 {
|
||||
dispatch_queue.async {
|
||||
completionHandler(Data(), nil)
|
||||
@@ -433,7 +461,12 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
}
|
||||
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
let localOperationHandle = LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.isCancellable = false
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.receiving, forKey: .fileOperationKindKey)
|
||||
|
||||
let url = self.url(of: path)
|
||||
|
||||
let operationHandler: (URL) -> Void = { url in
|
||||
@@ -448,18 +481,19 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
handle.closeFile()
|
||||
}
|
||||
|
||||
localOperationHandle.inProgress = true
|
||||
let size = LocalFileObject(fileWithURL: url)?.size ?? -1
|
||||
progress.totalUnitCount = size
|
||||
guard size > offset else {
|
||||
localOperationHandle.inProgress = false
|
||||
progress.cancel()
|
||||
self.dispatch_queue.async {
|
||||
completionHandler(nil, self.throwError(path, code: CocoaError.fileReadTooLarge as FoundationErrorEnum))
|
||||
}
|
||||
return
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
handle.seek(toFileOffset: UInt64(offset))
|
||||
guard Int64(handle.offsetInFile) == offset else {
|
||||
localOperationHandle.inProgress = false
|
||||
progress.cancel()
|
||||
self.dispatch_queue.async {
|
||||
completionHandler(nil, self.throwError(path, code: CocoaError.fileReadTooLarge as FoundationErrorEnum))
|
||||
}
|
||||
@@ -467,7 +501,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
}
|
||||
|
||||
let data = handle.readData(ofLength: length)
|
||||
localOperationHandle.inProgress = false
|
||||
progress.completedUnitCount = progress.totalUnitCount
|
||||
self.dispatch_queue.async {
|
||||
completionHandler(data, nil)
|
||||
}
|
||||
@@ -487,11 +521,11 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
}
|
||||
}
|
||||
|
||||
return localOperationHandle
|
||||
return progress
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let fileExists = fileManager.fileExists(atPath: url(of: path).path)
|
||||
let opType: FileOperationType = fileExists ? .modify(path: path) : .create(path: path)
|
||||
return self.doOperation(opType, data: data ?? Data(), atomically: atomically, completionHandler: completionHandler)
|
||||
|
||||
@@ -19,7 +19,7 @@ public final class LocalFileObject: FileObject {
|
||||
var fileURL: URL?
|
||||
var rpath = path.replacingOccurrences(of: relativeURL?.path ?? "", with: "", options: .anchored)
|
||||
if relativeURL != nil && rpath.hasPrefix("/") {
|
||||
rpath.remove(at: rpath.startIndex)
|
||||
_=rpath.characters.removeFirst()
|
||||
}
|
||||
if #available(iOS 9.0, macOS 10.11, tvOS 9.0, *) {
|
||||
fileURL = URL(fileURLWithPath: rpath, relativeTo: relativeURL)
|
||||
@@ -217,104 +217,6 @@ internal class LocalFileProviderManagerDelegate: NSObject, FileManagerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
/// - Note: Local operation handling is limited. Please don't use as much as possible.
|
||||
open class LocalOperationHandle: OperationHandle {
|
||||
/// Url of file which operation is doing on
|
||||
public let baseURL: URL
|
||||
/// Type of operation
|
||||
public let operationType: FileOperationType
|
||||
|
||||
init (operationType: FileOperationType, baseURL: URL?) {
|
||||
self.baseURL = baseURL ?? URL(fileURLWithPath: "/")
|
||||
self.operationType = operationType
|
||||
inProgress = false
|
||||
}
|
||||
|
||||
private var sourceURL: URL? {
|
||||
guard let source = operationType.source else { return nil }
|
||||
return source.hasPrefix("file://") ? URL(fileURLWithPath: source) : baseURL.appendingPathComponent(source)
|
||||
}
|
||||
|
||||
private var destURL: URL? {
|
||||
guard let dest = operationType.destination else { return nil }
|
||||
return dest.hasPrefix("file://") ? URL(fileURLWithPath: dest) : baseURL.appendingPathComponent(dest)
|
||||
}
|
||||
|
||||
/// Caution: may put pressure on CPU, may have latency
|
||||
open var bytesSoFar: Int64 {
|
||||
assert(!Thread.isMainThread, "Don't run \(#function) method on main thread")
|
||||
switch operationType {
|
||||
case .modify:
|
||||
guard let url = sourceURL, url.isFileURL else { return 0 }
|
||||
if url.fileIsDirectory {
|
||||
return iterateDirectory(url, deep: true).totalsize
|
||||
} else {
|
||||
return url.fileSize
|
||||
}
|
||||
case .copy, .move:
|
||||
guard let url = destURL, url.isFileURL else { return 0 }
|
||||
if url.fileIsDirectory {
|
||||
return iterateDirectory(url, deep: true).totalsize
|
||||
} else {
|
||||
return url.fileSize
|
||||
}
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Caution: may put pressure on CPU, may have latency
|
||||
open var totalBytes: Int64 {
|
||||
assert(!Thread.isMainThread, "Don't run \(#function) method on main thread")
|
||||
switch operationType {
|
||||
case .copy, .move:
|
||||
guard let url = sourceURL, url.isFileURL else { return 0 }
|
||||
if url.fileIsDirectory {
|
||||
return iterateDirectory(url, deep: true).totalsize
|
||||
} else {
|
||||
return url.fileSize
|
||||
}
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Not usable in local provider
|
||||
open var inProgress: Bool
|
||||
|
||||
|
||||
/// Not usable in local provider
|
||||
open func cancel() -> Bool{
|
||||
return false
|
||||
}
|
||||
|
||||
func iterateDirectory(_ pathURL: URL, deep: Bool) -> (folders: Int, files: Int, totalsize: Int64) {
|
||||
var folders = 0
|
||||
var files = 0
|
||||
var totalsize: Int64 = 0
|
||||
let keys: [URLResourceKey] = [.isDirectoryKey, .fileSizeKey]
|
||||
let enumOpt: FileManager.DirectoryEnumerationOptions = !deep ? [.skipsSubdirectoryDescendants, .skipsPackageDescendants] : []
|
||||
|
||||
let fp = FileManager()
|
||||
let filesList = fp.enumerator(at: pathURL, includingPropertiesForKeys: keys, options: enumOpt, errorHandler: nil)
|
||||
while let fileURL = filesList?.nextObject() as? URL {
|
||||
guard let values = try? fileURL.resourceValues(forKeys: [.isDirectoryKey, .fileSizeKey]) else { continue }
|
||||
let isdir = values.isDirectory ?? false
|
||||
let size = Int64(values.fileSize ?? 0)
|
||||
if isdir {
|
||||
folders += 1
|
||||
} else {
|
||||
files += 1
|
||||
}
|
||||
totalsize += size
|
||||
}
|
||||
|
||||
return (folders, files, totalsize)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class UndoBox: NSObject {
|
||||
weak var provider: FileProvideUndoable?
|
||||
let operation: FileOperationType
|
||||
|
||||
@@ -43,7 +43,7 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
public var validatingCache: Bool
|
||||
|
||||
fileprivate var _session: URLSession?
|
||||
fileprivate var sessionDelegate: SessionDelegate?
|
||||
internal fileprivate(set) var sessionDelegate: SessionDelegate?
|
||||
public var session: URLSession {
|
||||
get {
|
||||
if _session == nil {
|
||||
@@ -190,12 +190,13 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
|
||||
var foundFiles = [OneDriveFileObject]()
|
||||
var queryStr: String?
|
||||
queryStr = query.findValue(forKey: "name") as? String ?? query.findAllValues(forKey: nil).flatMap { $0.value as? String }.first
|
||||
guard let finalQueryStr = queryStr else { return }
|
||||
search(path, query: finalQueryStr, foundItem: { (file) in
|
||||
guard let finalQueryStr = queryStr else { return nil }
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
search(path, query: finalQueryStr, progress: progress, foundItem: { (file) in
|
||||
if query.evaluate(with: file.mapPredicate()) {
|
||||
foundFiles.append(file)
|
||||
foundItemHandler?(file)
|
||||
@@ -203,6 +204,7 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
}, completionHandler: { (error) in
|
||||
completionHandler(foundFiles, error)
|
||||
})
|
||||
return progress
|
||||
}
|
||||
|
||||
open func url(of path: String? = nil, modifier: String? = nil) -> URL {
|
||||
@@ -213,22 +215,24 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
rpath = self.currentPath
|
||||
}
|
||||
|
||||
let driveURL = baseURL!.appendingPathComponent("drive/\(drive):/")
|
||||
|
||||
if rpath.hasPrefix("/") {
|
||||
rpath.remove(at: rpath.startIndex)
|
||||
_=rpath.characters.removeFirst()
|
||||
}
|
||||
if rpath.isEmpty {
|
||||
if let modifier = modifier {
|
||||
return baseURL!.appendingPathComponent("drive/\(drive)/\(modifier)")
|
||||
return driveURL.appendingPathComponent(modifier)
|
||||
}
|
||||
return baseURL!.appendingPathComponent("drive/\(drive)")
|
||||
return driveURL
|
||||
}
|
||||
let driveURL = baseURL!.appendingPathComponent("drive/\(drive):/")
|
||||
rpath = (rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? rpath)
|
||||
|
||||
rpath = rpath.trimmingCharacters(in: pathTrimSet)
|
||||
if let modifier = modifier {
|
||||
rpath = rpath + ":/" + modifier
|
||||
}
|
||||
return URL(string: rpath, relativeTo: driveURL) ?? driveURL
|
||||
|
||||
return driveURL.appendingPathComponent(rpath)
|
||||
}
|
||||
|
||||
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
|
||||
@@ -247,28 +251,33 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
|
||||
extension OneDriveFileProvider: FileProviderOperations {
|
||||
|
||||
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
|
||||
return doOperation(.create(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
return doOperation(.remove(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
fileprivate func doOperation(_ operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
fileprivate func doOperation(_ operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let progress = Progress(totalUnitCount: 1)
|
||||
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
guard let sourcePath = operation.source else { return nil }
|
||||
let destPath = operation.destination
|
||||
var request = URLRequest(url: url(of: sourcePath))
|
||||
@@ -298,15 +307,24 @@ extension OneDriveFileProvider: FileProviderOperations {
|
||||
if let response = response as? HTTPURLResponse, response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
|
||||
serverError = FileProviderOneDriveError(code: code, path: sourcePath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
|
||||
}
|
||||
if serverError == nil && error == nil {
|
||||
progress.completedUnitCount = 1
|
||||
} else {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler?(serverError ?? error)
|
||||
self.delegateNotify(operation, error: serverError ?? error)
|
||||
})
|
||||
task.taskDescription = operation.json
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
return progress
|
||||
}
|
||||
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
// check file is not a folder
|
||||
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
|
||||
dispatch_queue.async {
|
||||
@@ -322,20 +340,34 @@ extension OneDriveFileProvider: FileProviderOperations {
|
||||
return upload_simple(toPath, localFile: localFile, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.copy(source: path, destination: destURL.absoluteString)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
var request = URLRequest(url: self.url(of: path, modifier: "content"))
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
let task = session.downloadTask(with: request)
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = completionHandler
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler?(error)
|
||||
}
|
||||
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
|
||||
guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: (task.response as? HTTPURLResponse)?.statusCode ?? -1)
|
||||
let errorData : Data? = nil //Data(contentsOf: cacheURL) // TODO: Figure out how to get error response data for the error description
|
||||
let serverError : FileProviderOneDriveError? = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil
|
||||
if serverError != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler?(serverError)
|
||||
return
|
||||
}
|
||||
@@ -347,13 +379,19 @@ extension OneDriveFileProvider: FileProviderOperations {
|
||||
}
|
||||
}
|
||||
task.taskDescription = opType.json
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
return progress
|
||||
}
|
||||
}
|
||||
|
||||
extension OneDriveFileProvider: FileProviderReadWrite {
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
|
||||
if length == 0 || offset < 0 {
|
||||
dispatch_queue.async {
|
||||
completionHandler(Data(), nil)
|
||||
@@ -362,19 +400,31 @@ extension OneDriveFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
|
||||
var progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
var request = URLRequest(url: self.url(of: path, modifier: "content"))
|
||||
request.httpMethod = "GET"
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
request.set(rangeWithOffset: offset, length: length)
|
||||
let task = session.downloadTask(with: request)
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler(nil, error)
|
||||
}
|
||||
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
|
||||
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
|
||||
guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: (task.response as? HTTPURLResponse)?.statusCode ?? -1)
|
||||
let errorData : Data? = nil //Data(contentsOf: cacheURL) // TODO: Figure out how to get error response data for the error description
|
||||
let serverError : FileProviderOneDriveError? = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil
|
||||
if serverError != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler(nil, serverError)
|
||||
return
|
||||
}
|
||||
@@ -386,11 +436,17 @@ extension OneDriveFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
}
|
||||
task.taskDescription = opType.json
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
return progress
|
||||
}
|
||||
|
||||
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
|
||||
@@ -18,12 +18,13 @@ public struct FileProviderOneDriveError: FileProviderHTTPError {
|
||||
/// Containts path, url and attributes of a OneDrive file or resource.
|
||||
public final class OneDriveFileObject: FileObject {
|
||||
internal init(baseURL: URL?, name: String, path: String) {
|
||||
var rpath = path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? path
|
||||
var rpath = (URL(string:path)?.appendingPathComponent(name).absoluteString)!
|
||||
if rpath.hasPrefix("/") {
|
||||
rpath.remove(at: rpath.startIndex)
|
||||
_=rpath.characters.removeFirst()
|
||||
}
|
||||
let url = URL(string: rpath, relativeTo: baseURL) ?? URL(string: rpath)!
|
||||
super.init(url: url, name: name, path: path)
|
||||
|
||||
super.init(url: url, name: name, path: rpath.removingPercentEncoding ?? path)
|
||||
}
|
||||
|
||||
internal convenience init? (baseURL: URL?, drive: String, jsonStr: String) {
|
||||
@@ -34,14 +35,14 @@ public final class OneDriveFileObject: FileObject {
|
||||
internal convenience init? (baseURL: URL?, drive: String, json: [String: AnyObject]) {
|
||||
guard let name = json["name"] as? String else { return nil }
|
||||
guard let path = (json["parentReference"] as? NSDictionary)?["path"] as? String else { return nil }
|
||||
var lPath = path.replacingOccurrences(of: "/drive/\(drive)", with: "/", options: .anchored, range: nil)
|
||||
var lPath = path.replacingOccurrences(of: "/drive/\(drive):", with: "/", options: .anchored, range: nil)
|
||||
lPath = lPath.replacingOccurrences(of: "/:", with: "", options: .anchored)
|
||||
lPath = lPath.replacingOccurrences(of: "//", with: "", options: .anchored)
|
||||
self.init(baseURL: baseURL, name: name, path: lPath)
|
||||
self.size = (json["size"] as? NSNumber)?.int64Value ?? -1
|
||||
self.modifiedDate = Date(rfcString: json["lastModifiedDateTime"] as? String ?? "")
|
||||
self.creationDate = Date(rfcString: json["createdDateTime"] as? String ?? "")
|
||||
self.type = (json["folder"] as? String) != nil ? .directory : .regular
|
||||
self.type = json["folder"] != nil ? .directory : .regular
|
||||
self.id = json["id"] as? String
|
||||
self.entryTag = json["eTag"] as? String
|
||||
}
|
||||
@@ -113,7 +114,7 @@ internal extension OneDriveFileProvider {
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, data: Data? = nil , localFile: URL? = nil, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
func upload_simple(_ targetPath: String, data: Data? = nil, localFile: URL? = nil, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let size = data?.count ?? (try? localFile?.resourceValues(forKeys: [.fileSizeKey]))??.fileSize ?? -1
|
||||
if size > 100 * 1024 * 1024 {
|
||||
let error = FileProviderOneDriveError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
@@ -121,6 +122,13 @@ internal extension OneDriveFileProvider {
|
||||
self.delegateNotify(.create(path: targetPath), error: error)
|
||||
return nil
|
||||
}
|
||||
|
||||
var progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
progress.totalUnitCount = Int64(size)
|
||||
|
||||
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
|
||||
let url = self.url(of: targetPath, modifier: "content\(queryStr)")
|
||||
var request = URLRequest(url: url)
|
||||
@@ -142,15 +150,27 @@ internal extension OneDriveFileProvider {
|
||||
// We can't fetch server result from delegate!
|
||||
responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: nil)
|
||||
}
|
||||
if !(responseError == nil && error == nil) {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self?.delegateNotify(.create(path: targetPath), error: responseError ?? error)
|
||||
}
|
||||
task.taskDescription = operation.json
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: &progress)
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
return progress
|
||||
}
|
||||
|
||||
func search(_ startPath: String = "", query: String, next: URL? = nil, foundItem:@escaping ((_ file: OneDriveFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
|
||||
func search(_ startPath: String = "", query: String, next: URL? = nil, progress: Progress, foundItem: @escaping ((_ file: OneDriveFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
|
||||
if progress.isCancelled {
|
||||
return
|
||||
}
|
||||
|
||||
let url: URL
|
||||
let q = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
|
||||
url = next ?? self.url(of: startPath, modifier: "view.search?q=\(q)")
|
||||
@@ -171,8 +191,8 @@ internal extension OneDriveFileProvider {
|
||||
}
|
||||
}
|
||||
let next: URL? = (json["@odata.nextLink"] as? String).flatMap { URL(string: $0) }
|
||||
if let next = next {
|
||||
self.search(startPath, query: query, next: next, foundItem: foundItem, completionHandler: completionHandler)
|
||||
if !progress.isCancelled, let next = next {
|
||||
self.search(startPath, query: query, next: next, progress: progress, foundItem: foundItem, completionHandler: completionHandler)
|
||||
} else {
|
||||
completionHandler(responseError ?? error)
|
||||
}
|
||||
@@ -180,7 +200,11 @@ internal extension OneDriveFileProvider {
|
||||
}
|
||||
}
|
||||
completionHandler(responseError ?? error)
|
||||
})
|
||||
})
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
+41
-61
@@ -8,67 +8,6 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Allows to get progress or cancel an in-progress operation, for remote, `URLSession` based providers.
|
||||
/// This class keeps strong reference to tasks.
|
||||
open class RemoteOperationHandle: OperationHandle {
|
||||
|
||||
internal var tasks: [URLSessionTask]
|
||||
|
||||
open private(set) var operationType: FileOperationType
|
||||
|
||||
init(operationType: FileOperationType, tasks: [URLSessionTask]) {
|
||||
self.operationType = operationType
|
||||
self.tasks = tasks
|
||||
}
|
||||
|
||||
internal func add(task: URLSessionTask) {
|
||||
tasks.append(task)
|
||||
}
|
||||
|
||||
internal func reape() {
|
||||
self.tasks = tasks.filter { $0.state != .completed }
|
||||
}
|
||||
|
||||
open var bytesSoFar: Int64 {
|
||||
return tasks.reduce(0) {
|
||||
switch $1 {
|
||||
case let task as URLSessionUploadTask:
|
||||
return $0 + task.countOfBytesSent
|
||||
case let task as FileProviderStreamTask:
|
||||
return $0 + task.countOfBytesSent + task.countOfBytesReceived
|
||||
default:
|
||||
return $0 + $1.countOfBytesReceived
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open var totalBytes: Int64 {
|
||||
return tasks.reduce(0) {
|
||||
switch $1 {
|
||||
case let task as URLSessionUploadTask:
|
||||
return $0 + task.countOfBytesExpectedToSend
|
||||
case let task as FileProviderStreamTask:
|
||||
return $0 + task.countOfBytesExpectedToSend + task.countOfBytesExpectedToReceive
|
||||
default:
|
||||
return $0 + $1.countOfBytesExpectedToReceive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func cancel() -> Bool {
|
||||
var canceled = false
|
||||
for taskbox in tasks {
|
||||
taskbox.cancel()
|
||||
canceled = true
|
||||
}
|
||||
return canceled
|
||||
}
|
||||
|
||||
open var inProgress: Bool {
|
||||
return tasks.reduce(false) { $0 || $1.state == .running }
|
||||
}
|
||||
}
|
||||
|
||||
/// A protocol defines properties for errors returned by HTTP/S based providers.
|
||||
/// Including Dropbox, OneDrive and WebDAV.
|
||||
public protocol FileProviderHTTPError: Error, CustomStringConvertible {
|
||||
@@ -140,8 +79,49 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession
|
||||
self.credential = fileProvider.credential
|
||||
}
|
||||
|
||||
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
if let progress = context?.load(as: Progress.self), let newVal = change?[.newKey] as? Int64 {
|
||||
switch keyPath ?? "" {
|
||||
case #keyPath(URLSessionTask.countOfBytesReceived):
|
||||
progress.completedUnitCount = newVal
|
||||
if let startTime = progress.userInfo[ProgressUserInfoKey.startingTimeKey] as? Date, let task = object as? URLSessionTask {
|
||||
let elapsed = Date().timeIntervalSince(startTime)
|
||||
let throughput = Double(newVal) / elapsed
|
||||
progress.setUserInfoObject(NSNumber(value: throughput), forKey: .throughputKey)
|
||||
if task.countOfBytesExpectedToReceive > 0 {
|
||||
let remain = task.countOfBytesExpectedToReceive - task.countOfBytesReceived
|
||||
let estimatedTimeRemaining = Double(remain) / elapsed
|
||||
progress.setUserInfoObject(NSNumber(value: estimatedTimeRemaining), forKey: .estimatedTimeRemainingKey)
|
||||
}
|
||||
}
|
||||
case #keyPath(URLSessionTask.countOfBytesSent):
|
||||
progress.completedUnitCount = newVal
|
||||
if let startTime = progress.userInfo[ProgressUserInfoKey.startingTimeKey] as? Date, let task = object as? URLSessionTask {
|
||||
let elapsed = Date().timeIntervalSince(startTime)
|
||||
let throughput = Double(newVal) / elapsed
|
||||
progress.setUserInfoObject(NSNumber(value: throughput), forKey: .throughputKey)
|
||||
if task.countOfBytesExpectedToSend > 0 {
|
||||
let remain = task.countOfBytesExpectedToSend - task.countOfBytesSent
|
||||
let estimatedTimeRemaining = Double(remain) / elapsed
|
||||
progress.setUserInfoObject(NSNumber(value: estimatedTimeRemaining), forKey: .estimatedTimeRemainingKey)
|
||||
}
|
||||
}
|
||||
case #keyPath(URLSessionTask.countOfBytesExpectedToReceive), #keyPath(URLSessionTask.countOfBytesExpectedToSend):
|
||||
progress.totalUnitCount = newVal
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
|
||||
}
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived))
|
||||
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive))
|
||||
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent))
|
||||
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived))
|
||||
|
||||
if !(error == nil && task is URLSessionDownloadTask) {
|
||||
let completionHandler = completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] ?? nil
|
||||
completionHandler?(error)
|
||||
|
||||
@@ -74,53 +74,54 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
|
||||
|
||||
open weak var fileOperationDelegate: FileOperationDelegate?
|
||||
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler:((FileObjectClass) -> Void)?, completionHandler: @escaping ((_ files: [FileObjectClass], _ error: Error?) -> Void)) {
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler:((FileObjectClass) -> Void)?, completionHandler: @escaping ((_ files: [FileObjectClass], _ error: Error?) -> Void)) -> Progress? {
|
||||
NotImplemented()
|
||||
return nil
|
||||
}
|
||||
|
||||
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
|
||||
|
||||
@@ -171,7 +171,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
request.set(contentType: .xml)
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n\(WebDavFileObject.propString(including))\n</D:propfind>".data(using: .utf8)
|
||||
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
|
||||
runDataTask(with: request, operationHandle: RemoteOperationHandle(operationType: opType, tasks: []), completionHandler: { (data, response, error) in
|
||||
runDataTask(with: request, operation: opType, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
|
||||
@@ -258,7 +258,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
})
|
||||
}
|
||||
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
|
||||
let url = self.url(of: path)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PROPFIND"
|
||||
@@ -266,7 +266,8 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
request.set(httpAuthentication: credential, with: credentialType)
|
||||
request.set(contentType: .xml)
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
|
||||
runDataTask(with: request, completionHandler: { (data, response, error) in
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
let task = session.dataTask(with: request) { (data, response, error) in
|
||||
// FIXME: paginating results
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
@@ -282,13 +283,20 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
fileObjects.append(fileObject)
|
||||
progress.completedUnitCount = Int64(fileObjects.count)
|
||||
foundItemHandler?(fileObject)
|
||||
}
|
||||
completionHandler(fileObjects, responseError ?? error)
|
||||
return
|
||||
}
|
||||
completionHandler([], responseError ?? error)
|
||||
})
|
||||
}
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return progress
|
||||
}
|
||||
|
||||
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
|
||||
@@ -310,30 +318,16 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
|
||||
extension WebDAVFileProvider: FileProviderOperations {
|
||||
@discardableResult
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.create(path: (atPath as NSString).appendingPathComponent(folderName) + "/")
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let url = self.url(of: atPath).appendingPathComponent(folderName, isDirectory: true)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "MKCOL"
|
||||
request.set(httpAuthentication: credential, with: credentialType)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderWebDavError(code: rCode, path: url.relativePath, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self.delegateNotify(opType, error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
return self.doOperation(operation: opType, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.move(source: path, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
@@ -342,7 +336,7 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.copy(source: path, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
@@ -351,7 +345,7 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.remove(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
@@ -359,7 +353,7 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
return self.doOperation(operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
fileprivate func doOperation(operation opType: FileOperationType, overwrite: Bool? = nil, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
fileprivate func doOperation(operation opType: FileOperationType, overwrite: Bool? = nil, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let source = opType.source!
|
||||
let sourceURL = self.url(of: source)
|
||||
var request = URLRequest(url: sourceURL)
|
||||
@@ -367,6 +361,8 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
request.setValue(url(of:dest).absoluteString, forHTTPHeaderField: "Destination")
|
||||
}
|
||||
switch opType {
|
||||
case .create:
|
||||
request.httpMethod = "MKCOL"
|
||||
case .copy:
|
||||
request.httpMethod = "COPY"
|
||||
case .move:
|
||||
@@ -377,6 +373,11 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
return nil
|
||||
}
|
||||
|
||||
let progress = Progress(totalUnitCount: 1)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
request.set(httpAuthentication: credential, with: credentialType)
|
||||
if let overwrite = overwrite, !overwrite {
|
||||
request.setValue("F", forHTTPHeaderField: "Overwrite")
|
||||
@@ -392,9 +393,17 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
for xresponse in xresponses where (xresponse.status ?? 0) >= 300 {
|
||||
let error = FileProviderWebDavError(code: code, path: source, errorDescription: String(data: data, encoding: .utf8), url: sourceURL)
|
||||
completionHandler?(error)
|
||||
progress.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if responseError == nil && error == nil {
|
||||
progress.completedUnitCount = 1
|
||||
} else {
|
||||
progress.cancel()
|
||||
}
|
||||
|
||||
if (response as? HTTPURLResponse)?.statusCode ?? 0 != FileProviderHTTPErrorCode.multiStatus.rawValue {
|
||||
completionHandler?(responseError ?? error)
|
||||
}
|
||||
@@ -402,12 +411,16 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
self.delegateNotify(opType, error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = opType.json
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
return progress
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
// check file is not a folder
|
||||
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
|
||||
dispatch_queue.async {
|
||||
@@ -420,6 +433,13 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
progress.totalUnitCount = localFile.fileSize
|
||||
|
||||
let url = self.url(of:toPath)
|
||||
var request = URLRequest(url: url)
|
||||
if !overwrite {
|
||||
@@ -434,25 +454,44 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
// We can't fetch server result from delegate!
|
||||
responseError = FileProviderWebDavError(code: rCode, path: toPath, errorDescription: nil, url: url)
|
||||
}
|
||||
if !(responseError == nil && error == nil) {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self?.delegateNotify(.create(path: toPath), error: responseError ?? error)
|
||||
}
|
||||
task.taskDescription = opType.json
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: &progress)
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
return progress
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.copy(source: path, destination: destURL.absoluteString)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
let url = self.url(of:path)
|
||||
var request = URLRequest(url: url)
|
||||
request.set(httpAuthentication: credential, with: credentialType)
|
||||
let task = session.downloadTask(with: request)
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = completionHandler
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler?(error)
|
||||
}
|
||||
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
|
||||
guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: (task.response as? HTTPURLResponse)?.statusCode ?? -1)
|
||||
@@ -468,14 +507,20 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
}
|
||||
}
|
||||
task.taskDescription = opType.json
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
return progress
|
||||
}
|
||||
}
|
||||
|
||||
extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
@discardableResult
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
|
||||
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
|
||||
if length == 0 || offset < 0 {
|
||||
dispatch_queue.async {
|
||||
completionHandler(Data(), nil)
|
||||
@@ -484,17 +529,26 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
|
||||
var progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
let url = self.url(of: path)
|
||||
var request = URLRequest(url: url)
|
||||
request.set(httpAuthentication: credential, with: credentialType)
|
||||
request.set(rangeWithOffset: offset, length: length)
|
||||
|
||||
let task = session.downloadTask(with: request)
|
||||
let handle = RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
|
||||
completionHandler(nil, error)
|
||||
}
|
||||
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
|
||||
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak self] tempURL in
|
||||
guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
|
||||
let code = FileProviderHTTPErrorCode(rawValue: (task.response as? HTTPURLResponse)?.statusCode ?? -1)
|
||||
let serverError : FileProviderWebDavError? = code != nil ? FileProviderWebDavError(code: code!, path: path, errorDescription: code?.description, url: url) : nil
|
||||
@@ -503,7 +557,7 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
do {
|
||||
let data = try Data(contentsOf: tempURL)
|
||||
self.dispatch_queue.async {
|
||||
(self?.dispatch_queue ?? DispatchQueue.global()).async {
|
||||
completionHandler(data, nil)
|
||||
}
|
||||
} catch let e {
|
||||
@@ -511,16 +565,29 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
}
|
||||
}
|
||||
task.taskDescription = opType.json
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress)
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress)
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return handle
|
||||
return progress
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let opType = FileOperationType.modify(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
progress.totalUnitCount = Int64(data?.count ?? 0)
|
||||
|
||||
// FIXME: lock destination before writing process
|
||||
let url = atomically ? self.url(of: path).appendingPathExtension("tmp") : self.url(of: path)
|
||||
var request = URLRequest(url: url)
|
||||
@@ -536,12 +603,20 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
// We can't fetch server result from delegate!
|
||||
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: nil, url: url)
|
||||
}
|
||||
if !(responseError == nil && error == nil) {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self?.delegateNotify(opType, error: responseError ?? error)
|
||||
}
|
||||
task.taskDescription = opType.json
|
||||
task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: &progress)
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: opType, tasks: [task])
|
||||
return progress
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user