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 {