From 5c2c56c44c037dee8d34e4f057d837c3cffd904b Mon Sep 17 00:00:00 2001 From: Amir Abbas Date: Mon, 3 Apr 2017 12:41:11 +0430 Subject: [PATCH] Fixed: Calling completion handler for upload task - Added including (file object properties) argument to WebDAV provider (resolves #31) --- FileProvider.podspec | 2 +- FileProvider.xcodeproj/project.pbxproj | 4 +- Sources/DropboxHelper.swift | 58 +++++------------ Sources/LocalHelper.swift | 2 +- Sources/OneDriveHelper.swift | 53 +++++---------- Sources/RemoteSession.swift | 8 +-- Sources/WebDAVFileProvider.swift | 90 ++++++++++++++++++++++---- 7 files changed, 115 insertions(+), 102 deletions(-) diff --git a/FileProvider.podspec b/FileProvider.podspec index d44c59a..be7463c 100644 --- a/FileProvider.podspec +++ b/FileProvider.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "FileProvider" - s.version = "0.15.0" + s.version = "0.15.1" s.summary = "FileManager replacement for Local and Remote (WebDAV/FTP/Dropbox/OneDrive/SMB2) files on iOS and macOS." # This description is used to generate tags and improve search results. diff --git a/FileProvider.xcodeproj/project.pbxproj b/FileProvider.xcodeproj/project.pbxproj index 4d1eab1..edd2694 100644 --- a/FileProvider.xcodeproj/project.pbxproj +++ b/FileProvider.xcodeproj/project.pbxproj @@ -621,7 +621,7 @@ 799396601D48B7BF00086753 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_VERSION_STRING = 0.15.0; + BUNDLE_VERSION_STRING = 0.15.1; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -651,7 +651,7 @@ 799396611D48B7BF00086753 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_VERSION_STRING = 0.15.0; + BUNDLE_VERSION_STRING = 0.15.1; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; diff --git a/Sources/DropboxHelper.swift b/Sources/DropboxHelper.swift index f057925..3133392 100644 --- a/Sources/DropboxHelper.swift +++ b/Sources/DropboxHelper.swift @@ -121,42 +121,8 @@ internal extension DropboxFileProvider { task.resume() } - func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? { - if data.count > 150 * 1024 * 1024 { - let error = FileProviderDropboxError(code: .payloadTooLarge, path: targetPath, errorDescription: nil) - completionHandler?(error) - self.delegateNotify(.create(path: targetPath), error: error) - return nil - } - var requestDictionary = [String: AnyObject]() - let url: URL - url = URL(string: "files/upload", relativeTo: contentURL)! - requestDictionary["path"] = correctPath(targetPath) as NSString? - requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString - requestDictionary["client_modified"] = modifiedDate.rfc3339utc() as NSString - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization") - request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") - request.setValue(String(jsonDictionary: requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg") - request.httpBody = data - let task = session.uploadTask(with: request, from: data) - completionHandlersForTasks[task.taskIdentifier] = completionHandler - dataCompletionHandlersForTasks[task.taskIdentifier] = { [weak self] data in - var responseError: FileProviderDropboxError? - if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) { - responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data, encoding: .utf8)) - } - completionHandler?(responseError) - self?.delegateNotify(.create(path: targetPath), error: responseError) - } - task.taskDescription = operation.json - task.resume() - return RemoteOperationHandle(operationType: operation, tasks: [task]) - } - - func upload_simple(_ targetPath: String, localFile: URL, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? { - let size = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1 + func upload_simple(_ targetPath: String, data: Data? = nil, localFile: URL? = nil, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? { + let size = data?.count ?? Int((try? localFile?.resourceValues(forKeys: [.fileSizeKey]))??.fileSize ?? -1) if size > 150 * 1024 * 1024 { let error = FileProviderDropboxError(code: .payloadTooLarge, path: targetPath, errorDescription: nil) completionHandler?(error) @@ -174,15 +140,23 @@ internal extension DropboxFileProvider { request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization") request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") request.setValue(String(jsonDictionary: requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg") - let task = session.uploadTask(with: request, fromFile: localFile) - completionHandlersForTasks[task.taskIdentifier] = completionHandler - dataCompletionHandlersForTasks[task.taskIdentifier] = { [weak self] data in + let task: URLSessionUploadTask + if let data = data { + task = session.uploadTask(with: request, from: data) + } else if let localFile = localFile { + task = session.uploadTask(with: request, fromFile: localFile) + } else { + return nil + } + + completionHandlersForTasks[task.taskIdentifier] = { [weak self] error in var responseError: FileProviderDropboxError? if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) { - responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data, encoding: .utf8)) + // We can't fetch server result from delegate! + responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: nil) } - completionHandler?(responseError) - self?.delegateNotify(.create(path: targetPath), error: responseError) + completionHandler?(responseError ?? error) + self?.delegateNotify(.create(path: targetPath), error: responseError ?? error) } task.taskDescription = operation.json task.resume() diff --git a/Sources/LocalHelper.swift b/Sources/LocalHelper.swift index 3f03453..9017285 100644 --- a/Sources/LocalHelper.swift +++ b/Sources/LocalHelper.swift @@ -37,7 +37,7 @@ public final class LocalFileObject: FileObject { /// Initiates a `LocalFileObject` with attributes of file in url. public convenience init?(fileWithURL fileURL: URL) { do { - let values = try fileURL.resourceValues(forKeys: [.nameKey, .fileSizeKey, .fileAllocatedSizeKey, .creationDateKey, .contentModificationDateKey, .fileResourceTypeKey, .isHiddenKey, .isWritableKey, .typeIdentifierKey, .generationIdentifierKey, .documentIdentifierKey]) + let values = try fileURL.resourceValues(forKeys: [.nameKey, .fileSizeKey, .totalFileSizeKey, .fileAllocatedSizeKey, .totalFileAllocatedSizeKey, .creationDateKey, .contentModificationDateKey, .fileResourceTypeKey, .isHiddenKey, .isWritableKey, .typeIdentifierKey, .generationIdentifierKey, .documentIdentifierKey]) let path = fileURL.relativePath.hasPrefix("/") ? fileURL.relativePath : "/" + fileURL.relativePath self.init(url: fileURL, name: values.name ?? fileURL.lastPathComponent, path: path) diff --git a/Sources/OneDriveHelper.swift b/Sources/OneDriveHelper.swift index 118b89e..03ede8c 100644 --- a/Sources/OneDriveHelper.swift +++ b/Sources/OneDriveHelper.swift @@ -113,37 +113,8 @@ internal extension OneDriveFileProvider { task.resume() } - func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? { - if data.count > 100 * 1024 * 1024 { - let error = FileProviderOneDriveError(code: .payloadTooLarge, path: targetPath, errorDescription: nil) - completionHandler?(error) - self.delegateNotify(.create(path: targetPath), error: error) - return nil - } - let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail" - let url = self.url(of: targetPath, modifier: "content\(queryStr)") - var request = URLRequest(url: url) - request.httpMethod = "PUT" - request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization") - request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") - request.httpBody = data - let task = session.uploadTask(with: request, from: data) - completionHandlersForTasks[task.taskIdentifier] = completionHandler - dataCompletionHandlersForTasks[task.taskIdentifier] = { [weak self] data in - var responseError: FileProviderOneDriveError? - if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) { - responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: String(data: data, encoding: .utf8)) - } - completionHandler?(responseError) - self?.delegateNotify(.create(path: targetPath), error: responseError) - } - task.taskDescription = operation.json - task.resume() - return RemoteOperationHandle(operationType: operation, tasks: [task]) - } - - func upload_simple(_ targetPath: String, localFile: URL, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? { - let size = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1 + func upload_simple(_ targetPath: String, data: Data? = nil , localFile: URL? = nil, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? { + let size = data?.count ?? (try? localFile?.resourceValues(forKeys: [.fileSizeKey]))??.fileSize ?? -1 if size > 100 * 1024 * 1024 { let error = FileProviderOneDriveError(code: .payloadTooLarge, path: targetPath, errorDescription: nil) completionHandler?(error) @@ -156,15 +127,23 @@ internal extension OneDriveFileProvider { request.httpMethod = "PUT" request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization") request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") - let task = session.uploadTask(with: request, fromFile: localFile) - completionHandlersForTasks[task.taskIdentifier] = completionHandler - dataCompletionHandlersForTasks[task.taskIdentifier] = { [weak self] data in + let task: URLSessionUploadTask + if let data = data { + task = session.uploadTask(with: request, from: data) + } else if let localFile = localFile { + task = session.uploadTask(with: request, fromFile: localFile) + } else { + return nil + } + + completionHandlersForTasks[task.taskIdentifier] = { [weak self] error in var responseError: FileProviderOneDriveError? if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) { - responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: String(data: data, encoding: .utf8)) + // We can't fetch server result from delegate! + responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: nil) } - completionHandler?(responseError) - self?.delegateNotify(.create(path: targetPath), error: responseError) + completionHandler?(responseError ?? error) + self?.delegateNotify(.create(path: targetPath), error: responseError ?? error) } task.taskDescription = operation.json task.resume() diff --git a/Sources/RemoteSession.swift b/Sources/RemoteSession.swift index 08895b1..c46550a 100644 --- a/Sources/RemoteSession.swift +++ b/Sources/RemoteSession.swift @@ -111,11 +111,9 @@ class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDeleg // codebeat:disable[ARITY] func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - if error != nil { - let completionHandler = completionHandlersForTasks[task.taskIdentifier] ?? nil - completionHandler?(error) - completionHandlersForTasks.removeValue(forKey: task.taskIdentifier) - } + let completionHandler = completionHandlersForTasks[task.taskIdentifier] ?? nil + completionHandler?(error) + completionHandlersForTasks.removeValue(forKey: task.taskIdentifier) } func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { diff --git a/Sources/WebDAVFileProvider.swift b/Sources/WebDAVFileProvider.swift index 566cb6c..65922da 100644 --- a/Sources/WebDAVFileProvider.swift +++ b/Sources/WebDAVFileProvider.swift @@ -120,14 +120,29 @@ open class WebDAVFileProvider: FileProviderBasicRemote { } } - open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) { + public func contentsOfDirectory(path: String, completionHandler: @escaping (([FileObject], Error?) -> Void)) { + self.contentsOfDirectory(path: path, including: [], completionHandler: completionHandler) + } + + /** + Returns an Array of `FileObject`s identifying the the directory entries via asynchronous completion handler. + + If the directory contains no entries or an error is occured, this method will return the empty array. + + - Parameter path: path to target directory. If empty, `currentPath` value will be used. + - Parameter including: An array which determines which file properties should be considered to fetch. + - Parameter completionHandler: a closure with result of directory entries or error. + - `contents`: An array of `FileObject` identifying the the directory entries. + - `error`: Error returned by system. + */ + open func contentsOfDirectory(path: String, including: [URLResourceKey], completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) { let opType = FileOperationType.fetch(path: path) let url = self.url(of: path).appendingPathComponent("") var request = URLRequest(url: url) request.httpMethod = "PROPFIND" request.setValue("1", forHTTPHeaderField: "Depth") request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type") - request.httpBody = "\n\n".data(using: .utf8) + request.httpBody = "\n\n\(WebDavFileObject.propString(including))\n".data(using: .utf8) request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length") runDataTask(with: request, operationHandle: RemoteOperationHandle(operationType: opType, tasks: []), completionHandler: { (data, response, error) in var responseError: FileProviderWebDavError? @@ -149,12 +164,27 @@ open class WebDAVFileProvider: FileProviderBasicRemote { } open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) { + self.attributesOfItem(path: path, including: [], completionHandler: completionHandler) + } + + /** + Returns a `FileObject` containing the attributes of the item (file, directory, symlink, etc.) at the path in question via asynchronous completion handler. + + If the directory contains no entries or an error is occured, this method will return the empty `FileObject`. + + - Parameter path: path to target directory. If empty, `currentPath` value will be used. + - Parameter including: An array which determines which file properties should be considered to fetch. + - Parameter completionHandler: a closure with result of directory entries or error. + - `attributes`: A `FileObject` containing the attributes of the item. + - `error`: Error returned by system. + */ + open func attributesOfItem(path: String, including: [URLResourceKey], completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) { let url = self.url(of: path) var request = URLRequest(url: url) request.httpMethod = "PROPFIND" request.setValue("1", forHTTPHeaderField: "Depth") request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type") - request.httpBody = "\n\n".data(using: .utf8) + request.httpBody = "\n\n\(WebDavFileObject.propString(including))\n".data(using: .utf8) request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length") runDataTask(with: request, completionHandler: { (data, response, error) in var responseError: FileProviderWebDavError? @@ -356,14 +386,14 @@ extension WebDAVFileProvider: FileProviderOperations { } request.httpMethod = "PUT" let task = session.uploadTask(with: request, fromFile: localFile) - completionHandlersForTasks[task.taskIdentifier] = completionHandler - dataCompletionHandlersForTasks[task.taskIdentifier] = { [weak self] data in + completionHandlersForTasks[task.taskIdentifier] = { [weak self] error in var responseError: FileProviderWebDavError? if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) { - responseError = FileProviderWebDavError(code: rCode, path: toPath, errorDescription: String(data: data, encoding: .utf8), url: url) + // We can't fetch server result from delegate! + responseError = FileProviderWebDavError(code: rCode, path: toPath, errorDescription: nil, url: url) } - completionHandler?(responseError) - self?.delegateNotify(.create(path: toPath), error: responseError) + completionHandler?(responseError ?? error) + self?.delegateNotify(.create(path: toPath), error: responseError ?? error) } task.taskDescription = opType.json task.resume() @@ -444,14 +474,14 @@ extension WebDAVFileProvider: FileProviderReadWrite { request.setValue("F", forHTTPHeaderField: "Overwrite") } let task = session.uploadTask(with: request, from: data ?? Data()) - completionHandlersForTasks[task.taskIdentifier] = completionHandler - dataCompletionHandlersForTasks[task.taskIdentifier] = { [weak self] data in + completionHandlersForTasks[task.taskIdentifier] = { [weak self] error in var responseError: FileProviderWebDavError? if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) { - responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data, encoding: .utf8), url: url) + // We can't fetch server result from delegate! + responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: nil, url: url) } - completionHandler?(responseError) - self?.delegateNotify(.create(path: path), error: responseError) + completionHandler?(responseError ?? error) + self?.delegateNotify(.create(path: path), error: responseError ?? error) } task.taskDescription = opType.json task.resume() @@ -591,7 +621,7 @@ struct DavResponse { public final class WebDavFileObject: FileObject { internal init(_ davResponse: DavResponse) { let href = davResponse.href - let name = davResponse.prop["displayname"] ?? (davResponse.hrefString.removingPercentEncoding! as NSString).lastPathComponent + let name = davResponse.prop["displayname"] ?? davResponse.href.lastPathComponent let relativePath = href.relativePath let path = relativePath.hasPrefix("/") ? relativePath : ("/" + relativePath) super.init(url: href, name: name, path: path) @@ -623,6 +653,38 @@ public final class WebDavFileObject: FileObject { allValues[.entryTag] = newValue } } + + internal class func resourceKeyToDAVProp(_ key: URLResourceKey) -> String? { + switch key { + case URLResourceKey.fileSizeKey: + return "getcontentlength" + case URLResourceKey.creationDateKey: + return "creationdate" + case URLResourceKey.contentModificationDateKey: + return "getlastmodified" + case URLResourceKey.fileResourceTypeKey, URLResourceKey.mimeType: + return "getcontenttype" + case URLResourceKey.isHiddenKey: + return "ishidden" + case URLResourceKey.entryTag: + return "getetag" + default: + return nil + } + } + + internal class func propString(_ keys: [URLResourceKey]) -> String { + var propKeys = "" + for item in keys { + if let prop = WebDavFileObject.resourceKeyToDAVProp(item) { + propKeys += "" + } + } + if propKeys.isEmpty { + propKeys = "" + } + return propKeys + } } /// Error returned by WebDAV server when trying to access or do operations on a file or folder.