Making protocol method implementations overridable
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+213
-217
@@ -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 { }
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 = "<propertyupdate xmlns=\"DAV:\">\n<set><prop>\n<public_url xmlns=\"urn:yandex:disk:meta\">true</public_url>\n</prop></set>\n</propertyupdate>"
|
||||
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 = "<propertyupdate xmlns=\"DAV:\">\n<set><prop>\n<public_url xmlns=\"urn:yandex:disk:meta\">true</public_url>\n</prop></set>\n</propertyupdate>"
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user