From ea5de2e2aa39d378cf1a2dba9df97e336b52902e Mon Sep 17 00:00:00 2001 From: Amir Abbas Date: Mon, 3 Apr 2017 18:50:13 +0430 Subject: [PATCH] Added progress for content(path:) method - Fixed issue with colliding handlers between sessions. - Sessions can be set. - SessionDelegate class is now public. --- FileProvider.podspec | 2 +- FileProvider.xcodeproj/project.pbxproj | 4 +- Sources/DropboxFileProvider.swift | 79 ++++++++++-------- Sources/FTPFileProvider.swift | 108 +++++++++++-------------- Sources/FileObject.swift | 6 -- Sources/FileProvider.swift | 11 +-- Sources/LocalFileProvider.swift | 6 -- Sources/OneDriveFileProvide.swift | 65 ++++++++++----- Sources/OneDriveHelper.swift | 2 +- Sources/RemoteSession.swift | 90 +++++++++++++-------- Sources/WebDAVFileProvider.swift | 78 ++++++++++++------ 11 files changed, 262 insertions(+), 189 deletions(-) diff --git a/FileProvider.podspec b/FileProvider.podspec index be7463c..fb4e3f1 100644 --- a/FileProvider.podspec +++ b/FileProvider.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "FileProvider" - s.version = "0.15.1" + s.version = "0.15.2" 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 edd2694..4477f64 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.1; + BUNDLE_VERSION_STRING = 0.15.2; 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.1; + BUNDLE_VERSION_STRING = 0.15.2; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; diff --git a/Sources/DropboxFileProvider.swift b/Sources/DropboxFileProvider.swift index fabf96a..fa02ebb 100644 --- a/Sources/DropboxFileProvider.swift +++ b/Sources/DropboxFileProvider.swift @@ -42,16 +42,33 @@ open class DropboxFileProvider: FileProviderBasicRemote { fileprivate var _session: URLSession? fileprivate var sessionDelegate: SessionDelegate? public var session: URLSession { - if _session == nil { - self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential) - let config = URLSessionConfiguration.default - config.urlCache = cache - config.requestCachePolicy = .returnCacheDataElseLoad - _session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: self.operation_queue) + get { + if _session == nil { + self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential) + let config = URLSessionConfiguration.default + config.urlCache = cache + config.requestCachePolicy = .returnCacheDataElseLoad + _session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: self.operation_queue) + _session!.sessionDescription = UUID().uuidString + initEmptySessionHandler(_session!.sessionDescription!) + } + return _session! + } + + set { + assert(newValue.delegate is SessionDelegate, "session instances should have a SessionDelegate instance as delegate.") + _session = newValue + if session.sessionDescription?.isEmpty ?? true { + _session?.sessionDescription = UUID().uuidString + } + initEmptySessionHandler(_session!.sessionDescription!) } - return _session! } + internal var completionHandlersForTasks = [Int: SimpleCompletionHandler]() + internal var downloadCompletionHandlersForTasks = [Int: (URL) -> Void]() + internal var dataCompletionHandlersForTasks = [Int: (Data) -> Void]() + fileprivate var _longpollSession: URLSession? internal var longpollSession: URLSession { if _longpollSession == nil { @@ -117,11 +134,16 @@ open class DropboxFileProvider: FileProviderBasicRemote { } deinit { + if let sessionuuid = _session?.sessionDescription { + removeSessionHandler(for: sessionuuid) + } + if fileProviderCancelTasksOnInvalidating { _session?.invalidateAndCancel() } else { _session?.finishTasksAndInvalidate() } + } open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) { @@ -283,7 +305,6 @@ extension DropboxFileProvider: FileProviderOperations { } let url = URL(string: "files/download", relativeTo: contentURL)! var request = URLRequest(url: url) - request.httpMethod = "GET" request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization") let requestDictionary: [String: AnyObject] = ["path": path as NSString] let requestJson = String(jsonDictionary: requestDictionary) ?? "" @@ -323,7 +344,6 @@ extension DropboxFileProvider: FileProviderReadWrite { let opType = FileOperationType.fetch(path: path) let url = URL(string: "files/download", relativeTo: contentURL)! var request = URLRequest(url: url) - request.httpMethod = "GET" request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization") if length > 0 { request.setValue("bytes=\(offset)-\(offset + Int64(length) - 1)", forHTTPHeaderField: "Range") @@ -332,14 +352,25 @@ extension DropboxFileProvider: FileProviderReadWrite { } let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString] request.setValue(String(jsonDictionary: requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg") - let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var serverError: FileProviderDropboxError? - if let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: httpResponse.statusCode) { - serverError = FileProviderDropboxError(code: code, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) + let task = session.downloadTask(with: request) + completionHandlersForTasks[task.taskIdentifier] = { error in + completionHandler(nil, error) + } + downloadCompletionHandlersForTasks[task.taskIdentifier] = { tempURL in + guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else { + let code = FileProviderHTTPErrorCode(rawValue: (task.response as? HTTPURLResponse)?.statusCode ?? -1) + let errorData : Data? = nil //Data(contentsOf:cacheURL) // TODO: Figure out how to get error response data for the error description + let serverError : FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil + completionHandler(nil, serverError) + return } - let filedata = serverError ?? error == nil ? data : nil - completionHandler(filedata, serverError ?? error) - }) + do { + let data = try Data(contentsOf: tempURL) + completionHandler(data, nil) + } catch let e { + completionHandler(nil, e) + } + } task.taskDescription = opType.json task.resume() return RemoteOperationHandle(operationType: opType, tasks: [task]) @@ -372,22 +403,6 @@ extension DropboxFileProvider: FileProviderReadWrite { } extension DropboxFileProvider { - /// *OBSOLETED:* Use `publicLink(to:, completionHandler: (URL?, DropboxFileObject?, Date?, Error?))` function instead. - @available(*, obsoleted: 1.0, renamed: "publicLink(to:completionHandler:)", message: "Use publicLink(to:, completionHandler: (URL?, DropboxFileObject?, Date?, Error?)) function instead.") - open func temporaryLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: DropboxFileObject?, _ error: Error?) -> Void)) { - self.publicLink(to: path) { (url, file, _, error) in - completionHandler(url, file, error) - } - } - - /// *OBSOLETED:* Use `publicLink(to:, completionHandler: (URL?, DropboxFileObject?, Date?, Error?))` function instead. - @available(*, obsoleted: 1.0, renamed: "publicLink(to:completionHandler:)", message: "Use publicLink(to:, completionHandler: (URL?, DropboxFileObject?, Date?, Error?)) function instead.") - open func temporaryLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: DropboxFileObject?, _ expiration: Date?, _ error: Error?) -> Void)) { - self.publicLink(to: path) { (url, file, expiration, error) in - completionHandler(url, file, expiration, error) - } - } - /** Genrates a public url to a file to be shared with other users and can be downloaded without authentication. diff --git a/Sources/FTPFileProvider.swift b/Sources/FTPFileProvider.swift index d727993..73476f9 100644 --- a/Sources/FTPFileProvider.swift +++ b/Sources/FTPFileProvider.swift @@ -41,35 +41,27 @@ open class FTPFileProvider: FileProviderBasicRemote { fileprivate var _session: URLSession? internal var sessionDelegate: SessionDelegate? public var session: URLSession { - if _session == nil { - self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential) - let config = URLSessionConfiguration.default - config.urlCache = cache - config.requestCachePolicy = .returnCacheDataElseLoad - _session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: self.operation_queue) - - - sessionDelegate?.didReceivedData = { [weak self] (session: URLSession, downloadTask: URLSessionDownloadTask, bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) -> Void in - guard let `self` = self else { return } - guard let opDic = downloadTask.taskDescription?.deserializeJSON(), - let opType = FileOperationType(json: opDic) else { return } - DispatchQueue.main.async { - let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite) - self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(progress)) - } - } - - sessionDelegate?.didSendDataHandler = { [weak self] (session: Foundation.URLSession, task: URLSessionTask, bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void in - guard let `self` = self else { return } - guard let opDic = task.taskDescription?.deserializeJSON(), - let opType = FileOperationType(json: opDic) else { return } - DispatchQueue.main.async { - let progress = Double(totalBytesSent) / Double(totalBytesExpectedToSend) - self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(progress)) - } + get { + if _session == nil { + self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential) + let config = URLSessionConfiguration.default + config.urlCache = cache + config.requestCachePolicy = .returnCacheDataElseLoad + _session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: self.operation_queue) + _session?.sessionDescription = UUID().uuidString + initEmptySessionHandler(_session!.sessionDescription!) } + return _session! + } + + set { + assert(newValue.delegate is SessionDelegate, "session instances should have a SessionDelegate instance as delegate.") + _session = newValue + if session.sessionDescription?.isEmpty ?? true { + _session?.sessionDescription = UUID().uuidString + } + initEmptySessionHandler(_session!.sessionDescription!) } - return _session! } /** @@ -127,10 +119,15 @@ open class FTPFileProvider: FileProviderBasicRemote { copy.fileOperationDelegate = self.fileOperationDelegate copy.useCache = self.useCache copy.validatingCache = self.validatingCache + copy.useAppleImplementation = self.useAppleImplementation return copy } deinit { + if let sessionuuid = _session?.sessionDescription { + removeSessionHandler(for: sessionuuid) + } + if fileProviderCancelTasksOnInvalidating { _session?.invalidateAndCancel() } else { @@ -485,6 +482,7 @@ extension FTPFileProvider: FileProviderOperations { self.ftpStore(task, filePath: self.ftpPath(toPath), fromData: nil, fromFile: localFile, onTask: { operation.add(task: $0) + $0.taskDescription = opType.json }, completionHandler: { (error) in self.ftpQuit(task) self.dispatch_queue.async { @@ -505,28 +503,18 @@ extension FTPFileProvider: FileProviderOperations { let operation = RemoteOperationHandle(operationType: opType, tasks: []) if self.useAppleImplementation { - let task = session.downloadTask(with: url(of: path)) { (tempDest, response, error) in - if let error = error { - self.dispatch_queue.async { - completionHandler?(error) - } - return - } - - if let tempDest = tempDest { - do { - try FileManager.default.moveItem(at: tempDest, to: destURL) - self.dispatch_queue.async { - completionHandler?(nil) - } - } catch let error { - self.dispatch_queue.async { - completionHandler?(error) - } - } + let task = session.downloadTask(with: url(of: path)) + completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = completionHandler + downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in + do { + try FileManager.default.moveItem(at: tempURL, to: destURL) + completionHandler?(nil) + } catch let e { + completionHandler?(e) } } operation.add(task: task) + task.taskDescription = opType.json task.resume() } else { let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!) @@ -540,6 +528,7 @@ extension FTPFileProvider: FileProviderOperations { self.ftpRetrieveFile(task, filePath: self.ftpPath(path), onTask: { operation.add(task: $0) + $0.taskDescription = opType.json }, onProgress: { recevied, totalReceived, totalSize in let progress = Double(totalReceived) / Double(totalSize) self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(progress)) @@ -574,22 +563,19 @@ extension FTPFileProvider: FileProviderReadWrite { } if self.useAppleImplementation { - let task = session.dataTask(with: url(of: path)) { (data, response, error) in - if let error = error { - self.dispatch_queue.async { - completionHandler(nil, error) - self.delegateNotify(opType, error: error) - } - return - } - - if let data = data { - self.dispatch_queue.async { - completionHandler(data, nil) - self.delegateNotify(opType, error: nil) - } + let task = session.downloadTask(with: url(of: path)) + completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in + completionHandler(nil, error) + } + downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in + do { + let data = try Data(contentsOf: tempURL) + completionHandler(data, nil) + } catch let e { + completionHandler(nil, e) } } + task.taskDescription = opType.json task.resume() return RemoteOperationHandle(operationType: opType, tasks: [task]) } else { @@ -619,6 +605,7 @@ extension FTPFileProvider: FileProviderReadWrite { self.ftpRetrieveData(task, filePath: self.ftpPath(path), from: offset, length: length, onTask: { operation.add(task: $0) + $0.taskDescription = opType.json }, onProgress: { recevied, totalReceived, totalSize in let progress = Double(totalReceived) / Double(totalSize) self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(progress)) @@ -663,6 +650,7 @@ extension FTPFileProvider: FileProviderReadWrite { let storeHandler = { self.ftpStore(task, filePath: self.ftpPath(path), fromData: data ?? Data(), fromFile: nil, onTask: { operation.add(task: $0) + $0.taskDescription = opType.json }, completionHandler: { (error) in self.ftpQuit(task) self.dispatch_queue.async { diff --git a/Sources/FileObject.swift b/Sources/FileObject.swift index 0a68e72..d73db74 100644 --- a/Sources/FileObject.swift +++ b/Sources/FileObject.swift @@ -24,12 +24,6 @@ open class FileObject: Equatable { self.path = path } - /// url to access the resource, not supported by Dropbox provider - @available(*, obsoleted: 1.0, renamed: "url", message: "Use url.absoluteURL instead.") - open var absoluteURL: URL? { - return url?.absoluteURL - } - /// URL to access the resource, can be a relative URL against base URL. /// not supported by Dropbox provider. open internal(set) var url: URL? { diff --git a/Sources/FileProvider.swift b/Sources/FileProvider.swift index 74dc2c6..9b722f2 100644 --- a/Sources/FileProvider.swift +++ b/Sources/FileProvider.swift @@ -195,6 +195,12 @@ public protocol FileProviderBasicRemote: FileProviderBasic { var validatingCache: Bool { get set } } +internal protocol FileProviderBasicRemoteInternal: FileProviderBasic { + var completionHandlersForTasks: [Int: SimpleCompletionHandler] { get set } + var downloadCompletionHandlersForTasks: [Int: (URL) -> Void] { get set } + var dataCompletionHandlersForTasks: [Int: (Data) -> Void] { get set } +} + internal extension FileProviderBasicRemote { func returnCachedDate(with request: URLRequest, validatingCache: Bool, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> Bool { guard let cache = self.cache else { return false } @@ -606,11 +612,6 @@ extension FileProviderBasic { return type(of: self).type } - /// **OBSOLETED** This property never worked as expected and is redundant as only supported by `LocalFileProvider`. - /// To simulate `false` value, assign `URL(fileURLWithPath: "/")` to `baseURL`. - @available(*, obsoleted: 1.0, message: "Redundant property, now is always true.") - var isPathRelative: Bool { return true } - public func url(of path: String? = nil) -> URL { var rpath: String = path ?? self.currentPath rpath = rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? rpath diff --git a/Sources/LocalFileProvider.swift b/Sources/LocalFileProvider.swift index cad5c36..00e85a9 100644 --- a/Sources/LocalFileProvider.swift +++ b/Sources/LocalFileProvider.swift @@ -140,12 +140,6 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo return copy } - /// **OBSOLETED:** No longer is in use and overriding this method has no effect anymore. - @available(*, obsoleted: 1.0, message: "Overriding this method has no effect anymore.") - open class func defaultBaseURL() -> URL { - return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! - } - open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) { dispatch_queue.async { do { diff --git a/Sources/OneDriveFileProvide.swift b/Sources/OneDriveFileProvide.swift index 30f36d0..c38d362 100644 --- a/Sources/OneDriveFileProvide.swift +++ b/Sources/OneDriveFileProvide.swift @@ -41,16 +41,29 @@ open class OneDriveFileProvider: FileProviderBasicRemote { fileprivate var _session: URLSession? fileprivate var sessionDelegate: SessionDelegate? public var session: URLSession { - if _session == nil { - self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential) - let queue = OperationQueue() - //queue.underlyingQueue = dispatch_queue - let config = URLSessionConfiguration.default - config.urlCache = cache - config.requestCachePolicy = .returnCacheDataElseLoad - _session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: queue) + get { + if _session == nil { + self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential) + let queue = OperationQueue() + //queue.underlyingQueue = dispatch_queue + let config = URLSessionConfiguration.default + config.urlCache = cache + config.requestCachePolicy = .returnCacheDataElseLoad + _session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: queue) + _session?.sessionDescription = UUID().uuidString + initEmptySessionHandler(_session!.sessionDescription!) + } + return _session! + } + + set { + assert(newValue.delegate is SessionDelegate, "session instances should have a SessionDelegate instance as delegate.") + _session = newValue + if session.sessionDescription?.isEmpty ?? true { + _session?.sessionDescription = UUID().uuidString + } + initEmptySessionHandler(_session!.sessionDescription!) } - return _session! } /** @@ -114,6 +127,10 @@ open class OneDriveFileProvider: FileProviderBasicRemote { } deinit { + if let sessionuuid = _session?.sessionDescription { + removeSessionHandler(for: sessionuuid) + } + if fileProviderCancelTasksOnInvalidating { _session?.invalidateAndCancel() } else { @@ -292,11 +309,10 @@ extension OneDriveFileProvider: FileProviderOperations { return nil } var request = URLRequest(url: self.url(of: path, modifier: "content")) - request.httpMethod = "GET" request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization") let task = session.downloadTask(with: request) - completionHandlersForTasks[task.taskIdentifier] = completionHandler - downloadCompletionHandlersForTasks[task.taskIdentifier] = { tempURL in + completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = completionHandler + downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else { let code = FileProviderHTTPErrorCode(rawValue: (task.response as? HTTPURLResponse)?.statusCode ?? -1) let errorData : Data? = nil //Data(contentsOf: cacheURL) // TODO: Figure out how to get error response data for the error description @@ -335,14 +351,25 @@ extension OneDriveFileProvider: FileProviderReadWrite { } else if offset > 0 && length < 0 { request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range") } - let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var serverError: FileProviderOneDriveError? - if let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: httpResponse.statusCode) { - serverError = FileProviderOneDriveError(code: code, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) + let task = session.downloadTask(with: request) + completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in + completionHandler(nil, error) + } + downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in + guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else { + let code = FileProviderHTTPErrorCode(rawValue: (task.response as? HTTPURLResponse)?.statusCode ?? -1) + let errorData : Data? = nil //Data(contentsOf: cacheURL) // TODO: Figure out how to get error response data for the error description + let serverError : FileProviderOneDriveError? = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil + completionHandler(nil, serverError) + return } - let filedata = serverError ?? error == nil ? data : nil - completionHandler(filedata, serverError ?? error) - }) + do { + let data = try Data(contentsOf: tempURL) + completionHandler(data, nil) + } catch let e { + completionHandler(nil, e) + } + } task.taskDescription = opType.json task.resume() return RemoteOperationHandle(operationType: opType, tasks: [task]) diff --git a/Sources/OneDriveHelper.swift b/Sources/OneDriveHelper.swift index 5a256c7..a79879d 100644 --- a/Sources/OneDriveHelper.swift +++ b/Sources/OneDriveHelper.swift @@ -136,7 +136,7 @@ internal extension OneDriveFileProvider { return nil } - completionHandlersForTasks[task.taskIdentifier] = { [weak self] error in + completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak self] error in var responseError: FileProviderOneDriveError? if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) { // We can't fetch server result from delegate! diff --git a/Sources/RemoteSession.swift b/Sources/RemoteSession.swift index c46550a..6bc6243 100644 --- a/Sources/RemoteSession.swift +++ b/Sources/RemoteSession.swift @@ -90,19 +90,36 @@ extension FileProviderHTTPError { } } -internal var completionHandlersForTasks = [Int: SimpleCompletionHandler]() -internal var downloadCompletionHandlersForTasks = [Int: (URL) -> Void]() -internal var dataCompletionHandlersForTasks = [Int: (Data) -> Void]() +internal var completionHandlersForTasks = [String: [Int: SimpleCompletionHandler]]() +internal var downloadCompletionHandlersForTasks = [String: [Int: (URL) -> Void]]() +internal var dataCompletionHandlersForTasks = [String: [Int: (Data) -> Void]]() -class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDelegate, URLSessionStreamDelegate { +internal func initEmptySessionHandler(_ uuid: String) { + completionHandlersForTasks[uuid] = [:] + downloadCompletionHandlersForTasks[uuid] = [:] + dataCompletionHandlersForTasks[uuid] = [:] +} + +internal func removeSessionHandler(for uuid: String) { + completionHandlersForTasks.removeValue(forKey: uuid) + downloadCompletionHandlersForTasks.removeValue(forKey: uuid) + dataCompletionHandlersForTasks.removeValue(forKey: uuid) +} + +/// All objects set to `FileProviderRemote.session` must be an instance of this class +final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDelegate, URLSessionStreamDelegate { weak var fileProvider: (FileProviderBasicRemote & FileProviderOperations)? var credential: URLCredential? - var finishDownloadHandler: ((_ session: URLSession, _ downloadTask: URLSessionDownloadTask, _ didFinishDownloadingToURL: URL) -> Void)? - var didSendDataHandler: ((_ session: URLSession, _ task: URLSessionTask, _ bytesSent: Int64, _ totalBytesSent: Int64, _ totalBytesExpectedToSend: Int64) -> Void)? - var didReceivedData: ((_ session: URLSession, _ downloadTask: URLSessionDownloadTask, _ bytesWritten: Int64, _ totalBytesWritten: Int64, _ totalBytesExpectedToWrite: Int64) -> Void)? - var didBecomeStream :((_ session: URLSession, _ taskId: Int, _ didBecome: InputStream, _ outputStream: OutputStream) -> Void)? + /// Forwardng URLSessionDownloadTaskDelegate call + public var finishDownloadHandler: ((_ session: URLSession, _ downloadTask: URLSessionDownloadTask, _ didFinishDownloadingToURL: URL) -> Void)? + /// Forwardng URLSessionTaskDelegate call + public var didSendDataHandler: ((_ session: URLSession, _ task: URLSessionTask, _ bytesSent: Int64, _ totalBytesSent: Int64, _ totalBytesExpectedToSend: Int64) -> Void)? + /// Forwardng URLSessionDownloadTaskDelegate call + public var didReceivedData: ((_ session: URLSession, _ downloadTask: URLSessionDownloadTask, _ bytesWritten: Int64, _ totalBytesWritten: Int64, _ totalBytesExpectedToWrite: Int64) -> Void)? + /// Forwardng URLSessionStreamTaskDelegate call + public var didBecomeStream :((_ session: URLSession, _ taskId: Int, _ didBecome: InputStream, _ outputStream: OutputStream) -> Void)? init(fileProvider: FileProviderBasicRemote & FileProviderOperations, credential: URLCredential?) { self.fileProvider = fileProvider @@ -110,36 +127,43 @@ class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDeleg } // codebeat:disable[ARITY] - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - let completionHandler = completionHandlersForTasks[task.taskIdentifier] ?? nil - completionHandler?(error) - completionHandlersForTasks.removeValue(forKey: task.taskIdentifier) - } - - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { - let completionHandler = dataCompletionHandlersForTasks[dataTask.taskIdentifier] ?? nil - completionHandler?(data) - completionHandlersForTasks.removeValue(forKey: dataTask.taskIdentifier) - } - - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { - self.finishDownloadHandler?(session, downloadTask, location) + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + if !(error == nil && task is URLSessionDownloadTask) { + let completionHandler = completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] ?? nil + completionHandler?(error) + completionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: task.taskIdentifier) + } - let dcompletionHandler = downloadCompletionHandlersForTasks[downloadTask.taskIdentifier] - dcompletionHandler?(location) - completionHandlersForTasks.removeValue(forKey: downloadTask.taskIdentifier) - - guard let json = downloadTask.taskDescription?.deserializeJSON(), + guard let json = task.taskDescription?.deserializeJSON(), let op = FileOperationType(json: json), let fileProvider = fileProvider else { return } DispatchQueue.main.async { - fileProvider.delegate?.fileproviderSucceed(fileProvider, operation: op) + if error != nil { + fileProvider.delegate?.fileproviderFailed(fileProvider, operation: op) + } else { + fileProvider.delegate?.fileproviderSucceed(fileProvider, operation: op) + } } } - func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + let completionHandler = dataCompletionHandlersForTasks[session.sessionDescription!]?[dataTask.taskIdentifier] ?? nil + completionHandler?(data) + dataCompletionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: dataTask.taskIdentifier) + } + + public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { + self.finishDownloadHandler?(session, downloadTask, location) + + let dcompletionHandler = downloadCompletionHandlersForTasks[session.sessionDescription!]?[downloadTask.taskIdentifier] + dcompletionHandler?(location) + downloadCompletionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: downloadTask.taskIdentifier) + completionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: downloadTask.taskIdentifier) + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { self.didSendDataHandler?(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend) guard let json = task.taskDescription?.deserializeJSON(), @@ -154,7 +178,7 @@ class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDeleg } } - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { + public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { self.didReceivedData?(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) guard let json = downloadTask.taskDescription?.deserializeJSON(), @@ -167,11 +191,11 @@ class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDeleg } } - func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + public func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { authenticate(didReceive: challenge, completionHandler: completionHandler) } - func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { authenticate(didReceive: challenge, completionHandler: completionHandler) } @@ -187,7 +211,7 @@ class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDeleg } @available(iOS 9.0, macOS 10.11, *) - func urlSession(_ session: URLSession, streamTask: URLSessionStreamTask, didBecome inputStream: InputStream, outputStream: OutputStream) { + public func urlSession(_ session: URLSession, streamTask: URLSessionStreamTask, didBecome inputStream: InputStream, outputStream: OutputStream) { self.didBecomeStream?(session, streamTask.taskIdentifier, inputStream, outputStream) } } diff --git a/Sources/WebDAVFileProvider.swift b/Sources/WebDAVFileProvider.swift index fcde50a..c9c292e 100644 --- a/Sources/WebDAVFileProvider.swift +++ b/Sources/WebDAVFileProvider.swift @@ -44,16 +44,29 @@ open class WebDAVFileProvider: FileProviderBasicRemote { fileprivate var _session: URLSession? fileprivate var sessionDelegate: SessionDelegate? public var session: URLSession { - if _session == nil { - self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential) - let queue = OperationQueue() - //queue.underlyingQueue = dispatch_queue - let config = URLSessionConfiguration.default - config.urlCache = cache - config.requestCachePolicy = .returnCacheDataElseLoad - _session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDownloadDelegate?, delegateQueue: queue) + get { + if _session == nil { + self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential) + let queue = OperationQueue() + //queue.underlyingQueue = dispatch_queue + let config = URLSessionConfiguration.default + config.urlCache = cache + config.requestCachePolicy = .returnCacheDataElseLoad + _session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDownloadDelegate?, delegateQueue: queue) + _session?.sessionDescription = UUID().uuidString + initEmptySessionHandler(_session!.sessionDescription!) + } + return _session! + } + + set { + assert(newValue.delegate is SessionDelegate, "session instances should have a SessionDelegate instance as delegate.") + _session = newValue + if session.sessionDescription?.isEmpty ?? true { + _session?.sessionDescription = UUID().uuidString + } + initEmptySessionHandler(_session!.sessionDescription!) } - return _session! } /** @@ -94,8 +107,8 @@ open class WebDAVFileProvider: FileProviderBasicRemote { aCoder.encode(self.baseURL, forKey: "baseURL") aCoder.encode(self.credential, forKey: "credential") aCoder.encode(self.currentPath, forKey: "currentPath") - aCoder.encode(self.useCache, forKey: "isCoorinating") - aCoder.encode(self.validatingCache, forKey: "undoManager") + aCoder.encode(self.useCache, forKey: "useCache") + aCoder.encode(self.validatingCache, forKey: "validatingCache") } public static var supportsSecureCoding: Bool { @@ -113,6 +126,10 @@ open class WebDAVFileProvider: FileProviderBasicRemote { } deinit { + if let sessionuuid = _session?.sessionDescription { + removeSessionHandler(for: sessionuuid) + } + if fileProviderCancelTasksOnInvalidating { _session?.invalidateAndCancel() } else { @@ -386,7 +403,7 @@ extension WebDAVFileProvider: FileProviderOperations { } request.httpMethod = "PUT" let task = session.uploadTask(with: request, fromFile: localFile) - completionHandlersForTasks[task.taskIdentifier] = { [weak self] error in + completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak self] error in var responseError: FileProviderWebDavError? if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) { // We can't fetch server result from delegate! @@ -409,8 +426,8 @@ extension WebDAVFileProvider: FileProviderOperations { let url = self.url(of:path) let request = URLRequest(url: url) let task = session.downloadTask(with: request) - completionHandlersForTasks[task.taskIdentifier] = completionHandler - downloadCompletionHandlersForTasks[task.taskIdentifier] = { tempURL in + completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = completionHandler + downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else { let code = FileProviderHTTPErrorCode(rawValue: (task.response as? HTTPURLResponse)?.statusCode ?? -1) let serverError : FileProviderWebDavError? = code != nil ? FileProviderWebDavError(code: code!, path: path, errorDescription: code?.description, url: url) : nil @@ -443,20 +460,33 @@ extension WebDAVFileProvider: FileProviderReadWrite { let opType = FileOperationType.fetch(path: path) let url = self.url(of: path) var request = URLRequest(url: url) - request.httpMethod = "GET" if length > 0 { request.setValue("bytes=\(offset)-\(offset + Int64(length) - 1)", forHTTPHeaderField: "Range") } else if offset > 0 && length < 0 { request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range") } - let handle = RemoteOperationHandle(operationType: opType, tasks: []) - runDataTask(with: request, operationHandle: handle, 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) + + let task = session.downloadTask(with: request) + let handle = RemoteOperationHandle(operationType: opType, tasks: [task]) + completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in + completionHandler(nil, error) + } + downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in + guard let httpResponse = task.response as? HTTPURLResponse , httpResponse.statusCode < 300 else { + let code = FileProviderHTTPErrorCode(rawValue: (task.response as? HTTPURLResponse)?.statusCode ?? -1) + let serverError : FileProviderWebDavError? = code != nil ? FileProviderWebDavError(code: code!, path: path, errorDescription: code?.description, url: url) : nil + completionHandler(nil, serverError) + return } - completionHandler(data, responseError ?? error) - }) + do { + let data = try Data(contentsOf: tempURL) + self.dispatch_queue.async { + completionHandler(data, nil) + } + } catch let e { + completionHandler(nil, e) + } + } return handle } @@ -474,14 +504,14 @@ extension WebDAVFileProvider: FileProviderReadWrite { request.setValue("F", forHTTPHeaderField: "Overwrite") } let task = session.uploadTask(with: request, from: data ?? Data()) - completionHandlersForTasks[task.taskIdentifier] = { [weak self] error in + completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak self] error in var responseError: FileProviderWebDavError? if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) { // We can't fetch server result from delegate! responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: nil, url: url) } completionHandler?(responseError ?? error) - self?.delegateNotify(.create(path: path), error: responseError ?? error) + self?.delegateNotify(opType, error: responseError ?? error) } task.taskDescription = opType.json task.resume()