Fix FTP provider crashes and errors on uploading and downloading
This commit is contained in:
@@ -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..<maxBytes
|
||||
dR = self.dataReceived.subdata(in: range)
|
||||
self.dataReceived.replaceSubrange(range, with: Data())
|
||||
self.dataReceived = allData.subdata(in: maxBytes..<allData.count)
|
||||
} else {
|
||||
if self.dataReceived.count > 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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+34
-125
@@ -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 {
|
||||
|
||||
+97
-142
@@ -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)..<endIndex)
|
||||
}else if let fileHandle = fileHandle {
|
||||
} else if let fileHandle = fileHandle {
|
||||
fileHandle.seek(toFileOffset: UInt64(sent))
|
||||
subdata = fileHandle.readData(ofLength: chunkSize)
|
||||
eof = Int64(fileHandle.offsetInFile) == size
|
||||
} else {
|
||||
return
|
||||
}
|
||||
@@ -700,22 +644,33 @@ internal extension FTPFileProvider {
|
||||
self.ftpStore(task, data: subdata, to: filePath, from: sent, onTask: onTask, completionHandler: { (serror) in
|
||||
error = serror
|
||||
sent += Int64(subdata.count)
|
||||
retried = 0
|
||||
group.leave()
|
||||
onProgress?(Int64(subdata.count), sent, size)
|
||||
//print("ftp \(filePath): \(subdata.count), \(sent), \(size)")
|
||||
})
|
||||
let waitResult = group.wait(timeout: .now() + timeout)
|
||||
|
||||
if let error = error {
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
|
||||
if waitResult == .timedOut {
|
||||
error = self.urlError(filePath, code: .timedOut)
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
retried += 1
|
||||
print(error.localizedDescription)
|
||||
if retried > 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user