Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cad68da076 | |||
| 39da09edd4 | |||
| 5129aee1b5 | |||
| 478f0819b5 |
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
|
||||
#
|
||||
|
||||
s.name = "FilesProvider"
|
||||
s.version = "0.19.0"
|
||||
s.version = "0.20.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.
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
794C220E1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */; };
|
||||
794C220F1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */; };
|
||||
794C22101D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */; };
|
||||
7958155A1F478ED9003344DD /* HTTPFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 795815591F478ED9003344DD /* HTTPFileProvider.swift */; };
|
||||
7958155B1F478ED9003344DD /* HTTPFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 795815591F478ED9003344DD /* HTTPFileProvider.swift */; };
|
||||
7958155C1F478ED9003344DD /* HTTPFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 795815591F478ED9003344DD /* HTTPFileProvider.swift */; };
|
||||
796807551E7BF17E00BBB87B /* FileProviderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796807541E7BF17E00BBB87B /* FileProviderExtensions.swift */; };
|
||||
796807561E7BF17E00BBB87B /* FileProviderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796807541E7BF17E00BBB87B /* FileProviderExtensions.swift */; };
|
||||
796807571E7BF17E00BBB87B /* FileProviderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796807541E7BF17E00BBB87B /* FileProviderExtensions.swift */; };
|
||||
@@ -144,6 +147,7 @@
|
||||
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropboxHelper.swift; sourceTree = "<group>"; };
|
||||
794C22091D5893F800EC49B8 /* SMB2Notification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Notification.swift; sourceTree = "<group>"; };
|
||||
794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2QueryTypes.swift; sourceTree = "<group>"; };
|
||||
795815591F478ED9003344DD /* HTTPFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPFileProvider.swift; sourceTree = "<group>"; };
|
||||
796807541E7BF17E00BBB87B /* FileProviderExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileProviderExtensions.swift; sourceTree = "<group>"; };
|
||||
798654321E8874BC002FA550 /* FTPHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTPHelper.swift; sourceTree = "<group>"; };
|
||||
799396671D48B7F600086753 /* FilesProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FilesProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -299,17 +303,18 @@
|
||||
792572401DF23BDA006A1526 /* LocalHelper.swift */,
|
||||
79480FF51E3ABDD0007E7275 /* CloudFileProvider.swift */,
|
||||
79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */,
|
||||
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */,
|
||||
7902C0851D61B56D00564440 /* RemoteSession.swift */,
|
||||
795815591F478ED9003344DD /* HTTPFileProvider.swift */,
|
||||
799396931D48C02300086753 /* DropboxFileProvider.swift */,
|
||||
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
|
||||
7936BC111E880F5700A6C81C /* FTPFileProvider.swift */,
|
||||
798654321E8874BC002FA550 /* FTPHelper.swift */,
|
||||
79BD63C31E2D17880035128C /* OneDriveFileProvider.swift */,
|
||||
79BD63C41E2D17880035128C /* OneDriveHelper.swift */,
|
||||
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */,
|
||||
799396A61D48C02300086753 /* WebDAVFileProvider.swift */,
|
||||
7936BC111E880F5700A6C81C /* FTPFileProvider.swift */,
|
||||
798654321E8874BC002FA550 /* FTPHelper.swift */,
|
||||
799396971D48C02300086753 /* SMBClient.swift */,
|
||||
799396981D48C02300086753 /* SMBFileProvider.swift */,
|
||||
799396A61D48C02300086753 /* WebDAVFileProvider.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
@@ -502,6 +507,7 @@
|
||||
7924B1991D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396D71D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
7958155A1F478ED9003344DD /* HTTPFileProvider.swift in Sources */,
|
||||
798654331E8874BC002FA550 /* FTPHelper.swift in Sources */,
|
||||
7924B1B21D89FCDA00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
799396C51D48C02300086753 /* SMB2FileOperation.swift in Sources */,
|
||||
@@ -545,6 +551,7 @@
|
||||
7924B1B01D89F7DE00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
7924B19A1D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
7958155B1F478ED9003344DD /* HTTPFileProvider.swift in Sources */,
|
||||
79F4678B1E8B80F200C91A85 /* FTPHelper.swift in Sources */,
|
||||
799396D81D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
799396C61D48C02300086753 /* SMB2FileOperation.swift in Sources */,
|
||||
@@ -588,6 +595,7 @@
|
||||
7924B1B11D89F7DF00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
7924B19B1D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
7958155C1F478ED9003344DD /* HTTPFileProvider.swift in Sources */,
|
||||
79F4678C1E8B80F200C91A85 /* FTPHelper.swift in Sources */,
|
||||
799396D91D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
799396C71D48C02300086753 /* SMB2FileOperation.swift in Sources */,
|
||||
@@ -621,7 +629,7 @@
|
||||
799396601D48B7BF00086753 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.19.0;
|
||||
BUNDLE_VERSION_STRING = 0.20.0;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -653,7 +661,7 @@
|
||||
799396611D48B7BF00086753 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.19.0;
|
||||
BUNDLE_VERSION_STRING = 0.20.0;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
|
||||
@@ -52,7 +52,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
- Parameter scope: Use `.documents` (default) to put documents that the user is allowed to access inside a Documents subdirectory. Otherwise use `.data` to store user-related data files that your app needs to share but that are not files you want the user to manipulate directly.
|
||||
*/
|
||||
public init? (containerId: String?, scope: UbiquitousScope = .documents) {
|
||||
assert(!CloudFileProvider.asserting || !Thread.isMainThread, "LocalFileProvider.init(containerId:) is not recommended to be executed on Main Thread.")
|
||||
assert(!(CloudFileProvider.asserting && Thread.isMainThread), "CloudFileProvider.init(containerId:) is not recommended to be executed on Main Thread.")
|
||||
guard FileManager.default.ubiquityIdentityToken != nil else {
|
||||
return nil
|
||||
}
|
||||
@@ -286,6 +286,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
|
||||
dispatch_queue.async {
|
||||
let pathURL = self.url(of: path)
|
||||
progress.setUserInfoObject(pathURL, forKey: .fileURLKey)
|
||||
let mdquery = NSMetadataQuery()
|
||||
mdquery.predicate = NSPredicate(format: "(%K BEGINSWITH %@) && (\(updateQueryKeys(query).predicateFormat))", NSMetadataItemPathKey, pathURL.path)
|
||||
mdquery.searchScopes = [self.scope.rawValue]
|
||||
@@ -463,6 +464,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.isCancellable = false
|
||||
progress.setUserInfoObject(localFile, forKey: .fileURLKey)
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
monitorFile(path: toPath, opType: opType, progress: progress)
|
||||
operation_queue.addOperation {
|
||||
@@ -512,6 +514,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.isCancellable = false
|
||||
progress.setUserInfoObject(self.url(of: path), forKey: .fileURLKey)
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
monitorFile(path: path, opType: opType, progress: progress)
|
||||
do {
|
||||
@@ -544,6 +547,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(self.url(of: path), forKey: .fileURLKey)
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
monitorFile(path: path, opType: operation, progress: progress)
|
||||
_ = super.contents(path: path, completionHandler: completionHandler)
|
||||
@@ -569,6 +573,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(self.url(of: path), forKey: .fileURLKey)
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
monitorFile(path: path, opType: operation, progress: progress)
|
||||
_ = super.contents(path: path, offset: offset, length: length, completionHandler: completionHandler)
|
||||
@@ -592,6 +597,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(self.url(of: path), forKey: .fileURLKey)
|
||||
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)
|
||||
@@ -680,7 +686,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
return file
|
||||
}
|
||||
|
||||
func monitorFile(path: String, opType: FileOperationType, progress: Progress?) {
|
||||
fileprivate func monitorFile(path: String, opType: FileOperationType, progress: Progress?) {
|
||||
dispatch_queue.async {
|
||||
let pathURL = self.url(of: path)
|
||||
let size = pathURL.fileSize
|
||||
@@ -731,20 +737,6 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes local copy of file, but spares cloud copy/
|
||||
/// - Parameter path: Path of file or directory to be remoed from local
|
||||
/// - Parameter completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
open func evictItem(path: String, completionHandler: SimpleCompletionHandler) {
|
||||
operation_queue.addOperation {
|
||||
do {
|
||||
try self.opFileManager.evictUbiquitousItem(at: self.url(of: path))
|
||||
completionHandler?(nil)
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
|
||||
operation_queue.addOperation {
|
||||
do {
|
||||
@@ -762,6 +754,38 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
}
|
||||
}
|
||||
|
||||
extension CloudFileProvider {
|
||||
|
||||
/// Removes local copy of file, but spares cloud copy.
|
||||
/// - Parameter path: Path of file or directory to be removed from local
|
||||
/// - Parameter completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
open func evictItem(path: String, completionHandler: SimpleCompletionHandler) {
|
||||
operation_queue.addOperation {
|
||||
do {
|
||||
try self.opFileManager.evictUbiquitousItem(at: self.url(of: path))
|
||||
completionHandler?(nil)
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns current version of file on this device and all versions of files in user devices.
|
||||
/// - Parameter path: Path of file or directory.
|
||||
/// - Parameter completionHandler: Retrieve current version on this device and all versions available. `currentVersion` will be nil if file doesn't exist. If an error parameter was provided, a presentable `Error` will be returned.
|
||||
func versionsOfItem(path: String, completionHandler: @escaping ((_ currentVersion: NSFileVersion?, _ versions: [NSFileVersion], _ error: Error?) -> Void)) {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
/// Resolves conflicts by selecting a version.
|
||||
/// - Parameter path: Path of file or directory.
|
||||
/// - Parameter version: Version than will be choose as main version. `nil` value indicates current version on this device will be selected.
|
||||
/// - Parameter completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
|
||||
func selectVersionOfItem(path: String, version: NSFileVersion? = nil, completionHandler: SimpleCompletionHandler) {
|
||||
NotImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
/// Scope of iCloud, wrapper for NSMetadataQueryUbiquitous...Scope constants
|
||||
public enum UbiquitousScope: RawRepresentable {
|
||||
/// Search all files not in the Documents directories of the app’s iCloud container directories.
|
||||
|
||||
@@ -16,70 +16,14 @@ import CoreGraphics
|
||||
|
||||
- Note: Uploading files and data are limited to 150MB, for now.
|
||||
*/
|
||||
open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
open class var type: String { return "Dropbox" }
|
||||
open let baseURL: URL?
|
||||
open var currentPath: String
|
||||
open class DropboxFileProvider: HTTPFileProvider {
|
||||
override open class var type: String { return "Dropbox" }
|
||||
|
||||
/// Dropbox RPC API URL, which is equal with [https://api.dropboxapi.com/2/](https://api.dropboxapi.com/2/)
|
||||
open let apiURL: URL
|
||||
/// Dropbox contents download/upload API URL, which is equal with [https://content.dropboxapi.com/2/](https://content.dropboxapi.com/2/)
|
||||
open let contentURL: URL
|
||||
|
||||
open var dispatch_queue: DispatchQueue
|
||||
open var operation_queue: OperationQueue {
|
||||
willSet {
|
||||
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
|
||||
}
|
||||
}
|
||||
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open var credential: URLCredential? {
|
||||
didSet {
|
||||
sessionDelegate?.credential = self.credential
|
||||
}
|
||||
}
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool
|
||||
public var validatingCache: Bool
|
||||
|
||||
fileprivate var _session: URLSession?
|
||||
internal fileprivate(set) var sessionDelegate: SessionDelegate?
|
||||
public var session: URLSession {
|
||||
get {
|
||||
if _session == nil {
|
||||
self.sessionDelegate = SessionDelegate(fileProvider: self)
|
||||
let config = URLSessionConfiguration.default
|
||||
config.urlCache = cache
|
||||
config.requestCachePolicy = .returnCacheDataElseLoad
|
||||
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: self.operation_queue)
|
||||
_session!.sessionDescription = UUID().uuidString
|
||||
initEmptySessionHandler(_session!.sessionDescription!)
|
||||
}
|
||||
return _session!
|
||||
}
|
||||
|
||||
set {
|
||||
assert(newValue.delegate is SessionDelegate, "session instances should have a SessionDelegate instance as delegate.")
|
||||
_session = newValue
|
||||
if session.sessionDescription?.isEmpty ?? true {
|
||||
_session?.sessionDescription = UUID().uuidString
|
||||
}
|
||||
self.sessionDelegate = newValue.delegate as? SessionDelegate
|
||||
initEmptySessionHandler(_session!.sessionDescription!)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var _longpollSession: URLSession?
|
||||
internal var longpollSession: URLSession {
|
||||
if _longpollSession == nil {
|
||||
let config = URLSessionConfiguration.default
|
||||
config.timeoutIntervalForRequest = 600
|
||||
_longpollSession = URLSession(configuration: config, delegate: nil, delegateQueue: nil)
|
||||
}
|
||||
return _longpollSession!
|
||||
}
|
||||
|
||||
/**
|
||||
Initializer for Dropbox provider with given client ID and Token.
|
||||
These parameters must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide).
|
||||
@@ -91,24 +35,9 @@ open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
- Parameter cache: A URLCache to cache downloaded files and contents.
|
||||
*/
|
||||
public init(credential: URLCredential?, cache: URLCache? = nil) {
|
||||
self.baseURL = nil
|
||||
self.currentPath = ""
|
||||
self.useCache = false
|
||||
self.validatingCache = true
|
||||
self.cache = cache
|
||||
self.credential = credential
|
||||
|
||||
self.apiURL = URL(string: "https://api.dropboxapi.com/2/")!
|
||||
self.contentURL = URL(string: "https://content.dropboxapi.com/2/")!
|
||||
|
||||
#if swift(>=3.1)
|
||||
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
|
||||
#else
|
||||
let queueLabel = "FileProvider.\(type(of: self).type)"
|
||||
#endif
|
||||
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
|
||||
operation_queue = OperationQueue()
|
||||
operation_queue.name = "\(queueLabel).Operation"
|
||||
super.init(baseURL: nil, credential: credential, cache: cache)
|
||||
}
|
||||
|
||||
public required convenience init?(coder aDecoder: NSCoder) {
|
||||
@@ -118,18 +47,7 @@ open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
|
||||
}
|
||||
|
||||
public func encode(with aCoder: NSCoder) {
|
||||
aCoder.encode(self.credential, forKey: "credential")
|
||||
aCoder.encode(self.currentPath, forKey: "currentPath")
|
||||
aCoder.encode(self.useCache, forKey: "useCache")
|
||||
aCoder.encode(self.validatingCache, forKey: "validatingCache")
|
||||
}
|
||||
|
||||
public static var supportsSecureCoding: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
open func copy(with zone: NSZone? = nil) -> Any {
|
||||
override open func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = DropboxFileProvider(credential: self.credential, cache: self.cache)
|
||||
copy.currentPath = self.currentPath
|
||||
copy.delegate = self.delegate
|
||||
@@ -139,27 +57,14 @@ open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
return copy
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let sessionuuid = _session?.sessionDescription {
|
||||
removeSessionHandler(for: sessionuuid)
|
||||
}
|
||||
|
||||
if fileProviderCancelTasksOnInvalidating {
|
||||
_session?.invalidateAndCancel()
|
||||
} else {
|
||||
_session?.finishTasksAndInvalidate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
open override func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
list(path, progress: progress) { (contents, cursor, error) in
|
||||
completionHandler(contents, error)
|
||||
}
|
||||
}
|
||||
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
open override func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
@@ -182,7 +87,7 @@ open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
open override func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
let url = URL(string: "users/get_space_usage", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
@@ -199,7 +104,7 @@ 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)) -> Progress? {
|
||||
open override 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 {
|
||||
@@ -228,45 +133,57 @@ open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
return progress
|
||||
}
|
||||
|
||||
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
|
||||
open override func isReachable(completionHandler: @escaping (Bool) -> Void) {
|
||||
self.storageProperties { total, _ in
|
||||
completionHandler(total > 0)
|
||||
}
|
||||
}
|
||||
|
||||
open weak var fileOperationDelegate: FileOperationDelegate?
|
||||
}
|
||||
|
||||
extension DropboxFileProvider: FileProviderOperations {
|
||||
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) -> Progress? {
|
||||
return doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
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) -> Progress? {
|
||||
return doOperation(.remove(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
fileprivate func doOperation(_ operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
|
||||
return nil
|
||||
override func request(for operation: FileOperationType, overwrite: Bool, attributes: [URLResourceKey : Any]) -> URLRequest {
|
||||
// content operations
|
||||
var request: URLRequest
|
||||
switch operation {
|
||||
case .copy(source: let source, destination: let dest) where dest.lowercased().hasPrefix("file://"):
|
||||
let url = URL(string: "files/download", relativeTo: contentURL)!
|
||||
request = URLRequest(url: url)
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
request.set(dropboxArgKey: ["path": correctPath(source)! as NSString])
|
||||
case .fetch(let path):
|
||||
let url = URL(string: "files/download", relativeTo: contentURL)!
|
||||
request = URLRequest(url: url)
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
request.set(dropboxArgKey: ["path": correctPath(path)! as NSString])
|
||||
case .copy(source: let source, destination: let dest) where source.lowercased().hasPrefix("file://"):
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: URL = URL(string: "files/upload", relativeTo: contentURL)!
|
||||
requestDictionary["path"] = correctPath(dest) as NSString?
|
||||
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
|
||||
requestDictionary["client_modified"] = (attributes[.contentModificationDateKey] as? Date)?.rfc3339utc() as NSString?
|
||||
request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
request.set(contentType: .stream)
|
||||
request.set(dropboxArgKey: requestDictionary)
|
||||
case .modify(let path):
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: URL = URL(string: "files/upload", relativeTo: contentURL)!
|
||||
requestDictionary["path"] = correctPath(path) as NSString?
|
||||
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
|
||||
requestDictionary["client_modified"] = (attributes[.contentModificationDateKey] as? Date)?.rfc3339utc() as NSString?
|
||||
request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
request.set(contentType: .stream)
|
||||
request.set(dropboxArgKey: requestDictionary)
|
||||
default: // modify, link, fetch
|
||||
return self.apiRequest(for: operation)
|
||||
}
|
||||
|
||||
let progress = Progress(totalUnitCount: 1)
|
||||
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
func apiRequest(for operation: FileOperationType) -> URLRequest {
|
||||
let url: String
|
||||
guard let sourcePath = operation.source else { return nil }
|
||||
let sourcePath = operation.source
|
||||
let destPath = operation.destination
|
||||
switch operation {
|
||||
case .create:
|
||||
@@ -278,7 +195,7 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
case .remove:
|
||||
url = "files/delete"
|
||||
default: // modify, link, fetch
|
||||
return nil
|
||||
fatalError("Unimplemented operation \(operation.description) in \(#file)")
|
||||
}
|
||||
var request = URLRequest(url: URL(string: url, relativeTo: apiURL)!)
|
||||
request.httpMethod = "POST"
|
||||
@@ -292,162 +209,27 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
requestDictionary["path"] = correctPath(sourcePath) as NSString?
|
||||
}
|
||||
request.httpBody = Data(jsonDictionary: requestDictionary)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var serverError: FileProviderDropboxError?
|
||||
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 progress
|
||||
return request
|
||||
}
|
||||
|
||||
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 {
|
||||
completionHandler?(self.throwError(localFile.path, code: URLError.fileIsDirectory))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
return upload_simple(toPath, localFile: localFile, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
override func serverError(with code: FileProviderHTTPErrorCode, path: String?, data: Data?) -> FileProviderHTTPError {
|
||||
return FileProviderDropboxError(code: code, path: path ?? "", errorDescription: data.flatMap({ String(data: $0, encoding: .utf8) }))
|
||||
}
|
||||
|
||||
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] = { error in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
override func upload_simple(_ targetPath: String, request: URLRequest, data: Data?, localFile: URL?, 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)
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(operation, error: error)
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
do {
|
||||
try FileManager.default.moveItem(at: tempURL, to: destURL)
|
||||
completionHandler?(nil)
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
}
|
||||
}
|
||||
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 progress
|
||||
|
||||
return super.upload_simple(targetPath, request: request, data: data, localFile: localFile, operation: operation, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
extension DropboxFileProvider: FileProviderReadWrite {
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
do {
|
||||
let data = try Data(contentsOf: tempURL)
|
||||
completionHandler(data, nil)
|
||||
} catch let e {
|
||||
completionHandler(nil, e)
|
||||
}
|
||||
}
|
||||
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 progress
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
// FIXME: remove 150MB restriction
|
||||
return upload_simple(path, data: data ?? Data(), overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
extension DropboxFileProvider {
|
||||
/*
|
||||
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
|
||||
/* There is two ways to monitor folders changing in Dropbox. Either using webooks
|
||||
@@ -680,5 +462,3 @@ extension DropboxFileProvider: ExtendedFileProvider {
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
extension DropboxFileProvider: FileProvider { }
|
||||
|
||||
@@ -126,63 +126,6 @@ internal extension DropboxFileProvider {
|
||||
task.resume()
|
||||
}
|
||||
|
||||
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)
|
||||
completionHandler?(error)
|
||||
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)!
|
||||
requestDictionary["path"] = correctPath(targetPath) as NSString?
|
||||
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
|
||||
requestDictionary["client_modified"] = modifiedDate.rfc3339utc() as NSString
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
request.set(contentType: .stream)
|
||||
request.set(dropboxArgKey: requestDictionary)
|
||||
let task: URLSessionUploadTask
|
||||
if let data = data {
|
||||
task = session.uploadTask(with: request, from: data)
|
||||
} else if let localFile = localFile {
|
||||
task = session.uploadTask(with: request, fromFile: localFile)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak self] error in
|
||||
var responseError: FileProviderDropboxError?
|
||||
if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
// 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(.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 progress
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
@@ -261,14 +204,4 @@ internal extension DropboxFileProvider {
|
||||
}
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
func delegateNotify(_ operation: FileOperationType, error: Error?) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if error == nil {
|
||||
self.delegate?.fileproviderSucceed(self, operation: operation)
|
||||
} else {
|
||||
self.delegate?.fileproviderFailed(self, operation: operation)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
guard let sourcePath = opType.source else { return nil }
|
||||
let sourcePath = opType.source
|
||||
let destPath = opType.destination
|
||||
|
||||
let command: String
|
||||
@@ -455,7 +455,7 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
private func fallbackCopy(_ opType: FileOperationType, progress: Progress, completionHandler: SimpleCompletionHandler) {
|
||||
guard let sourcePath = opType.source else { return }
|
||||
let sourcePath = opType.source
|
||||
guard let destPath = opType.destination else { return }
|
||||
|
||||
let localURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).appendingPathExtension("tmp")
|
||||
@@ -482,7 +482,7 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
private func fallbackRemove(_ opType: FileOperationType, progress: Progress, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
|
||||
guard let sourcePath = opType.source else { return }
|
||||
let sourcePath = opType.source
|
||||
|
||||
self.execute(command: "SITE RMDIR \(ftpPath(sourcePath))", on: task) { (response, error) in
|
||||
if let error = error {
|
||||
@@ -521,7 +521,7 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
private func fallbackRecursiveRemove(_ opType: FileOperationType, progress: Progress, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
|
||||
guard let sourcePath = opType.source else { return }
|
||||
let sourcePath = opType.source
|
||||
|
||||
_ = self.recursiveList(path: sourcePath, useMLST: true, completionHandler: { (contents, error) in
|
||||
if let error = error {
|
||||
|
||||
@@ -956,10 +956,10 @@ public enum FileOperationType: CustomStringConvertible {
|
||||
}
|
||||
|
||||
/// Path of subjecting file.
|
||||
public var source: String? {
|
||||
guard let reflect = Mirror(reflecting: self).children.first?.value else { return nil }
|
||||
public var source: String {
|
||||
let reflect = Mirror(reflecting: self).children.first!.value
|
||||
let mirror = Mirror(reflecting: reflect)
|
||||
return reflect as? String ?? mirror.children.first?.value as? String
|
||||
return reflect as? String ?? mirror.children.first?.value as! String
|
||||
}
|
||||
|
||||
/// Path of subjecting file.
|
||||
|
||||
@@ -51,8 +51,10 @@ public extension URLResourceKey {
|
||||
}
|
||||
|
||||
public extension ProgressUserInfoKey {
|
||||
/// **FileProvider** returns associated `FileProviderOperationType`
|
||||
public static let fileProvderOperationTypeKey = ProgressUserInfoKey("FilesProviderOperationTypeKey")
|
||||
public static let startingTimeKey = ProgressUserInfoKey("NSProgressstartingTimeKey")
|
||||
/// **FileProvider** returns start date/time of operation
|
||||
public static let startingTimeKey = ProgressUserInfoKey("NSProgressStartingTimeKey")
|
||||
}
|
||||
|
||||
internal extension URL {
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
//
|
||||
// HTTPFileProvider.swift
|
||||
// FilesProvider
|
||||
//
|
||||
// Created by Amir Abbas Mousavian.
|
||||
// Copyright © 2017 Mousavian. Distributed under MIT license.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
Allows accessing to Dropbox stored files. This provider doesn't cache or save files internally, however you can
|
||||
set `useCache` and `cache` properties to use Foundation `NSURLCache` system.
|
||||
|
||||
- Note: Uploading files and data are limited to 150MB, for now.
|
||||
*/
|
||||
open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite {
|
||||
open class var type: String { fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.") }
|
||||
open let baseURL: URL?
|
||||
open var currentPath: String
|
||||
|
||||
open var dispatch_queue: DispatchQueue
|
||||
open var operation_queue: OperationQueue {
|
||||
willSet {
|
||||
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
|
||||
}
|
||||
}
|
||||
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open var credential: URLCredential? {
|
||||
didSet {
|
||||
sessionDelegate?.credential = self.credential
|
||||
}
|
||||
}
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool
|
||||
public var validatingCache: Bool
|
||||
|
||||
fileprivate var _session: URLSession?
|
||||
internal fileprivate(set) var sessionDelegate: SessionDelegate?
|
||||
public var session: URLSession {
|
||||
get {
|
||||
if _session == nil {
|
||||
self.sessionDelegate = SessionDelegate(fileProvider: self)
|
||||
let config = URLSessionConfiguration.default
|
||||
config.urlCache = cache
|
||||
config.requestCachePolicy = .returnCacheDataElseLoad
|
||||
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: self.operation_queue)
|
||||
_session!.sessionDescription = UUID().uuidString
|
||||
initEmptySessionHandler(_session!.sessionDescription!)
|
||||
}
|
||||
return _session!
|
||||
}
|
||||
|
||||
set {
|
||||
assert(newValue.delegate is SessionDelegate, "session instances should have a SessionDelegate instance as delegate.")
|
||||
_session = newValue
|
||||
if session.sessionDescription?.isEmpty ?? true {
|
||||
_session?.sessionDescription = UUID().uuidString
|
||||
}
|
||||
self.sessionDelegate = newValue.delegate as? SessionDelegate
|
||||
initEmptySessionHandler(_session!.sessionDescription!)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var _longpollSession: URLSession?
|
||||
internal var longpollSession: URLSession {
|
||||
if _longpollSession == nil {
|
||||
let config = URLSessionConfiguration.default
|
||||
config.timeoutIntervalForRequest = 600
|
||||
_longpollSession = URLSession(configuration: config, delegate: nil, delegateQueue: nil)
|
||||
}
|
||||
return _longpollSession!
|
||||
}
|
||||
|
||||
public init(baseURL: URL?, credential: URLCredential?, cache: URLCache?) {
|
||||
self.baseURL = baseURL
|
||||
self.currentPath = ""
|
||||
self.useCache = false
|
||||
self.validatingCache = true
|
||||
self.cache = cache
|
||||
self.credential = credential
|
||||
|
||||
#if swift(>=3.1)
|
||||
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
|
||||
#else
|
||||
let queueLabel = "FileProvider.\(type(of: self).type)"
|
||||
#endif
|
||||
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
|
||||
operation_queue = OperationQueue()
|
||||
operation_queue.name = "\(queueLabel).Operation"
|
||||
}
|
||||
|
||||
public required convenience init?(coder aDecoder: NSCoder) {
|
||||
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
|
||||
}
|
||||
|
||||
public func encode(with aCoder: NSCoder) {
|
||||
aCoder.encode(self.baseURL, forKey: "baseURL")
|
||||
aCoder.encode(self.credential, forKey: "credential")
|
||||
aCoder.encode(self.currentPath, forKey: "currentPath")
|
||||
aCoder.encode(self.useCache, forKey: "useCache")
|
||||
aCoder.encode(self.validatingCache, forKey: "validatingCache")
|
||||
}
|
||||
|
||||
public static var supportsSecureCoding: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
open func copy(with zone: NSZone? = nil) -> Any {
|
||||
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let sessionuuid = _session?.sessionDescription {
|
||||
removeSessionHandler(for: sessionuuid)
|
||||
}
|
||||
|
||||
if fileProviderCancelTasksOnInvalidating {
|
||||
_session?.invalidateAndCancel()
|
||||
} else {
|
||||
_session?.finishTasksAndInvalidate()
|
||||
}
|
||||
}
|
||||
|
||||
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
|
||||
}
|
||||
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
|
||||
}
|
||||
|
||||
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
|
||||
}
|
||||
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
|
||||
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
|
||||
}
|
||||
|
||||
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
|
||||
self.storageProperties { total, _ in
|
||||
completionHandler(total > 0)
|
||||
}
|
||||
}
|
||||
|
||||
open weak var fileOperationDelegate: FileOperationDelegate?
|
||||
|
||||
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) -> Progress? {
|
||||
return doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
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) -> Progress? {
|
||||
return doOperation(.remove(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
internal func request(for operation: FileOperationType, overwrite: Bool = false, attributes: [URLResourceKey: Any] = [:]) -> URLRequest {
|
||||
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
|
||||
}
|
||||
|
||||
internal func serverError(with code: FileProviderHTTPErrorCode, path: String?, data: Data?) -> FileProviderHTTPError {
|
||||
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
|
||||
}
|
||||
|
||||
internal func multiStatusHandler(source: String, data: Data, completionHandler: SimpleCompletionHandler) -> Void {
|
||||
// WebDAV will override this function
|
||||
}
|
||||
|
||||
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 request = self.request(for: operation)
|
||||
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var serverError: FileProviderHTTPError?
|
||||
if let response = response as? HTTPURLResponse, response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
|
||||
serverError = self.serverError(with: code, path: operation.source, data: data)
|
||||
}
|
||||
|
||||
if let response = response as? HTTPURLResponse, FileProviderHTTPErrorCode(rawValue: response.statusCode) == .multiStatus, let data = data {
|
||||
self.multiStatusHandler(source: operation.source, data: data, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
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 progress
|
||||
}
|
||||
|
||||
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 {
|
||||
completionHandler?(self.throwError(localFile.path, code: URLError.fileIsDirectory))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let operation = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let request = self.request(for: operation, overwrite: overwrite)
|
||||
return upload_simple(toPath, request: request, localFile: localFile, operation: operation, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let operation = FileOperationType.copy(source: path, destination: destURL.absoluteString)
|
||||
let request = self.request(for: operation)
|
||||
return self.download_simple(path: path, request: request, operation: operation, completionHandler: { [weak self] (tempURL, error) in
|
||||
if let error = error {
|
||||
completionHandler?(error)
|
||||
self?.delegateNotify(operation, error: error)
|
||||
return
|
||||
}
|
||||
|
||||
guard let tempURL = tempURL else {
|
||||
completionHandler?(error)
|
||||
self?.delegateNotify(operation, error: error)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try FileManager.default.moveItem(at: tempURL, to: destURL)
|
||||
completionHandler?(nil)
|
||||
self?.delegateNotify(operation, error: nil)
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
self?.delegateNotify(operation, error: e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let operation = FileOperationType.fetch(path: path)
|
||||
var request = self.request(for: operation)
|
||||
request.set(rangeWithOffset: offset, length: length)
|
||||
return self.download_simple(path: path, request: request, operation: operation, completionHandler: { (tempURL, error) in
|
||||
if let error = error {
|
||||
completionHandler(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
guard let tempURL = tempURL else {
|
||||
completionHandler(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let data = try Data(contentsOf: tempURL)
|
||||
completionHandler(data, nil)
|
||||
} catch let e {
|
||||
completionHandler(nil, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let operation = FileOperationType.modify(path: path)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
let request = self.request(for: operation, overwrite: overwrite, attributes: [.contentModificationDateKey: Date()])
|
||||
return upload_simple(path, request: request, data: data ?? Data(), operation: operation, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
internal func upload_simple(_ targetPath: String, request: URLRequest, data: Data? = nil, localFile: URL? = nil, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let size = data?.count ?? Int((try? localFile?.resourceValues(forKeys: [.fileSizeKey]))??.fileSize ?? -1)
|
||||
|
||||
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 task: URLSessionUploadTask
|
||||
if let data = data {
|
||||
task = session.uploadTask(with: request, from: data)
|
||||
} else if let localFile = localFile {
|
||||
task = session.uploadTask(with: request, fromFile: localFile)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak self] error in
|
||||
var responseError: FileProviderHTTPError?
|
||||
if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
// We can't fetch server result from delegate!
|
||||
responseError = self?.serverError(with: rCode, path: targetPath, data: nil)
|
||||
}
|
||||
if !(responseError == nil && error == nil) {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler?(responseError ?? error)
|
||||
self?.delegateNotify(operation, 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 progress
|
||||
}
|
||||
|
||||
internal func download_simple(path: String, request: URLRequest, operation: FileOperationType, completionHandler: @escaping ((_ tempURL: URL?, _ error: Error?) -> Void)) -> Progress? {
|
||||
var progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
|
||||
|
||||
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
|
||||
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 : FileProviderHTTPError? = code != nil ? self.serverError(with: code!, path: path, data: errorData) : nil
|
||||
if serverError != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
completionHandler(nil, serverError)
|
||||
return
|
||||
}
|
||||
|
||||
completionHandler(tempURL, nil)
|
||||
}
|
||||
task.taskDescription = operation.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 progress
|
||||
}
|
||||
}
|
||||
|
||||
extension HTTPFileProvider {
|
||||
internal func delegateNotify(_ operation: FileOperationType, error: Error?) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if error == nil {
|
||||
self.delegate?.fileproviderSucceed(self, operation: operation)
|
||||
} else {
|
||||
self.delegate?.fileproviderFailed(self, operation: operation)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extension HTTPFileProvider: FileProvider { }
|
||||
@@ -175,6 +175,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
|
||||
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)
|
||||
progress.setUserInfoObject(self.url(of: path), forKey: .fileURLKey)
|
||||
|
||||
dispatch_queue.async {
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
@@ -288,9 +289,10 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
}
|
||||
}
|
||||
|
||||
guard let sourcePath = opType.source else { return nil }
|
||||
let sourcePath = opType.source
|
||||
let destPath = opType.destination
|
||||
let source: URL = urlofpath(path: sourcePath)
|
||||
progress.setUserInfoObject(source, forKey: .fileURLKey)
|
||||
|
||||
let dest: URL?
|
||||
if let destPath = destPath {
|
||||
@@ -403,13 +405,14 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
@discardableResult
|
||||
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? {
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
let url = self.url(of: path)
|
||||
|
||||
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.setUserInfoObject(url, forKey: .fileURLKey)
|
||||
progress.totalUnitCount = url.fileSize
|
||||
|
||||
let operationHandler: (URL) -> Void = { url in
|
||||
@@ -461,14 +464,15 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
}
|
||||
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
let url = self.url(of: path)
|
||||
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
|
||||
progress.kind = .file
|
||||
progress.isCancellable = false
|
||||
progress.setUserInfoObject(url, forKey: .fileURLKey)
|
||||
progress.setUserInfoObject(Progress.FileOperationKind.receiving, forKey: .fileOperationKindKey)
|
||||
|
||||
let url = self.url(of: path)
|
||||
|
||||
let operationHandler: (URL) -> Void = { url in
|
||||
guard let handle = FileHandle(forReadingAtPath: url.path) else {
|
||||
self.dispatch_queue.async {
|
||||
|
||||
@@ -17,59 +17,10 @@ import CoreGraphics
|
||||
|
||||
- Note: Uploading files and data are limited to 100MB, for now.
|
||||
*/
|
||||
open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
open class var type: String { return "OneDrive" }
|
||||
open let baseURL: URL?
|
||||
open class OneDriveFileProvider: HTTPFileProvider {
|
||||
override open class var type: String { return "OneDrive" }
|
||||
/// Drive name for user, default is `root`. Changing its value will effect on new operations.
|
||||
open var drive: String
|
||||
/// Generated storage url from server url and drive name
|
||||
open var currentPath: String
|
||||
|
||||
open var dispatch_queue: DispatchQueue
|
||||
open var operation_queue: OperationQueue {
|
||||
willSet {
|
||||
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
|
||||
}
|
||||
}
|
||||
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open var credential: URLCredential? {
|
||||
didSet {
|
||||
sessionDelegate?.credential = self.credential
|
||||
}
|
||||
}
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool
|
||||
public var validatingCache: Bool
|
||||
|
||||
fileprivate var _session: URLSession?
|
||||
internal fileprivate(set) var sessionDelegate: SessionDelegate?
|
||||
public var session: URLSession {
|
||||
get {
|
||||
if _session == nil {
|
||||
self.sessionDelegate = SessionDelegate(fileProvider: self)
|
||||
let queue = OperationQueue()
|
||||
//queue.underlyingQueue = dispatch_queue
|
||||
let config = URLSessionConfiguration.default
|
||||
config.urlCache = cache
|
||||
config.requestCachePolicy = .returnCacheDataElseLoad
|
||||
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: queue)
|
||||
_session?.sessionDescription = UUID().uuidString
|
||||
initEmptySessionHandler(_session!.sessionDescription!)
|
||||
}
|
||||
return _session!
|
||||
}
|
||||
|
||||
set {
|
||||
assert(newValue.delegate is SessionDelegate, "session instances should have a SessionDelegate instance as delegate.")
|
||||
_session = newValue
|
||||
if session.sessionDescription?.isEmpty ?? true {
|
||||
_session?.sessionDescription = UUID().uuidString
|
||||
}
|
||||
self.sessionDelegate = newValue.delegate as? SessionDelegate
|
||||
initEmptySessionHandler(_session!.sessionDescription!)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Initializer for Onedrive provider with given client ID and Token.
|
||||
@@ -87,22 +38,9 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
*/
|
||||
public init(credential: URLCredential?, serverURL: URL? = nil, drive: String = "root", cache: URLCache? = nil) {
|
||||
let baseURL = serverURL?.absoluteURL ?? URL(string: "https://api.onedrive.com/")!
|
||||
self.baseURL = baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")
|
||||
let refinedBaseURL = baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")
|
||||
self.drive = drive
|
||||
self.currentPath = ""
|
||||
self.useCache = false
|
||||
self.validatingCache = true
|
||||
self.cache = cache
|
||||
self.credential = credential
|
||||
|
||||
#if swift(>=3.1)
|
||||
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
|
||||
#else
|
||||
let queueLabel = "FileProvider.\(type(of: self).type)"
|
||||
#endif
|
||||
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
|
||||
operation_queue = OperationQueue()
|
||||
operation_queue.name = "\(queueLabel).Operation"
|
||||
super.init(baseURL: refinedBaseURL, credential: credential, cache: cache)
|
||||
}
|
||||
|
||||
public required convenience init?(coder aDecoder: NSCoder) {
|
||||
@@ -114,20 +52,12 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
|
||||
}
|
||||
|
||||
open func encode(with aCoder: NSCoder) {
|
||||
aCoder.encode(self.credential, forKey: "credential")
|
||||
aCoder.encode(self.baseURL, forKey: "baseURL")
|
||||
open override func encode(with aCoder: NSCoder) {
|
||||
super.encode(with: aCoder)
|
||||
aCoder.encode(self.drive, forKey: "drive")
|
||||
aCoder.encode(self.currentPath, forKey: "currentPath")
|
||||
aCoder.encode(self.useCache, forKey: "useCache")
|
||||
aCoder.encode(self.validatingCache, forKey: "validatingCache")
|
||||
}
|
||||
|
||||
public static var supportsSecureCoding: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
open func copy(with zone: NSZone? = nil) -> Any {
|
||||
open override func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = OneDriveFileProvider(credential: self.credential, serverURL: self.baseURL, drive: self.drive, cache: self.cache)
|
||||
copy.currentPath = self.currentPath
|
||||
copy.delegate = self.delegate
|
||||
@@ -137,25 +67,13 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
return copy
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let sessionuuid = _session?.sessionDescription {
|
||||
removeSessionHandler(for: sessionuuid)
|
||||
}
|
||||
|
||||
if fileProviderCancelTasksOnInvalidating {
|
||||
_session?.invalidateAndCancel()
|
||||
} else {
|
||||
_session?.finishTasksAndInvalidate()
|
||||
}
|
||||
}
|
||||
|
||||
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
open override func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
list(path) { (contents, cursor, error) in
|
||||
completionHandler(contents, error)
|
||||
}
|
||||
}
|
||||
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
open override func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
var request = URLRequest(url: url(of: path))
|
||||
request.httpMethod = "GET"
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
@@ -174,7 +92,7 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
open override func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
var request = URLRequest(url: url())
|
||||
request.httpMethod = "GET"
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
@@ -190,12 +108,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)) -> Progress? {
|
||||
open override 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 nil }
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(url(of: path), forKey: .fileURLKey)
|
||||
search(path, query: finalQueryStr, progress: progress, foundItem: { (file) in
|
||||
if query.evaluate(with: file.mapPredicate()) {
|
||||
foundFiles.append(file)
|
||||
@@ -235,7 +154,7 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
return driveURL.appendingPathComponent(rpath)
|
||||
}
|
||||
|
||||
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
|
||||
open override func isReachable(completionHandler: @escaping (Bool) -> Void) {
|
||||
var request = URLRequest(url: url())
|
||||
request.httpMethod = "HEAD"
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
@@ -246,215 +165,71 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
task.resume()
|
||||
}
|
||||
|
||||
open weak var fileOperationDelegate: FileOperationDelegate?
|
||||
}
|
||||
|
||||
extension OneDriveFileProvider: FileProviderOperations {
|
||||
|
||||
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) -> Progress? {
|
||||
return doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
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) -> Progress? {
|
||||
return doOperation(.remove(path: path), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
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))
|
||||
override func request(for operation: FileOperationType, overwrite: Bool = false, attributes: [URLResourceKey : Any] = [:]) -> URLRequest {
|
||||
let method: String
|
||||
let url: URL
|
||||
switch operation {
|
||||
case .create:
|
||||
request.httpMethod = "CREATE"
|
||||
case .copy:
|
||||
request.httpMethod = "POST"
|
||||
case .move:
|
||||
request.httpMethod = "PATCH"
|
||||
case .remove:
|
||||
request.httpMethod = "DELETE"
|
||||
default: // modify, link, fetch
|
||||
return nil
|
||||
case .fetch(path: let path):
|
||||
method = "GET"
|
||||
url = self.url(of: path, modifier: "content")
|
||||
case .modify(path: let path):
|
||||
method = "PUT"
|
||||
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
|
||||
url = self.url(of: path, modifier: "content\(queryStr)")
|
||||
case .create(path: let path):
|
||||
method = "CREATE"
|
||||
url = self.url(of: path)
|
||||
case .copy(let source, let dest) where !source.hasPrefix("file://") && !dest.hasPrefix("file://"):
|
||||
method = "POST"
|
||||
url = self.url(of: source)
|
||||
case .copy(let source, let dest) where source.hasPrefix("file://"):
|
||||
method = "PUT"
|
||||
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
|
||||
url = self.url(of: dest, modifier: "content\(queryStr)")
|
||||
case .copy(let source, let dest) where dest.hasPrefix("file://"):
|
||||
method = "GET"
|
||||
url = self.url(of: source, modifier: "content")
|
||||
case .move(source: let source, destination: _):
|
||||
method = "PATCH"
|
||||
url = self.url(of: source)
|
||||
case .remove(path: let path):
|
||||
method = "DELETE"
|
||||
url = self.url(of: path)
|
||||
default: // link
|
||||
fatalError("Unimplemented operation \(operation.description) in \(#file)")
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = method
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
if let dest = correctPath(destPath) as NSString? {
|
||||
if let dest = correctPath(operation.destination) as NSString?, !dest.hasPrefix("file://") {
|
||||
request.set(contentType: .json)
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
requestDictionary["parentReference"] = ("/drive/\(drive):" + dest.deletingLastPathComponent) as NSString
|
||||
requestDictionary["name"] = dest.lastPathComponent as NSString
|
||||
request.httpBody = Data(jsonDictionary: requestDictionary)
|
||||
}
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var serverError: FileProviderOneDriveError?
|
||||
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 progress
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
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 {
|
||||
completionHandler?(self.throwError(localFile.path, code: URLError.fileIsDirectory))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
}
|
||||
return upload_simple(toPath, localFile: localFile, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
override func serverError(with code: FileProviderHTTPErrorCode, path: String?, data: Data?) -> FileProviderHTTPError {
|
||||
return FileProviderOneDriveError(code: code, path: path ?? "", errorDescription: data.flatMap({ String(data: $0, encoding: .utf8) }))
|
||||
}
|
||||
|
||||
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] = { error in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
override func upload_simple(_ targetPath: String, request: URLRequest, data: Data?, localFile: URL?, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let size = data?.count ?? Int((try? localFile?.resourceValues(forKeys: [.fileSizeKey]))??.fileSize ?? -1)
|
||||
if size > 100 * 1024 * 1024 {
|
||||
let error = FileProviderOneDriveError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(operation, error: error)
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
do {
|
||||
try FileManager.default.moveItem(at: tempURL, to: destURL)
|
||||
completionHandler?(nil)
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
}
|
||||
}
|
||||
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 progress
|
||||
|
||||
return super.upload_simple(targetPath, request: request, data: data, localFile: localFile, operation: operation, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
extension OneDriveFileProvider: FileProviderReadWrite {
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
do {
|
||||
let data = try Data(contentsOf: tempURL)
|
||||
completionHandler(data, nil)
|
||||
} catch let e {
|
||||
completionHandler(nil, e)
|
||||
}
|
||||
}
|
||||
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 progress
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
// FIXME: remove 150MB restriction
|
||||
return upload_simple(path, data: data ?? Data(), overwrite: overwrite, operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
extension OneDriveFileProvider {
|
||||
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
|
||||
/* There is two ways to monitor folders changing in OneDrive. Either using webooks
|
||||
* which means you have to implement a server to translate it to push notifications
|
||||
@@ -559,5 +334,3 @@ extension OneDriveFileProvider: ExtendedFileProvider {
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
extension OneDriveFileProvider: FileProvider { }
|
||||
|
||||
@@ -114,58 +114,6 @@ 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) -> 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)
|
||||
completionHandler?(error)
|
||||
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)
|
||||
request.httpMethod = "PUT"
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
request.set(contentType: .stream)
|
||||
let task: URLSessionUploadTask
|
||||
if let data = data {
|
||||
task = session.uploadTask(with: request, from: data)
|
||||
} else if let localFile = localFile {
|
||||
task = session.uploadTask(with: request, fromFile: localFile)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak self] error in
|
||||
var responseError: FileProviderOneDriveError?
|
||||
if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
// 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 progress
|
||||
}
|
||||
|
||||
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
|
||||
@@ -282,14 +230,4 @@ internal extension OneDriveFileProvider {
|
||||
|
||||
return (dic, keys)
|
||||
}
|
||||
|
||||
func delegateNotify(_ operation: FileOperationType, error: Error?) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if error == nil {
|
||||
self.delegate?.fileproviderSucceed(self, operation: operation)
|
||||
} else {
|
||||
self.delegate?.fileproviderFailed(self, operation: operation)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Regular → Executable
+14
-7
@@ -109,19 +109,21 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession
|
||||
case #keyPath(URLSessionTask.countOfBytesExpectedToReceive), #keyPath(URLSessionTask.countOfBytesExpectedToSend):
|
||||
progress.totalUnitCount = newVal
|
||||
default:
|
||||
break
|
||||
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
|
||||
}
|
||||
}
|
||||
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 task is URLSessionDownloadTask {
|
||||
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived))
|
||||
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive))
|
||||
}
|
||||
if task is URLSessionUploadTask {
|
||||
task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent))
|
||||
//task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToSend))
|
||||
}
|
||||
if !(error == nil && task is URLSessionDownloadTask) {
|
||||
let completionHandler = completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] ?? nil
|
||||
completionHandler?(error)
|
||||
@@ -177,8 +179,11 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession
|
||||
switch op {
|
||||
case .create(path: let path):
|
||||
if path.hasSuffix("/") { return }
|
||||
break
|
||||
case .modify:
|
||||
break
|
||||
case .copy(source: let source, destination: _) where source.hasPrefix("file://"):
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
@@ -193,6 +198,8 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession
|
||||
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
||||
self.didReceivedData?(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
|
||||
|
||||
if totalBytesExpectedToWrite == NSURLSessionTransferSizeUnknown { return }
|
||||
|
||||
guard let json = downloadTask.taskDescription?.deserializeJSON(),
|
||||
let op = FileOperationType(json: json), let fileProvider = fileProvider else {
|
||||
return
|
||||
|
||||
@@ -20,57 +20,9 @@ import CoreGraphics
|
||||
in case of using this class with unencrypted HTTP connection.
|
||||
[Read this to know how](http://iosdevtips.co/post/121756573323/ios-9-xcode-7-http-connect-server-error).
|
||||
*/
|
||||
open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
open class var type: String { return "WebDAV" }
|
||||
open let baseURL: URL?
|
||||
open var currentPath: String
|
||||
|
||||
open var dispatch_queue: DispatchQueue
|
||||
open var operation_queue: OperationQueue {
|
||||
willSet {
|
||||
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
|
||||
}
|
||||
}
|
||||
|
||||
public weak var delegate: FileProviderDelegate?
|
||||
open class WebDAVFileProvider: HTTPFileProvider {
|
||||
override open class var type: String { return "WebDAV" }
|
||||
public var credentialType: HTTPAuthenticationType = .digest
|
||||
open var credential: URLCredential? {
|
||||
didSet {
|
||||
sessionDelegate?.credential = credential
|
||||
}
|
||||
}
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool
|
||||
public var validatingCache: Bool
|
||||
|
||||
fileprivate var _session: URLSession?
|
||||
fileprivate var sessionDelegate: SessionDelegate?
|
||||
public var session: URLSession {
|
||||
get {
|
||||
if _session == nil {
|
||||
self.sessionDelegate = SessionDelegate(fileProvider: self)
|
||||
let queue = OperationQueue()
|
||||
//queue.underlyingQueue = dispatch_queue
|
||||
let config = URLSessionConfiguration.default
|
||||
config.urlCache = cache
|
||||
config.requestCachePolicy = .returnCacheDataElseLoad
|
||||
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDownloadDelegate?, delegateQueue: queue)
|
||||
_session?.sessionDescription = UUID().uuidString
|
||||
initEmptySessionHandler(_session!.sessionDescription!)
|
||||
}
|
||||
return _session!
|
||||
}
|
||||
|
||||
set {
|
||||
assert(newValue.delegate is SessionDelegate, "session instances should have a SessionDelegate instance as delegate.")
|
||||
_session = newValue
|
||||
if session.sessionDescription?.isEmpty ?? true {
|
||||
_session?.sessionDescription = UUID().uuidString
|
||||
}
|
||||
self.sessionDelegate = newValue.delegate as? SessionDelegate
|
||||
initEmptySessionHandler(_session!.sessionDescription!)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes WebDAV provider.
|
||||
@@ -84,21 +36,8 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
if !["http", "https"].contains(baseURL.uw_scheme.lowercased()) {
|
||||
return nil
|
||||
}
|
||||
self.baseURL = (baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")).absoluteURL
|
||||
self.currentPath = ""
|
||||
self.useCache = false
|
||||
self.validatingCache = true
|
||||
self.cache = cache
|
||||
self.credential = credential
|
||||
|
||||
#if swift(>=3.1)
|
||||
let queueLabel = "FileProvider.\(Swift.type(of: self).type)"
|
||||
#else
|
||||
let queueLabel = "FileProvider.\(type(of: self).type)"
|
||||
#endif
|
||||
dispatch_queue = DispatchQueue(label: queueLabel, attributes: .concurrent)
|
||||
operation_queue = OperationQueue()
|
||||
operation_queue.name = "\(queueLabel).Operation"
|
||||
let refinedBaseURL = (baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")).absoluteURL
|
||||
super.init(baseURL: refinedBaseURL, credential: credential, cache: cache)
|
||||
}
|
||||
|
||||
public required convenience init?(coder aDecoder: NSCoder) {
|
||||
@@ -112,19 +51,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
|
||||
}
|
||||
|
||||
open func encode(with aCoder: NSCoder) {
|
||||
aCoder.encode(self.baseURL, forKey: "baseURL")
|
||||
aCoder.encode(self.credential, forKey: "credential")
|
||||
aCoder.encode(self.currentPath, forKey: "currentPath")
|
||||
aCoder.encode(self.useCache, forKey: "useCache")
|
||||
aCoder.encode(self.validatingCache, forKey: "validatingCache")
|
||||
}
|
||||
|
||||
public static var supportsSecureCoding: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
open func copy(with zone: NSZone? = nil) -> Any {
|
||||
override open func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = WebDAVFileProvider(baseURL: self.baseURL!, credential: self.credential, cache: self.cache)!
|
||||
copy.currentPath = self.currentPath
|
||||
copy.delegate = self.delegate
|
||||
@@ -134,19 +61,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
return copy
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let sessionuuid = _session?.sessionDescription {
|
||||
removeSessionHandler(for: sessionuuid)
|
||||
}
|
||||
|
||||
if fileProviderCancelTasksOnInvalidating {
|
||||
_session?.invalidateAndCancel()
|
||||
} else {
|
||||
_session?.finishTasksAndInvalidate()
|
||||
}
|
||||
}
|
||||
|
||||
open func contentsOfDirectory(path: String, completionHandler: @escaping (([FileObject], Error?) -> Void)) {
|
||||
override open func contentsOfDirectory(path: String, completionHandler: @escaping (([FileObject], Error?) -> Void)) {
|
||||
self.contentsOfDirectory(path: path, including: [], completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@@ -190,7 +105,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
})
|
||||
}
|
||||
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
override open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
self.attributesOfItem(path: path, including: [], completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@@ -230,7 +145,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
})
|
||||
}
|
||||
|
||||
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
override open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
|
||||
// Not all WebDAV clients implements RFC2518 which allows geting storage quota.
|
||||
// In this case you won't get error. totalSize is NSURLSessionTransferSizeUnknown
|
||||
// and used space is zero.
|
||||
@@ -258,7 +173,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
})
|
||||
}
|
||||
|
||||
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
|
||||
override 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"
|
||||
@@ -267,6 +182,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<D:allprop/></D:propfind>".data(using: .utf8)
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.setUserInfoObject(url, forKey: .fileURLKey)
|
||||
let task = session.dataTask(with: request) { (data, response, error) in
|
||||
// FIXME: paginating results
|
||||
var responseError: FileProviderWebDavError?
|
||||
@@ -299,7 +215,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
return progress
|
||||
}
|
||||
|
||||
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
|
||||
override open func isReachable(completionHandler: @escaping (Bool) -> Void) {
|
||||
var request = URLRequest(url: baseURL!)
|
||||
request.httpMethod = "PROPFIND"
|
||||
request.setValue("0", forHTTPHeaderField: "Depth")
|
||||
@@ -313,311 +229,72 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
})
|
||||
}
|
||||
|
||||
open weak var fileOperationDelegate: FileOperationDelegate?
|
||||
}
|
||||
|
||||
extension WebDAVFileProvider: FileProviderOperations {
|
||||
@discardableResult
|
||||
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
|
||||
}
|
||||
return self.doOperation(operation: opType, overwrite: false, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
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
|
||||
}
|
||||
return self.doOperation(operation: opType, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
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
|
||||
}
|
||||
return self.doOperation(operation: opType, overwrite: overwrite, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
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
|
||||
}
|
||||
return self.doOperation(operation: opType, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
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)
|
||||
if let dest = opType.destination {
|
||||
request.setValue(url(of:dest).absoluteString, forHTTPHeaderField: "Destination")
|
||||
}
|
||||
switch opType {
|
||||
override func request(for operation: FileOperationType, overwrite: Bool = true, attributes: [URLResourceKey: Any] = [:]) -> URLRequest {
|
||||
let method: String
|
||||
let url: URL
|
||||
let sourceURL = self.url(of: operation.source)
|
||||
|
||||
switch operation {
|
||||
case .fetch:
|
||||
method = "GET"
|
||||
url = sourceURL
|
||||
case .create:
|
||||
request.httpMethod = "MKCOL"
|
||||
case .copy:
|
||||
request.httpMethod = "COPY"
|
||||
case .move:
|
||||
request.httpMethod = "MOVE"
|
||||
case .remove:
|
||||
request.httpMethod = "DELETE"
|
||||
default:
|
||||
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")
|
||||
}
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let response = response as? HTTPURLResponse, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
|
||||
if response.statusCode >= 300 {
|
||||
responseError = FileProviderWebDavError(code: code, path: source, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: sourceURL)
|
||||
}
|
||||
if code == .multiStatus, let data = data {
|
||||
let xresponses = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
|
||||
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
|
||||
if sourceURL.absoluteString.hasSuffix("/") {
|
||||
method = "MKCOL"
|
||||
url = sourceURL
|
||||
} else {
|
||||
progress.cancel()
|
||||
fallthrough
|
||||
}
|
||||
|
||||
if (response as? HTTPURLResponse)?.statusCode ?? 0 != FileProviderHTTPErrorCode.multiStatus.rawValue {
|
||||
completionHandler?(responseError ?? error)
|
||||
case .modify:
|
||||
method = "PUT"
|
||||
url = sourceURL
|
||||
break
|
||||
case .copy(let source, let dest):
|
||||
if source.hasPrefix("file://") {
|
||||
method = "PUT"
|
||||
url = self.url(of: dest)
|
||||
} else if dest.hasPrefix("file://") {
|
||||
method = "GET"
|
||||
url = sourceURL
|
||||
} else {
|
||||
method = "COPY"
|
||||
url = sourceURL
|
||||
}
|
||||
|
||||
self.delegateNotify(opType, error: responseError ?? error)
|
||||
})
|
||||
task.taskDescription = opType.json
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
case .move:
|
||||
method = "MOVE"
|
||||
url = sourceURL
|
||||
case .remove:
|
||||
method = "DELETE"
|
||||
url = sourceURL
|
||||
default:
|
||||
fatalError("Unimplemented operation \(operation.description) in \(#file)")
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return progress
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = method
|
||||
request.set(httpAuthentication: credential, with: credentialType)
|
||||
request.setValue(overwrite ? "T" : "F", forHTTPHeaderField: "Overwrite")
|
||||
if let dest = operation.destination, !dest.hasPrefix("file://") {
|
||||
request.setValue(self.url(of:dest).absoluteString, forHTTPHeaderField: "Destination")
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
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 {
|
||||
completionHandler?(self.throwError(localFile.path, code: URLError.fileIsDirectory))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
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 {
|
||||
request.setValue("F", forHTTPHeaderField: "Overwrite")
|
||||
}
|
||||
request.httpMethod = "PUT"
|
||||
request.set(httpAuthentication: credential, with: credentialType)
|
||||
let task = session.uploadTask(with: request, fromFile: localFile)
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak self] error in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
// 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 progress
|
||||
override func serverError(with code: FileProviderHTTPErrorCode, path: String?, data: Data?) -> FileProviderHTTPError {
|
||||
return FileProviderWebDavError(code: code, path: path ?? "", errorDescription: data.flatMap({ String(data: $0, encoding: .utf8) }), url: self.url(of: path))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
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] = { error in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
override func multiStatusHandler(source: String, data: Data, completionHandler: SimpleCompletionHandler) {
|
||||
let xresponses = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
|
||||
for xresponse in xresponses where (xresponse.status ?? 0) >= 300 {
|
||||
let code = xresponse.status.flatMap { FileProviderHTTPErrorCode(rawValue: $0) } ?? .internalServerError
|
||||
let error = FileProviderWebDavError(code: code, path: source, errorDescription: String(data: data, encoding: .utf8), url: self.url(of: source))
|
||||
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 serverError : FileProviderWebDavError? = code != nil ? FileProviderWebDavError(code: code!, path: path, errorDescription: code?.description, url: url) : nil
|
||||
completionHandler?(serverError)
|
||||
return
|
||||
}
|
||||
do {
|
||||
try FileManager.default.moveItem(at: tempURL, to: destURL)
|
||||
completionHandler?(nil)
|
||||
} catch let e {
|
||||
completionHandler?(e)
|
||||
}
|
||||
}
|
||||
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 progress
|
||||
}
|
||||
}
|
||||
|
||||
extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
@discardableResult
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
|
||||
if error != nil {
|
||||
progress.cancel()
|
||||
}
|
||||
|
||||
completionHandler(nil, error)
|
||||
}
|
||||
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
|
||||
completionHandler(nil, serverError)
|
||||
return
|
||||
}
|
||||
do {
|
||||
let data = try Data(contentsOf: tempURL)
|
||||
(self?.dispatch_queue ?? DispatchQueue.global()).async {
|
||||
completionHandler(data, nil)
|
||||
}
|
||||
} catch let e {
|
||||
completionHandler(nil, e)
|
||||
}
|
||||
}
|
||||
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 progress
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
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)
|
||||
request.httpMethod = "PUT"
|
||||
request.set(httpAuthentication: credential, with: credentialType)
|
||||
if !overwrite {
|
||||
request.setValue("F", forHTTPHeaderField: "Overwrite")
|
||||
}
|
||||
let task = session.uploadTask(with: request, from: data ?? Data())
|
||||
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak self] error in
|
||||
var responseError: FileProviderWebDavError?
|
||||
if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
// 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 progress
|
||||
}
|
||||
extension WebDAVFileProvider {
|
||||
|
||||
/*
|
||||
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
|
||||
@@ -715,22 +392,8 @@ extension WebDAVFileProvider: FileProviderSharing {
|
||||
}
|
||||
}
|
||||
|
||||
extension WebDAVFileProvider: FileProvider { }
|
||||
|
||||
// MARK: WEBDAV XML response implementation
|
||||
|
||||
internal extension WebDAVFileProvider {
|
||||
fileprivate func delegateNotify(_ operation: FileOperationType, error: Error?) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if error == nil {
|
||||
self.delegate?.fileproviderSucceed(self, operation: operation)
|
||||
} else {
|
||||
self.delegate?.fileproviderFailed(self, operation: operation)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct DavResponse {
|
||||
let href: URL
|
||||
let hrefString: String
|
||||
|
||||
Reference in New Issue
Block a user