From d29826c56cb3103efc0f023bbf0a8ea5e11e8344 Mon Sep 17 00:00:00 2001 From: Amir Abbas Date: Wed, 27 Dec 2017 17:36:12 +0330 Subject: [PATCH] Fix FTP provider crashes and errors on uploading and downloading --- Sources/FPSStreamTask.swift | 13 +- Sources/FTPFileProvider.swift | 159 +++++----------------- Sources/FTPHelper.swift | 239 ++++++++++++++-------------------- Sources/FileObject.swift | 18 ++- Sources/FileProvider.swift | 12 +- 5 files changed, 153 insertions(+), 288 deletions(-) diff --git a/Sources/FPSStreamTask.swift b/Sources/FPSStreamTask.swift index b652514..4a79471 100644 --- a/Sources/FPSStreamTask.swift +++ b/Sources/FPSStreamTask.swift @@ -265,7 +265,9 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate { fileprivate var host: (hostname: String, port: Int)? fileprivate var service: NetService? - internal init(session: URLSession, host: String, port: Int, useURLSession: Bool = true) { + private static let defaultUseURLSession = false + + internal init(session: URLSession, host: String, port: Int, useURLSession: Bool = defaultUseURLSession) { self._underlyingSession = session self.useURLSession = useURLSession if #available(iOS 9.0, macOS 10.11, *) { @@ -285,7 +287,7 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate { self.operation_queue.maxConcurrentOperationCount = 1 } - internal init(session: URLSession, netService: NetService, useURLSession: Bool = true) { + internal init(session: URLSession, netService: NetService, useURLSession: Bool = defaultUseURLSession) { self._underlyingSession = session self.useURLSession = useURLSession if #available(iOS 9.0, macOS 10.11, *) { @@ -463,10 +465,11 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate { timedOut = expireDate < Date() } var dR: Data? - if self.dataReceived.count > maxBytes { + let allData = self.dataReceived + if allData.count > maxBytes { let range: Range = 0.. 0 { dR = self.dataReceived @@ -681,7 +684,7 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate { public extension URLSession { /// Creates a bidirectional stream task to a given host and port. func fpstreamTask(withHostName hostname: String, port: Int) -> FileProviderStreamTask { - return FileProviderStreamTask(session: self, host: hostname, port: port, useURLSession: false) + return FileProviderStreamTask(session: self, host: hostname, port: port) } /** diff --git a/Sources/FTPFileProvider.swift b/Sources/FTPFileProvider.swift index 962c532..fcae51f 100644 --- a/Sources/FTPFileProvider.swift +++ b/Sources/FTPFileProvider.swift @@ -39,9 +39,6 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil /// Determine either FTP session is in passive or active mode. public let passiveMode: Bool - /// Force to use URLSessionDownloadTask/URLSessionDataTask when possible - public var useAppleImplementation = true - fileprivate var _session: URLSession! internal var sessionDelegate: SessionDelegate? public var session: URLSession { @@ -109,7 +106,6 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil self.init(baseURL: baseURL, passive: aDecoder.decodeBool(forKey: "passiveMode"), credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential) self.useCache = aDecoder.decodeBool(forKey: "useCache") self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache") - self.useAppleImplementation = aDecoder.decodeBool(forKey: "useAppleImplementation") } public func encode(with aCoder: NSCoder) { @@ -117,7 +113,6 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil aCoder.encode(self.credential, forKey: "credential") aCoder.encode(self.useCache, forKey: "useCache") aCoder.encode(self.validatingCache, forKey: "validatingCache") - aCoder.encode(self.useAppleImplementation, forKey: "useAppleImplementation") aCoder.encode(self.passiveMode, forKey: "passiveMode") } @@ -131,7 +126,6 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil copy.fileOperationDelegate = self.fileOperationDelegate copy.useCache = self.useCache copy.validatingCache = self.validatingCache - copy.useAppleImplementation = self.useAppleImplementation return copy } @@ -147,10 +141,12 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil } } - internal var serverSupportsRFC3659: Bool = true + internal var supportsRFC3659: Bool = true + /// Uploads files in chunk if true. + public var supportsREST: Bool = true open func contentsOfDirectory(path: String, completionHandler: @escaping ([FileObject], Error?) -> Void) { - self.contentsOfDirectory(path: path, rfc3659enabled: serverSupportsRFC3659, completionHandler: completionHandler) + self.contentsOfDirectory(path: path, rfc3659enabled: supportsRFC3659, completionHandler: completionHandler) } /** @@ -205,7 +201,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil } open func attributesOfItem(path: String, completionHandler: @escaping (FileObject?, Error?) -> Void) { - self.attributesOfItem(path: path, rfc3659enabled: serverSupportsRFC3659, completionHandler: completionHandler) + self.attributesOfItem(path: path, rfc3659enabled: supportsRFC3659, completionHandler: completionHandler) } /** @@ -246,7 +242,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil } if response.hasPrefix("500") { - self.serverSupportsRFC3659 = false + self.supportsRFC3659 = false self.attributesOfItem(path: path, rfc3659enabled: false, completionHandler: completionHandler) } @@ -395,6 +391,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil } progress.setUserInfoObject(Date(), forKey: .startingTimeKey) }, onProgress: { bytesSent, totalSent, expectedBytes in + progress.totalUnitCount = expectedBytes progress.completedUnitCount = totalSent self.delegateNotify(operation, progress: progress.fractionCompleted) }, completionHandler: { (error) in @@ -417,22 +414,33 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else { return nil } - var progress = Progress(totalUnitCount: 0) + let progress = Progress(totalUnitCount: 0) progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey) progress.kind = .file progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey) - if self.useAppleImplementation { - self.attributesOfItem(path: path, completionHandler: { (file, error) in - do { - if let error = error { - throw error - } - - if file?.isDirectory ?? false { - throw self.urlError(path, code: .fileIsDirectory) - } - } catch { + 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) + } + return + } + + self.ftpRetrieveFile(task, filePath: self.ftpPath(path), onTask: { task in + weak var weakTask = task + progress.cancellationHandler = { + weakTask?.cancel() + } + progress.setUserInfoObject(Date(), forKey: .startingTimeKey) + }, onProgress: { recevied, totalReceived, totalSize in + progress.totalUnitCount = totalSize + progress.completedUnitCount = totalReceived + self.delegateNotify(operation, progress: progress.fractionCompleted) + }) { (tmpurl, error) in + if let error = error { + progress.cancel() self.dispatch_queue.async { completionHandler?(error) self.delegateNotify(operation, error: error) @@ -440,69 +448,11 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil return } - progress.totalUnitCount = file?.size ?? 0 - - let task = self.session.downloadTask(with: self.url(of: path)) - completionHandlersForTasks[self.session.sessionDescription!]?[task.taskIdentifier] = completionHandler - downloadCompletionHandlersForTasks[self.session.sessionDescription!]?[task.taskIdentifier] = { tempURL in - var error: NSError? - NSFileCoordinator().coordinate(writingItemAt: tempURL, options: .forMoving, writingItemAt: destURL, options: .forReplacing, error: &error, byAccessor: { (tempURL, destURL) in - do { - try FileManager.default.moveItem(at: tempURL, to: destURL) - completionHandler?(nil) - } catch { - completionHandler?(error) - } - }) - if let error = error { - completionHandler?(error) - } - } - task.taskDescription = operation.json - task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress) - task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress) - progress.cancellationHandler = { [weak task] in - task?.cancel() - } - progress.setUserInfoObject(Date(), forKey: .startingTimeKey) - task.resume() - }) - } else { - let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!) - self.ftpLogin(task) { (error) in - if let error = error { + if let tmpurl = tmpurl { + try? FileManager.default.moveItem(at: tmpurl, to: destURL) self.dispatch_queue.async { - completionHandler?(error) - } - return - } - - self.ftpRetrieveFile(task, filePath: self.ftpPath(path), onTask: { task in - weak var weakTask = task - progress.cancellationHandler = { - weakTask?.cancel() - } - progress.setUserInfoObject(Date(), forKey: .startingTimeKey) - }, onProgress: { recevied, totalReceived, totalSize in - progress.totalUnitCount = totalSize - progress.completedUnitCount = totalReceived - self.delegateNotify(operation, progress: progress.fractionCompleted) - }) { (tmpurl, error) in - if let error = error { - progress.cancel() - self.dispatch_queue.async { - completionHandler?(error) - self.delegateNotify(operation, error: error) - } - return - } - - if let tmpurl = tmpurl { - try? FileManager.default.moveItem(at: tmpurl, to: destURL) - self.dispatch_queue.async { - completionHandler?(nil) - self.delegateNotify(operation) - } + completionHandler?(nil) + self.delegateNotify(operation) } } } @@ -510,47 +460,6 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil return progress } - open func contents(path: String, completionHandler: @escaping ((Data?, Error?) -> Void)) -> Progress? { - let operation = FileOperationType.fetch(path: path) - guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else { - return nil - } - - if self.useAppleImplementation { - var progress = Progress(totalUnitCount: 0) - progress.setUserInfoObject(operation, forKey: .fileProvderOperationTypeKey) - progress.kind = .file - progress.setUserInfoObject(Progress.FileOperationKind.downloading, forKey: .fileOperationKindKey) - - let task = session.downloadTask(with: url(of: path)) - completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in - if error != nil { - progress.cancel() - } - completionHandler(nil, error) - } - downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in - do { - let data = try Data(contentsOf: tempURL) - completionHandler(data, nil) - } catch { - completionHandler(nil, error) - } - } - task.taskDescription = operation.json - task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress) - task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress) - progress.cancellationHandler = { [weak task] in - task?.cancel() - } - progress.setUserInfoObject(Date(), forKey: .startingTimeKey) - task.resume() - return progress - } else { - return self.contents(path: path, offset: 0, length: -1, completionHandler: completionHandler) - } - } - open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> Progress? { let operation = FileOperationType.fetch(path: path) if length == 0 || offset < 0 { diff --git a/Sources/FTPHelper.swift b/Sources/FTPHelper.swift index 515c470..7b280e3 100644 --- a/Sources/FTPHelper.swift +++ b/Sources/FTPHelper.swift @@ -66,9 +66,6 @@ internal extension FTPFileProvider { func ftpLogin(_ task: FileProviderStreamTask, completionHandler: @escaping (_ error: Error?) -> Void) { let timeout = session.configuration.timeoutIntervalForRequest - if task.state == .suspended { - task.resume() - } var isSecure = false // Implicit FTP Connection @@ -76,7 +73,10 @@ internal extension FTPFileProvider { task.startSecureConnection() isSecure = true } - + if task.state == .suspended { + task.resume() + } + let credential = self.credential task.readData(ofMinLength: 4, maxLength: 2048, timeout: timeout) { (data, eof, error) in @@ -377,7 +377,7 @@ internal extension FTPFileProvider { if response.hasPrefix("500") && useMLST { dataTask.cancel() - self.serverSupportsRFC3659 = false + self.supportsRFC3659 = false throw self.urlError(path, code: .unsupportedURL) } @@ -445,27 +445,19 @@ internal extension FTPFileProvider { return progress } - func ftpRetrieveData(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1, onTask: ((_ task: FileProviderStreamTask) -> Void)?, onProgress: ((_ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?, completionHandler: @escaping (_ data: Data?, _ error: Error?) -> Void) { - - // Check cache - if useCache, let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL, let cachedResponse = self.cache?.cachedResponse(for: URLRequest(url: url)), cachedResponse.data.count > 0 { - dispatch_queue.async { - completionHandler(cachedResponse.data, nil) - } - return - } + func ftpRetrieve(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1, onTask: ((_ task: FileProviderStreamTask) -> Void)?, onProgress: @escaping (_ data: Data, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void, completionHandler: @escaping (_ error: Error?) -> Void) { self.attributesOfItem(path: filePath) { (file, error) in let totalSize = file?.size ?? -1 // Retreive data from server self.ftpDataConnect(task) { (dataTask, error) in if let error = error { - completionHandler(nil, error) + completionHandler(error) return } guard let dataTask = dataTask else { - completionHandler(nil, self.urlError(filePath, code: .badServerResponse)) + completionHandler(self.urlError(filePath, code: .badServerResponse)) return } @@ -477,7 +469,7 @@ internal extension FTPFileProvider { let timeout = self.session.configuration.timeoutIntervalForRequest DispatchQueue.global().async { - var finalData = Data() + var totalReceived: Int64 = 0 var eof = false var error: Error? while !eof { @@ -485,37 +477,31 @@ internal extension FTPFileProvider { group.enter() dataTask.readData(ofMinLength: 0, maxLength: 65535, timeout: timeout, completionHandler: { (data, seof, serror) in if let data = data { - finalData.append(data) - onProgress?(Int64(data.count), Int64(finalData.count), totalSize) - } - eof = seof || (length > 0 && finalData.count >= length) - if length > 0 && finalData.count > length { - finalData.count = length + var data = data + if length > 0, Int64(data.count) + totalReceived > Int64(length) { + data.count = Int(Int64(length) - totalReceived) + } + totalReceived += Int64(data.count) + onProgress(data, totalReceived, totalSize) } + eof = seof || (length > 0 && totalReceived >= length) error = serror group.leave() }) let waitResult = group.wait(timeout: .now() + timeout) if let error = error { - completionHandler(nil, error) + completionHandler(error) return } if waitResult == .timedOut { - completionHandler(nil, self.urlError(filePath, code: .timedOut)) + completionHandler(self.urlError(filePath, code: .timedOut)) return } } - if let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL { - let urlresponse = URLResponse(url: url, mimeType: nil, expectedContentLength: finalData.count, textEncodingName: nil) - let cachedResponse = CachedURLResponse(response: urlresponse, data: finalData) - let request = URLRequest(url: url) - self.cache?.storeCachedResponse(cachedResponse, for: request) - } - - completionHandler(finalData, nil) + completionHandler(nil) return } }) { (response, error) in @@ -533,7 +519,7 @@ internal extension FTPFileProvider { } } catch { self.dispatch_queue.async { - completionHandler(nil, error) + completionHandler(error) } } } @@ -541,6 +527,34 @@ internal extension FTPFileProvider { } } + func ftpRetrieveData(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1, onTask: ((_ task: FileProviderStreamTask) -> Void)?, onProgress: ((_ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?, completionHandler: @escaping (_ data: Data?, _ error: Error?) -> Void) { + + // Check cache + if useCache, let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL, let cachedResponse = self.cache?.cachedResponse(for: URLRequest(url: url)), cachedResponse.data.count > 0 { + dispatch_queue.async { + completionHandler(cachedResponse.data, nil) + } + return + } + + var finalData = Data() + self.ftpRetrieve(task, filePath: filePath, from: position, length: length, onTask: onTask, onProgress: { (data, total, expected) in + finalData.append(data) + onProgress?(Int64(data.count), total, expected) + }) { (error) in + if let error = error { + completionHandler(nil, error) + } + if let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL { + let urlresponse = URLResponse(url: url, mimeType: nil, expectedContentLength: finalData.count, textEncodingName: nil) + let cachedResponse = CachedURLResponse(response: urlresponse, data: finalData) + let request = URLRequest(url: url) + self.cache?.storeCachedResponse(cachedResponse, for: request) + } + completionHandler(finalData, nil) + } + } + func ftpRetrieveFile(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1, onTask: ((_ task: FileProviderStreamTask) -> Void)?, onProgress: ((_ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?, completionHandler: @escaping (_ file: URL?, _ error: Error?) -> Void) { let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).appendingPathExtension("tmp") @@ -558,116 +572,46 @@ internal extension FTPFileProvider { return } - self.attributesOfItem(path: filePath) { (file, error) in - let totalSize = file?.size ?? -1 - // Retreive data from server - self.ftpDataConnect(task) { (dataTask, error) in + do { + try Data().write(to: tempURL, options: [.noFileProtection]) + let fileHandle = try FileHandle(forWritingTo: tempURL) + self.ftpRetrieve(task, filePath: filePath, from: position, length: length, onTask: onTask, onProgress: { (data, total, expected) in + fileHandle.write(data) + onProgress?(Int64(data.count), total, expected) + }) { (error) in + fileHandle.closeFile() if let error = error { completionHandler(nil, error) return } - - guard let dataTask = dataTask else { - completionHandler(nil, self.urlError(filePath, code: .badServerResponse)) - return - } - - // Send retreive command - let len = 19 /* TYPE response */ + 65 + String(position).count /* REST Response */ + 53 + filePath.count + String(totalSize).count /* RETR open response */ + 26 /* RETR Transfer complete message. */ - self.execute(command: "TYPE I" + "\r\n" + "REST \(position)" + "\r\n" + "RETR \(filePath)", on: task, minLength: len, afterSend: { error in - // starting passive task - onTask?(dataTask) - - let timeout = self.session.configuration.timeoutIntervalForRequest - DispatchQueue.global().async { - var finalData = Data() - var eof = false - var error: Error? - while !eof { - let group = DispatchGroup() - group.enter() - dataTask.readData(ofMinLength: 0, maxLength: 65535, timeout: timeout, completionHandler: { (data, seof, serror) in - if let data = data { - finalData.append(data) - onProgress?(Int64(data.count), Int64(finalData.count), totalSize) - } - eof = seof || (length > 0 && finalData.count >= length) - if length > 0 && finalData.count > length { - finalData.count = length - } - error = serror - group.leave() - }) - let waitResult = group.wait(timeout: .now() + timeout) - - if let error = error { - completionHandler(nil, error) - return - } - - if waitResult == .timedOut { - error = self.urlError("", code: .timedOut) - completionHandler(nil, error) - return - } - } - - if let url = URL(string: filePath.addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? filePath, relativeTo: self.baseURL!)?.absoluteURL { - let urlresponse = URLResponse(url: url, mimeType: nil, expectedContentLength: finalData.count, textEncodingName: nil) - let cachedResponse = CachedURLResponse(response: urlresponse, data: finalData) - let request = URLRequest(url: url) - self.cache?.storeCachedResponse(cachedResponse, for: request) - } - - self.dispatch_queue.async { - do { - try finalData.write(to: tempURL) - completionHandler(tempURL, nil) - // Removing temporary file after coordinating - NSFileCoordinator().coordinate(writingItemAt: tempURL, options: .forDeleting, error: nil, byAccessor: { (tempURL) in - try? FileManager.default.removeItem(at: tempURL) - }) - } catch { - completionHandler(nil, error) - } - } - } - }) { (response, error) in - do { - if let error = error { - throw error - } - - guard let response = response else { - throw self.urlError(filePath, code: .cannotParseResponse) - } - - if !(response.hasPrefix("1") || response.hasPrefix("2")) { - throw FileProviderFTPError(message: response) - } - } catch { - self.dispatch_queue.async { - completionHandler(nil, error) - } - } - } + completionHandler(tempURL, nil) } + } catch { + completionHandler(nil, error) } } func ftpStore(_ task: FileProviderStreamTask, filePath: String, fromData: Data?, fromFile: URL?, onTask: ((_ task: FileProviderStreamTask) -> Void)?, onProgress: ((_ bytesSent: Int64, _ totalSent: Int64, _ expectedBytes: Int64) -> Void)?, completionHandler: @escaping (_ error: Error?) -> Void) { - let timeout = self.session.configuration.timeoutIntervalForRequest + operation_queue.addOperation { guard let size: Int64 = (fromData != nil ? Int64(fromData!.count) : nil) ?? fromFile?.fileSize else { return } + let timeout = self.session.configuration.timeoutIntervalForResource var error: Error? let chunkSize: Int - switch size { - case 0..<262_144: chunkSize = 32_768 // 0KB To 256KB, chunk size is 32KB - case 262_144..<1_048_576: chunkSize = 65_536 // 256KB To 1MB, chunk size is 64KB - case 1_048_576..<10_485_760: chunkSize = 131_072 // 1MB To 10MB, chunk size is 128KB - case 10_048_576..<33_554_432: chunkSize = 262_144 // 10MB To 32MB, chunk size is 256KB - default: chunkSize = 524_288 // Larger than 32MB, chunk size is 512KB + switch (self.supportsREST, size) { + case (false, _): + chunkSize = Int.max + case (_, 0..<262_144): + chunkSize = 32_768 // 0KB To 256KB, chunk size is 32KB + case (_, 262_144..<1_048_576): + chunkSize = 65_536 // 256KB To 1MB, chunk size is 64KB + case (_, 1_048_576..<10_485_760): + chunkSize = 131_072 // 1MB To 10MB, chunk size is 128KB + case (_, 10_048_576..<33_554_432): + chunkSize = 262_144 // 10MB To 32MB, chunk size is 256KB + default: + chunkSize = 524_288 // Larger than 32MB, chunk size is 512KB } var fileHandle: FileHandle? @@ -680,16 +624,16 @@ internal extension FTPFileProvider { var eof = false var sent: Int64 = 0 + var retried = 0 - while !eof { + repeat { let subdata: Data if let data = fromData { let endIndex = min(data.count, Int(sent) + chunkSize) - eof = endIndex == data.count subdata = data.subdata(in: Int(sent).. 3 { + completionHandler(error) + return + } + } + + if let data = fromData { + let endIndex = min(data.count, Int(sent) + chunkSize) + eof = endIndex == data.count + } else if let fileHandle = fileHandle { + eof = Int64(fileHandle.offsetInFile) == size + } + } while !eof completionHandler(nil) } } diff --git a/Sources/FileObject.swift b/Sources/FileObject.swift index 56322ab..c47a27b 100644 --- a/Sources/FileObject.swift +++ b/Sources/FileObject.swift @@ -170,9 +170,13 @@ open class FileObject: Equatable { } internal func mapPredicate() -> [String: Any] { - let mapDict: [URLResourceKey: String] = [.fileURLKey: "url", .nameKey: "name", .pathKey: "path", .fileSizeKey: "filesize", .creationDateKey: "creationDate", - .contentModificationDateKey: "modifiedDate", .isHiddenKey: "isHidden", .isWritableKey: "isWritable", .serverDateKey: "serverDate", .entryTagKey: "entryTag", .mimeTypeKey: "mimeType"] - let typeDict: [URLFileResourceType: String] = [.directory: "directory", .regular: "regular", .symbolicLink: "symbolicLink", .unknown: "unknown"] + let mapDict: [URLResourceKey: String] = [.fileURLKey: "url", .nameKey: "name", .pathKey: "path", + .fileSizeKey: "filesize", .creationDateKey: "creationDate", + .contentModificationDateKey: "modifiedDate", .isHiddenKey: "isHidden", + .isWritableKey: "isWritable", .serverDateKey: "serverDate", + .entryTagKey: "entryTag", .mimeTypeKey: "mimeType"] + let typeDict: [URLFileResourceType: String] = [.directory: "directory", .regular: "regular", + .symbolicLink: "symbolicLink", .unknown: "unknown"] var result = [String: Any]() for (key, value) in allValues { if let convertkey = mapDict[key] { @@ -190,9 +194,11 @@ open class FileObject: Equatable { /// Converts macOS spotlight query for searching files to a query that can be used for `searchFiles()` method static public func convertPredicate(fromSpotlight query: NSPredicate) -> NSPredicate { - let mapDict: [String: URLResourceKey] = [NSMetadataItemURLKey: .fileURLKey, NSMetadataItemFSNameKey: .nameKey, NSMetadataItemPathKey: .pathKey, - NSMetadataItemFSSizeKey: .fileSizeKey, NSMetadataItemFSCreationDateKey: .creationDateKey, - NSMetadataItemFSContentChangeDateKey: .contentModificationDateKey, "kMDItemFSInvisible": .isHiddenKey, "kMDItemFSIsWriteable": .isWritableKey, "kMDItemKind": .mimeTypeKey] + let mapDict: [String: URLResourceKey] = [NSMetadataItemURLKey: .fileURLKey, NSMetadataItemFSNameKey: .nameKey, + NSMetadataItemPathKey: .pathKey, NSMetadataItemFSSizeKey: .fileSizeKey, + NSMetadataItemFSCreationDateKey: .creationDateKey, NSMetadataItemFSContentChangeDateKey: .contentModificationDateKey, + "kMDItemFSInvisible": .isHiddenKey, "kMDItemFSIsWriteable": .isWritableKey, + "kMDItemKind": .mimeTypeKey] if let cQuery = query as? NSCompoundPredicate { let newSub = cQuery.subpredicates.map { convertPredicate(fromSpotlight: $0 as! NSPredicate) } diff --git a/Sources/FileProvider.swift b/Sources/FileProvider.swift index 2c1d948..8fc0673 100644 --- a/Sources/FileProvider.swift +++ b/Sources/FileProvider.swift @@ -173,14 +173,6 @@ extension FileProviderBasic { operation_queue.maxConcurrentOperationCount = newValue } } - - /// Returns total and used capacity in provider container asynchronously. - @available(*, deprecated, message: "Use storageProperties which returns VolumeObject") - func storageProperties(completionHandler: @escaping (_ total: Int64, _ used: Int64) -> Void) { - self.storageProperties { (info) in - completionHandler(info?.totalCapacity ?? -1, info?.usage ?? 0) - } - } } /// Checking equality of two file provider, regardless of current path queues and delegates. @@ -410,8 +402,8 @@ public protocol FileProviderOperations: FileProviderBasic { } public extension FileProviderOperations { - /// *DEPRECATED:* Use Use FileProviderReadWrite.writeContents(path:, data:, completionHandler:) method instead. - @available(*, deprecated, message: "Use FileProviderReadWrite.writeContents(path:, data:, completionHandler:) method instead.") + /// *OBSOLETED:* Use Use FileProviderReadWrite.writeContents(path:, data:, completionHandler:) method instead. + @available(*, obsoleted: 0.23, message: "Use FileProviderReadWrite.writeContents(path:, data:, completionHandler:) method instead.") @discardableResult public func create(file: String, at: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> Progress? { let path = (at as NSString).appendingPathComponent(file)