diff --git a/Sources/CloudFileProvider.swift b/Sources/CloudFileProvider.swift
index ccc9d61..f8dd61d 100644
--- a/Sources/CloudFileProvider.swift
+++ b/Sources/CloudFileProvider.swift
@@ -752,9 +752,6 @@ 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
diff --git a/Sources/DropboxFileProvider.swift b/Sources/DropboxFileProvider.swift
index 912fb4f..41db13a 100644
--- a/Sources/DropboxFileProvider.swift
+++ b/Sources/DropboxFileProvider.swift
@@ -16,7 +16,7 @@ import CoreGraphics
- Note: Uploading files and data are limited to 150MB, for now.
*/
-open class DropboxFileProvider: HTTPFileProvider {
+open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
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/)
@@ -133,12 +133,6 @@ open class DropboxFileProvider: HTTPFileProvider {
return progress
}
- open override func isReachable(completionHandler: @escaping (Bool) -> Void) {
- self.storageProperties { total, _ in
- completionHandler(total > 0)
- }
- }
-
override func request(for operation: FileOperationType, overwrite: Bool, attributes: [URLResourceKey : Any]) -> URLRequest {
// content operations
var request: URLRequest
@@ -227,9 +221,7 @@ open class DropboxFileProvider: HTTPFileProvider {
return super.upload_simple(targetPath, request: request, data: data, localFile: localFile, operation: operation, completionHandler: completionHandler)
}
-}
-extension DropboxFileProvider {
/*
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
/* There is two ways to monitor folders changing in Dropbox. Either using webooks
@@ -245,9 +237,7 @@ extension DropboxFileProvider {
}
*/
// TODO: Implement /get_account & /get_current_account
-}
-
-extension DropboxFileProvider: FileProviderSharing {
+
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
let url = URL(string: "files/get_temporary_link", relativeTo: apiURL)!
var request = URLRequest(url: url)
diff --git a/Sources/FTPFileProvider.swift b/Sources/FTPFileProvider.swift
index 8ed68cb..6ad87d7 100644
--- a/Sources/FTPFileProvider.swift
+++ b/Sources/FTPFileProvider.swift
@@ -12,7 +12,7 @@ import Foundation
Allows accessing to FTP files and directories. This provider doesn't cache or save files internally.
It's a complete reimplementation and doesn't use CFNetwork deprecated API.
*/
-open class FTPFileProvider: FileProviderBasicRemote {
+open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite {
open class var type: String { return "FTP" }
open let baseURL: URL?
open var currentPath: String
@@ -317,7 +317,7 @@ open class FTPFileProvider: FileProviderBasicRemote {
return progress
}
- public func url(of path: String?) -> URL {
+ open func url(of path: String?) -> URL {
let path = (path ?? self.currentPath).trimmingCharacters(in: CharacterSet(charactersIn: "/ ")).addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? (path ?? self.currentPath)
var baseUrlComponent = URLComponents(url: self.baseURL!, resolvingAgainstBaseURL: true)
@@ -326,7 +326,7 @@ open class FTPFileProvider: FileProviderBasicRemote {
return URL(string: path, relativeTo: baseUrlComponent?.url ?? baseURL) ?? baseUrlComponent?.url ?? baseURL!
}
- public func relativePathOf(url: URL) -> String {
+ open func relativePathOf(url: URL) -> String {
// check if url derieved from current base url
let relativePath = url.relativePath
if !relativePath.isEmpty, url.baseURL == self.baseURL {
@@ -347,9 +347,7 @@ open class FTPFileProvider: FileProviderBasicRemote {
}
open weak var fileOperationDelegate: FileOperationDelegate?
-}
-
-extension FTPFileProvider: 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)
@@ -367,210 +365,6 @@ extension FTPFileProvider: FileProviderOperations {
return doOperation(.remove(path: path), completionHandler: completionHandler)
}
- fileprivate func doOperation(_ opType: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
- guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
- return nil
- }
- let sourcePath = opType.source
- let destPath = opType.destination
-
- let command: String
- switch opType {
- case .create:
- command = "MKD \(ftpPath(sourcePath))"
- case .copy:
- command = "SITE CPFR \(ftpPath(sourcePath))\r\nSITE CPTO \(ftpPath(destPath!))"
- case .move:
- command = "RNFR \(ftpPath(sourcePath))\r\nRNTO \(ftpPath(destPath!))"
- case .remove:
- command = "DELE \(ftpPath(sourcePath))"
- case .link:
- command = "SITE SYMLINK \(ftpPath(sourcePath)) \(ftpPath(destPath!))"
- default: // modify, fetch
- return nil
- }
- let progress = Progress(totalUnitCount: 1)
- progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
- progress.kind = .file
- progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
-
- let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
- self.ftpLogin(task) { (error) in
- if let error = error {
- self.dispatch_queue.async {
- completionHandler?(error)
- self.delegateNotify(opType, error: error)
- }
- return
- }
-
- self.execute(command: command, on: task, completionHandler: { (response, error) in
- if let error = error {
- self.dispatch_queue.async {
- completionHandler?(error)
- self.delegateNotify(opType, error: error)
- }
- return
- }
-
- guard let response = response else {
- self.dispatch_queue.async {
- completionHandler?(error)
- self.delegateNotify(opType, error: self.throwError(sourcePath, code: URLError.badServerResponse))
- }
- return
- }
-
- let codes: [Int] = response.components(separatedBy: .newlines).flatMap({ $0.isEmpty ? nil : $0})
- .flatMap {
- let code = $0.components(separatedBy: .whitespaces).flatMap({ $0.isEmpty ? nil : $0}).first
- return code != nil ? Int(code!) : nil
- }
-
- if codes.filter({ (450..<560).contains($0) }).count > 0 {
- let errorCode: URLError.Code
- switch opType {
- case .create:
- errorCode = URLError.cannotCreateFile
- case .modify:
- errorCode = URLError.cannotWriteToFile
- case .copy:
- self.fallbackCopy(opType, progress: progress, completionHandler: completionHandler)
- return
- case .move:
- errorCode = URLError.cannotMoveFile
- case .remove:
- self.fallbackRemove(opType, progress: progress, on: task, completionHandler: completionHandler)
- return
- case .link:
- errorCode = URLError.cannotWriteToFile
- default:
- errorCode = URLError.cannotOpenFile
- }
- let error = self.throwError(sourcePath, code: errorCode)
- progress.cancel()
- self.dispatch_queue.async {
- completionHandler?(error)
- }
- self.delegateNotify(opType, error: error)
- return
- }
-
- progress.completedUnitCount = progress.totalUnitCount
- self.dispatch_queue.async {
- completionHandler?(nil)
- }
- self.delegateNotify(opType, error: nil)
- })
- }
-
- progress.cancellationHandler = { [weak task] in
- task?.cancel()
- }
- progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
- return progress
- }
-
- private func fallbackCopy(_ opType: FileOperationType, progress: Progress, completionHandler: SimpleCompletionHandler) {
- let sourcePath = opType.source
- guard let destPath = opType.destination else { return }
-
- let localURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).appendingPathExtension("tmp")
-
- progress.becomeCurrent(withPendingUnitCount: 1)
- _ = self.copyItem(path: sourcePath, toLocalURL: localURL) { (error) in
- if let error = error {
- self.dispatch_queue.async {
- completionHandler?(error)
- self.delegateNotify(opType, error: error)
- }
- return
- }
-
- progress.becomeCurrent(withPendingUnitCount: 1)
- _ = self.copyItem(localFile: localURL, to: destPath) { error in
- completionHandler?(nil)
- self.delegateNotify(opType, error: nil)
- }
- progress.resignCurrent()
- }
- progress.resignCurrent()
- return
- }
-
- private func fallbackRemove(_ opType: FileOperationType, progress: Progress, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
- let sourcePath = opType.source
-
- self.execute(command: "SITE RMDIR \(ftpPath(sourcePath))", on: task) { (response, error) in
- if let error = error {
- progress.cancel()
- self.dispatch_queue.async {
- completionHandler?(error)
- }
- self.delegateNotify(opType, error: error)
- return
- }
-
- guard let response = response else {
- progress.cancel()
- let error = self.throwError(sourcePath, code: URLError.badServerResponse)
- self.dispatch_queue.async {
- completionHandler?(error)
- }
- self.delegateNotify(opType, error: error)
- return
- }
-
- if response.hasPrefix("50") {
- self.fallbackRecursiveRemove(opType, progress: progress, on: task, completionHandler: completionHandler)
- return
- }
-
- var error: Error?
- if !response.hasPrefix("2") {
- error = self.throwError(sourcePath, code: URLError.cannotRemoveFile)
- }
- self.dispatch_queue.async {
- completionHandler?(error)
- }
- self.delegateNotify(opType, error: error)
- }
- }
-
- private func fallbackRecursiveRemove(_ opType: FileOperationType, progress: Progress, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
- let sourcePath = opType.source
-
- _ = self.recursiveList(path: sourcePath, useMLST: true, completionHandler: { (contents, error) in
- if let error = error {
- self.dispatch_queue.async {
- completionHandler?(error)
- self.delegateNotify(opType, error: error)
- }
- return
- }
-
- let recursiveProgress = Progress(parent: progress, userInfo: nil)
- recursiveProgress.totalUnitCount = Int64(contents.count)
- let sortedContents = contents.sorted(by: {
- $0.path.localizedStandardCompare($1.path) == .orderedDescending
- })
- var command = ""
- for file in sortedContents {
- command += (file.isDirectory ? "RMD \(self.ftpPath(file.path))" : "DELE \(self.ftpPath(file.path))") + "\r\n"
- }
- command += "RMD \(self.ftpPath(sourcePath))"
-
- self.execute(command: command, on: task, completionHandler: { (response, error) in
- recursiveProgress.completedUnitCount += 1
- self.dispatch_queue.async {
- completionHandler?(error)
- self.delegateNotify(opType, error: error)
- }
- // TODO: Digest response
- })
- })
- }
-
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 {
@@ -720,9 +514,7 @@ extension FTPFileProvider: FileProviderOperations {
}
return progress
}
-}
-
-extension FTPFileProvider: FileProviderReadWrite {
+
open func contents(path: String, completionHandler: @escaping ((Data?, Error?) -> Void)) -> Progress? {
let opType = FileOperationType.fetch(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
@@ -879,9 +671,7 @@ extension FTPFileProvider: FileProviderReadWrite {
return progress
}
-}
-
-public extension FTPFileProvider {
+
/**
Creates a symbolic link at the specified path that points to an item at the given path.
This method does not traverse symbolic links contained in destination path, making it possible
@@ -895,10 +685,216 @@ public extension FTPFileProvider {
- withDestinationPath: The path that contains the item to be pointed to by the link. In other words, this is the destination of the link.
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
*/
- public func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
+ open func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
let opType = FileOperationType.link(link: path, target: destPath)
_=self.doOperation(opType, completionHandler: completionHandler)
}
}
+extension FTPFileProvider {
+ fileprivate func doOperation(_ opType: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
+ guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
+ return nil
+ }
+ let sourcePath = opType.source
+ let destPath = opType.destination
+
+ let command: String
+ switch opType {
+ case .create:
+ command = "MKD \(ftpPath(sourcePath))"
+ case .copy:
+ command = "SITE CPFR \(ftpPath(sourcePath))\r\nSITE CPTO \(ftpPath(destPath!))"
+ case .move:
+ command = "RNFR \(ftpPath(sourcePath))\r\nRNTO \(ftpPath(destPath!))"
+ case .remove:
+ command = "DELE \(ftpPath(sourcePath))"
+ case .link:
+ command = "SITE SYMLINK \(ftpPath(sourcePath)) \(ftpPath(destPath!))"
+ default: // modify, fetch
+ return nil
+ }
+ let progress = Progress(totalUnitCount: 1)
+ progress.setUserInfoObject(opType, forKey: .fileProvderOperationTypeKey)
+ progress.kind = .file
+ progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey)
+
+ let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
+ self.ftpLogin(task) { (error) in
+ if let error = error {
+ self.dispatch_queue.async {
+ completionHandler?(error)
+ self.delegateNotify(opType, error: error)
+ }
+ return
+ }
+
+ self.execute(command: command, on: task, completionHandler: { (response, error) in
+ if let error = error {
+ self.dispatch_queue.async {
+ completionHandler?(error)
+ self.delegateNotify(opType, error: error)
+ }
+ return
+ }
+
+ guard let response = response else {
+ self.dispatch_queue.async {
+ completionHandler?(error)
+ self.delegateNotify(opType, error: self.throwError(sourcePath, code: URLError.badServerResponse))
+ }
+ return
+ }
+
+ let codes: [Int] = response.components(separatedBy: .newlines).flatMap({ $0.isEmpty ? nil : $0})
+ .flatMap {
+ let code = $0.components(separatedBy: .whitespaces).flatMap({ $0.isEmpty ? nil : $0}).first
+ return code != nil ? Int(code!) : nil
+ }
+
+ if codes.filter({ (450..<560).contains($0) }).count > 0 {
+ let errorCode: URLError.Code
+ switch opType {
+ case .create:
+ errorCode = URLError.cannotCreateFile
+ case .modify:
+ errorCode = URLError.cannotWriteToFile
+ case .copy:
+ self.fallbackCopy(opType, progress: progress, completionHandler: completionHandler)
+ return
+ case .move:
+ errorCode = URLError.cannotMoveFile
+ case .remove:
+ self.fallbackRemove(opType, progress: progress, on: task, completionHandler: completionHandler)
+ return
+ case .link:
+ errorCode = URLError.cannotWriteToFile
+ default:
+ errorCode = URLError.cannotOpenFile
+ }
+ let error = self.throwError(sourcePath, code: errorCode)
+ progress.cancel()
+ self.dispatch_queue.async {
+ completionHandler?(error)
+ }
+ self.delegateNotify(opType, error: error)
+ return
+ }
+
+ progress.completedUnitCount = progress.totalUnitCount
+ self.dispatch_queue.async {
+ completionHandler?(nil)
+ }
+ self.delegateNotify(opType, error: nil)
+ })
+ }
+
+ progress.cancellationHandler = { [weak task] in
+ task?.cancel()
+ }
+ progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
+ return progress
+ }
+
+ private func fallbackCopy(_ opType: FileOperationType, progress: Progress, completionHandler: SimpleCompletionHandler) {
+ let sourcePath = opType.source
+ guard let destPath = opType.destination else { return }
+
+ let localURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).appendingPathExtension("tmp")
+
+ progress.becomeCurrent(withPendingUnitCount: 1)
+ _ = self.copyItem(path: sourcePath, toLocalURL: localURL) { (error) in
+ if let error = error {
+ self.dispatch_queue.async {
+ completionHandler?(error)
+ self.delegateNotify(opType, error: error)
+ }
+ return
+ }
+
+ progress.becomeCurrent(withPendingUnitCount: 1)
+ _ = self.copyItem(localFile: localURL, to: destPath) { error in
+ completionHandler?(nil)
+ self.delegateNotify(opType, error: nil)
+ }
+ progress.resignCurrent()
+ }
+ progress.resignCurrent()
+ return
+ }
+
+ private func fallbackRemove(_ opType: FileOperationType, progress: Progress, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
+ let sourcePath = opType.source
+
+ self.execute(command: "SITE RMDIR \(ftpPath(sourcePath))", on: task) { (response, error) in
+ if let error = error {
+ progress.cancel()
+ self.dispatch_queue.async {
+ completionHandler?(error)
+ }
+ self.delegateNotify(opType, error: error)
+ return
+ }
+
+ guard let response = response else {
+ progress.cancel()
+ let error = self.throwError(sourcePath, code: URLError.badServerResponse)
+ self.dispatch_queue.async {
+ completionHandler?(error)
+ }
+ self.delegateNotify(opType, error: error)
+ return
+ }
+
+ if response.hasPrefix("50") {
+ self.fallbackRecursiveRemove(opType, progress: progress, on: task, completionHandler: completionHandler)
+ return
+ }
+
+ var error: Error?
+ if !response.hasPrefix("2") {
+ error = self.throwError(sourcePath, code: URLError.cannotRemoveFile)
+ }
+ self.dispatch_queue.async {
+ completionHandler?(error)
+ }
+ self.delegateNotify(opType, error: error)
+ }
+ }
+
+ private func fallbackRecursiveRemove(_ opType: FileOperationType, progress: Progress, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
+ let sourcePath = opType.source
+
+ _ = self.recursiveList(path: sourcePath, useMLST: true, completionHandler: { (contents, error) in
+ if let error = error {
+ self.dispatch_queue.async {
+ completionHandler?(error)
+ self.delegateNotify(opType, error: error)
+ }
+ return
+ }
+
+ let recursiveProgress = Progress(parent: progress, userInfo: nil)
+ recursiveProgress.totalUnitCount = Int64(contents.count)
+ let sortedContents = contents.sorted(by: {
+ $0.path.localizedStandardCompare($1.path) == .orderedDescending
+ })
+ var command = ""
+ for file in sortedContents {
+ command += (file.isDirectory ? "RMD \(self.ftpPath(file.path))" : "DELE \(self.ftpPath(file.path))") + "\r\n"
+ }
+ command += "RMD \(self.ftpPath(sourcePath))"
+
+ self.execute(command: command, on: task, completionHandler: { (response, error) in
+ recursiveProgress.completedUnitCount += 1
+ self.dispatch_queue.async {
+ completionHandler?(error)
+ self.delegateNotify(opType, error: error)
+ }
+ // TODO: Digest response
+ })
+ })
+ }
+}
+
extension FTPFileProvider: FileProvider { }
diff --git a/Sources/HTTPFileProvider.swift b/Sources/HTTPFileProvider.swift
index 19d6c54..9297141 100644
--- a/Sources/HTTPFileProvider.swift
+++ b/Sources/HTTPFileProvider.swift
@@ -64,6 +64,7 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
}
fileprivate var _longpollSession: URLSession?
+ /// This session has extended timeout up to 10 minutes, suitable for monitoring.
internal var longpollSession: URLSession {
if _longpollSession == nil {
let config = URLSessionConfiguration.default
@@ -73,6 +74,14 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
return _longpollSession!
}
+ /**
+ This is parent initializer for subclasses. Using this method on `HTTPFileProvider` will fail as `type` is not implemented.
+
+ - Parameters:
+ - baseURL: Location of WebDAV server.
+ - credential: An `URLCredential` object with `user` and `password`.
+ - cache: A URLCache to cache downloaded files and contents.
+ */
public init(baseURL: URL?, credential: URLCredential?, cache: URLCache?) {
self.baseURL = baseURL
self.currentPath = ""
@@ -164,57 +173,6 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
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 {
@@ -298,7 +256,58 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
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 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
+ }
+
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)
diff --git a/Sources/LocalFileProvider.swift b/Sources/LocalFileProvider.swift
index 06b0f75..81d6c1f 100644
--- a/Sources/LocalFileProvider.swift
+++ b/Sources/LocalFileProvider.swift
@@ -565,9 +565,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
open func isRegisteredForNotification(path: String) -> Bool {
return monitors.map( { self.relativePathOf(url: $0.url) } ).contains(path.trimmingCharacters(in: CharacterSet(charactersIn: "/")))
}
-}
-
-public extension LocalFileProvider {
+
/**
Creates a symbolic link at the specified path that points to an item at the given path.
This method does not traverse symbolic links contained in destination path, making it possible
@@ -579,7 +577,7 @@ public extension LocalFileProvider {
- withDestinationPath: The path that contains the item to be pointed to by the link. In other words, this is the destination of the link.
- completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
*/
- public func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
+ open func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
operation_queue.addOperation {
do {
try self.opFileManager.createSymbolicLink(at: self.url(of: path), withDestinationURL: self.url(of: destPath))
@@ -601,7 +599,7 @@ public extension LocalFileProvider {
/// - Parameters:
/// - path: The path of a file or directory.
/// - completionHandler: Returns destination url of given symbolic link, or an `Error` object if it fails.
- public func destination(ofSymbolicLink path: String, completionHandler: @escaping (_ url: URL?, _ error: Error?) -> Void) {
+ open func destination(ofSymbolicLink path: String, completionHandler: @escaping (_ url: URL?, _ error: Error?) -> Void) {
dispatch_queue.async {
do {
let destPath = try self.opFileManager.destinationOfSymbolicLink(atPath: self.url(of: path).path)
diff --git a/Sources/OneDriveFileProvider.swift b/Sources/OneDriveFileProvider.swift
index 661e0a2..29257f8 100644
--- a/Sources/OneDriveFileProvider.swift
+++ b/Sources/OneDriveFileProvider.swift
@@ -17,7 +17,7 @@ import CoreGraphics
- Note: Uploading files and data are limited to 100MB, for now.
*/
-open class OneDriveFileProvider: HTTPFileProvider {
+open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
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
@@ -227,9 +227,7 @@ open class OneDriveFileProvider: HTTPFileProvider {
return super.upload_simple(targetPath, request: request, data: data, localFile: localFile, operation: operation, 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
@@ -244,7 +242,7 @@ extension OneDriveFileProvider {
}
}
-extension OneDriveFileProvider: FileProviderSharing {
+extension OneDriveFileProvider {
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
var request = URLRequest(url: self.url(of: path, modifier: "action.createLink"))
request.httpMethod = "POST"
diff --git a/Sources/WebDAVFileProvider.swift b/Sources/WebDAVFileProvider.swift
index 9a27bee..ca2e8e9 100644
--- a/Sources/WebDAVFileProvider.swift
+++ b/Sources/WebDAVFileProvider.swift
@@ -20,7 +20,7 @@ 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: HTTPFileProvider {
+open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
override open class var type: String { return "WebDAV" }
public var credentialType: HTTPAuthenticationType = .digest
@@ -230,6 +230,38 @@ open class WebDAVFileProvider: HTTPFileProvider {
})
}
+ open func publicLink(to path: String, completionHandler: @escaping ((URL?, FileObject?, Date?, Error?) -> Void)) {
+ guard self.baseURL?.host?.contains("dav.yandex.") ?? false else {
+ dispatch_queue.async {
+ completionHandler(nil, nil, nil, self.throwError(path, code: URLError.resourceUnavailable))
+ }
+ return
+ }
+
+ let url = self.url(of: path)
+ var request = URLRequest(url: url)
+ request.httpMethod = "PROPPATCH"
+ request.set(httpAuthentication: credential, with: credentialType)
+ request.set(contentType: .xml)
+ let body = "\n\ntrue\n\n"
+ request.httpBody = body.data(using: .utf8)
+ request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
+ runDataTask(with: request, completionHandler: { (data, response, error) in
+ var responseError: FileProviderWebDavError?
+ if let code = (response as? HTTPURLResponse)?.statusCode, code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
+ responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
+ }
+ if let data = data {
+ let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
+ if let urlStr = xresponse.first?.prop["public_url"], let url = URL(string: urlStr) {
+ completionHandler(url, nil, nil, nil)
+ return
+ }
+ }
+ completionHandler(nil, nil, nil, responseError ?? error)
+ })
+ }
+
override func request(for operation: FileOperationType, overwrite: Bool = true, attributes: [URLResourceKey: Any] = [:]) -> URLRequest {
let method: String
let url: URL
@@ -294,8 +326,6 @@ open class WebDAVFileProvider: HTTPFileProvider {
completionHandler?(error)
}
}
-}
-extension WebDAVFileProvider {
/*
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
@@ -359,40 +389,6 @@ extension WebDAVFileProvider: ExtendedFileProvider {
}
}
-extension WebDAVFileProvider: FileProviderSharing {
- open func publicLink(to path: String, completionHandler: @escaping ((URL?, FileObject?, Date?, Error?) -> Void)) {
- guard self.baseURL?.host?.contains("dav.yandex.") ?? false else {
- dispatch_queue.async {
- completionHandler(nil, nil, nil, self.throwError(path, code: URLError.resourceUnavailable))
- }
- return
- }
-
- let url = self.url(of: path)
- var request = URLRequest(url: url)
- request.httpMethod = "PROPPATCH"
- request.set(httpAuthentication: credential, with: credentialType)
- request.set(contentType: .xml)
- let body = "\n\ntrue\n\n"
- request.httpBody = body.data(using: .utf8)
- request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
- runDataTask(with: request, completionHandler: { (data, response, error) in
- var responseError: FileProviderWebDavError?
- if let code = (response as? HTTPURLResponse)?.statusCode, code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
- responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
- }
- if let data = data {
- let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
- if let urlStr = xresponse.first?.prop["public_url"], let url = URL(string: urlStr) {
- completionHandler(url, nil, nil, nil)
- return
- }
- }
- completionHandler(nil, nil, nil, responseError ?? error)
- })
- }
-}
-
// MARK: WEBDAV XML response implementation
struct DavResponse {