Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ff5e13931f | |||
| 75af738d2e | |||
| f54a1253e4 | |||
| 1394a92662 | |||
| dab171c755 | |||
| ea5de2e2aa | |||
| 2253cca086 | |||
| e15a900ade | |||
| 5c2c56c44c |
+1
-1
@@ -56,7 +56,7 @@ script:
|
||||
after_success:
|
||||
# Run `pod trunk push` if specified
|
||||
- if [ $POD == "YES" ] && [ -n "$TRAVIS_TAG" ]; then
|
||||
pod trunk push;
|
||||
pod trunk push --allow-warnings;
|
||||
fi
|
||||
|
||||
# - bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
|
||||
#
|
||||
|
||||
s.name = "FileProvider"
|
||||
s.version = "0.15.0"
|
||||
s.version = "0.15.3"
|
||||
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.
|
||||
@@ -58,7 +58,7 @@ Pod::Spec.new do |s|
|
||||
s.author = { "Amir Abbas Mousavian" => "a.mosavian@gmail.com" }
|
||||
# Or just: s.author = "Amir Abbas Mousavian"
|
||||
# s.authors = { "Amir Abbas Mousavian" => "a.mosavian@gmail.com" }
|
||||
s.social_media_url = "https://twitter.com/amosavian"
|
||||
# s.social_media_url = "https://twitter.com/amosavian"
|
||||
|
||||
# ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
|
||||
#
|
||||
|
||||
@@ -116,9 +116,9 @@
|
||||
79BD63BE1E2CC3C20035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BD1E2CC3C20035128C /* ImageIO.framework */; };
|
||||
79BD63C01E2CC3CD0035128C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */; };
|
||||
79BD63C21E2CC3D30035128C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63C11E2CC3D30035128C /* AVFoundation.framework */; };
|
||||
79BD63C51E2D17880035128C /* OneDriveFileProvide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */; };
|
||||
79BD63C61E2D17880035128C /* OneDriveFileProvide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */; };
|
||||
79BD63C71E2D17880035128C /* OneDriveFileProvide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */; };
|
||||
79BD63C51E2D17880035128C /* OneDriveFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvider.swift */; };
|
||||
79BD63C61E2D17880035128C /* OneDriveFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvider.swift */; };
|
||||
79BD63C71E2D17880035128C /* OneDriveFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvider.swift */; };
|
||||
79BD63C81E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
|
||||
79BD63C91E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
|
||||
79BD63CA1E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
|
||||
@@ -182,7 +182,7 @@
|
||||
79BD63BD1E2CC3C20035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63C11E2CC3D30035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||
79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveFileProvide.swift; sourceTree = "<group>"; };
|
||||
79BD63C31E2D17880035128C /* OneDriveFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveFileProvider.swift; sourceTree = "<group>"; };
|
||||
79BD63C41E2D17880035128C /* OneDriveHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveHelper.swift; sourceTree = "<group>"; };
|
||||
79F5745A1DFDB10A00179ABF /* FileObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileObject.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -304,7 +304,7 @@
|
||||
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
|
||||
7936BC111E880F5700A6C81C /* FTPFileProvider.swift */,
|
||||
798654321E8874BC002FA550 /* FTPHelper.swift */,
|
||||
79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */,
|
||||
79BD63C31E2D17880035128C /* OneDriveFileProvider.swift */,
|
||||
79BD63C41E2D17880035128C /* OneDriveHelper.swift */,
|
||||
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */,
|
||||
799396971D48C02300086753 /* SMBClient.swift */,
|
||||
@@ -498,7 +498,7 @@
|
||||
7936BC121E880F5700A6C81C /* FTPFileProvider.swift in Sources */,
|
||||
79480FF61E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */,
|
||||
79F5745B1DFDB10B00179ABF /* FileObject.swift in Sources */,
|
||||
79BD63C51E2D17880035128C /* OneDriveFileProvide.swift in Sources */,
|
||||
79BD63C51E2D17880035128C /* OneDriveFileProvider.swift in Sources */,
|
||||
7924B1991D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
799396D71D48C02300086753 /* SMB2Types.swift in Sources */,
|
||||
@@ -541,7 +541,7 @@
|
||||
7936BC131E880F5700A6C81C /* FTPFileProvider.swift in Sources */,
|
||||
79480FF71E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */,
|
||||
79F5745C1DFDB10B00179ABF /* FileObject.swift in Sources */,
|
||||
79BD63C61E2D17880035128C /* OneDriveFileProvide.swift in Sources */,
|
||||
79BD63C61E2D17880035128C /* OneDriveFileProvider.swift in Sources */,
|
||||
7924B1B01D89F7DE00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
7924B19A1D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
@@ -584,7 +584,7 @@
|
||||
7936BC141E880F5700A6C81C /* FTPFileProvider.swift in Sources */,
|
||||
79480FF81E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */,
|
||||
79F5745D1DFDB10B00179ABF /* FileObject.swift in Sources */,
|
||||
79BD63C71E2D17880035128C /* OneDriveFileProvide.swift in Sources */,
|
||||
79BD63C71E2D17880035128C /* OneDriveFileProvider.swift in Sources */,
|
||||
7924B1B11D89F7DF00589DB7 /* FPSStreamTask.swift in Sources */,
|
||||
7924B19B1D89DAE000589DB7 /* Element.swift in Sources */,
|
||||
799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */,
|
||||
@@ -621,7 +621,7 @@
|
||||
799396601D48B7BF00086753 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.15.0;
|
||||
BUNDLE_VERSION_STRING = 0.15.3;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -631,6 +631,7 @@
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -651,7 +652,7 @@
|
||||
799396611D48B7BF00086753 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_VERSION_STRING = 0.15.0;
|
||||
BUNDLE_VERSION_STRING = 0.15.3;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
|
||||
@@ -717,8 +717,11 @@ public enum UbiquitousScope: RawRepresentable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get progress of CloudFileProvider operations
|
||||
open class CloudOperationHandle: OperationHandle {
|
||||
/// Url of file which operation is doing on
|
||||
public let baseURL: URL?
|
||||
/// Type of operation
|
||||
public let operationType: FileOperationType
|
||||
|
||||
init (operationType: FileOperationType, baseURL: URL?) {
|
||||
|
||||
@@ -34,7 +34,11 @@ open class DropboxFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open var credential: URLCredential?
|
||||
open var credential: URLCredential? {
|
||||
didSet {
|
||||
sessionDelegate?.credential = self.credential
|
||||
}
|
||||
}
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool
|
||||
public var validatingCache: Bool
|
||||
@@ -42,16 +46,34 @@ 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)
|
||||
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
|
||||
}
|
||||
self.sessionDelegate = newValue.delegate as? SessionDelegate
|
||||
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 +139,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)) {
|
||||
@@ -269,6 +296,14 @@ extension DropboxFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
// check file is not a folder
|
||||
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
|
||||
dispatch_queue.async {
|
||||
completionHandler?(self.throwError(localFile.path, code: URLError.fileIsDirectory))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
@@ -283,7 +318,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 +357,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 +365,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 +416,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.
|
||||
|
||||
|
||||
+18
-44
@@ -42,10 +42,10 @@ public final class DropboxFileObject: FileObject {
|
||||
/// The time contents of file has been modified on server, returns nil if not set
|
||||
open internal(set) var serverTime: Date? {
|
||||
get {
|
||||
return allValues[.serverDate] as? Date
|
||||
return allValues[.serverDateKey] as? Date
|
||||
}
|
||||
set {
|
||||
allValues[.serverDate] = newValue
|
||||
allValues[.serverDateKey] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,42 +121,8 @@ internal extension DropboxFileProvider {
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
if data.count > 150 * 1024 * 1024 {
|
||||
let error = FileProviderDropboxError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(.create(path: targetPath), error: error)
|
||||
return nil
|
||||
}
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: URL
|
||||
url = URL(string: "files/upload", relativeTo: contentURL)!
|
||||
requestDictionary["path"] = correctPath(targetPath) as NSString?
|
||||
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
|
||||
requestDictionary["client_modified"] = modifiedDate.rfc3339utc() as NSString
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue(String(jsonDictionary: requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
request.httpBody = data
|
||||
let task = session.uploadTask(with: request, from: data)
|
||||
completionHandlersForTasks[task.taskIdentifier] = completionHandler
|
||||
dataCompletionHandlersForTasks[task.taskIdentifier] = { [weak self] data in
|
||||
var responseError: FileProviderDropboxError?
|
||||
if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data, encoding: .utf8))
|
||||
}
|
||||
completionHandler?(responseError)
|
||||
self?.delegateNotify(.create(path: targetPath), error: responseError)
|
||||
}
|
||||
task.taskDescription = operation.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, localFile: URL, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let size = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1
|
||||
func upload_simple(_ targetPath: String, data: Data? = nil, localFile: URL? = nil, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let size = data?.count ?? Int((try? localFile?.resourceValues(forKeys: [.fileSizeKey]))??.fileSize ?? -1)
|
||||
if size > 150 * 1024 * 1024 {
|
||||
let error = FileProviderDropboxError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
@@ -174,15 +140,23 @@ internal extension DropboxFileProvider {
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue(String(jsonDictionary: requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
|
||||
let task = session.uploadTask(with: request, fromFile: localFile)
|
||||
completionHandlersForTasks[task.taskIdentifier] = completionHandler
|
||||
dataCompletionHandlersForTasks[task.taskIdentifier] = { [weak self] data in
|
||||
let task: URLSessionUploadTask
|
||||
if let data = data {
|
||||
task = session.uploadTask(with: request, from: data)
|
||||
} else if let localFile = localFile {
|
||||
task = session.uploadTask(with: request, fromFile: localFile)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
completionHandlersForTasks[task.taskIdentifier] = { [weak self] error in
|
||||
var responseError: FileProviderDropboxError?
|
||||
if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: String(data: data, encoding: .utf8))
|
||||
// We can't fetch server result from delegate!
|
||||
responseError = FileProviderDropboxError(code: rCode, path: targetPath, errorDescription: nil)
|
||||
}
|
||||
completionHandler?(responseError)
|
||||
self?.delegateNotify(.create(path: targetPath), error: responseError)
|
||||
completionHandler?(responseError ?? error)
|
||||
self?.delegateNotify(.create(path: targetPath), error: responseError ?? error)
|
||||
}
|
||||
task.taskDescription = operation.json
|
||||
task.resume()
|
||||
|
||||
@@ -25,7 +25,11 @@ open class FTPFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open var credential: URLCredential?
|
||||
open var credential: URLCredential? {
|
||||
didSet {
|
||||
sessionDelegate?.credential = self.credential
|
||||
}
|
||||
}
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool
|
||||
public var validatingCache: Bool
|
||||
@@ -41,35 +45,28 @@ 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)
|
||||
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
|
||||
}
|
||||
self.sessionDelegate = newValue.delegate as? SessionDelegate
|
||||
initEmptySessionHandler(_session!.sessionDescription!)
|
||||
}
|
||||
return _session!
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,10 +124,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 {
|
||||
@@ -229,9 +231,8 @@ open class FTPFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
guard let response = response, response.hasPrefix("250") || (response.hasPrefix("50") && rfc3659enabled) else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
self.dispatch_queue.async {
|
||||
completionHandler(nil, error)
|
||||
completionHandler(nil, self.throwError(path, code: URLError.badServerResponse))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -242,9 +243,8 @@ open class FTPFileProvider: FileProviderBasicRemote {
|
||||
|
||||
let lines = response.components(separatedBy: "\n").flatMap { $0.isEmpty ? nil : $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||
guard lines.count > 2 else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
self.dispatch_queue.async {
|
||||
completionHandler(nil, error)
|
||||
completionHandler(nil, self.throwError(path, code: URLError.badServerResponse))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -346,10 +346,9 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
guard let response = response else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(opType, error: error)
|
||||
self.delegateNotify(opType, error: self.throwError(sourcePath, code: URLError.badServerResponse))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -381,9 +380,7 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
default:
|
||||
errorCode = URLError.cannotOpenFile
|
||||
}
|
||||
let escapedPath = sourcePath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? sourcePath
|
||||
let url = NSURL(string: escapedPath, relativeTo: self.baseURL) ?? self.baseURL! as NSURL
|
||||
let error = NSError(domain: URLError.errorDomain, code: errorCode.rawValue, userInfo: [NSURLErrorFailingURLErrorKey: url])
|
||||
let error = self.throwError(sourcePath, code: errorCode)
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(opType, error: error)
|
||||
@@ -441,7 +438,7 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
guard let response = response else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
let error = self.throwError(sourcePath, code: URLError.badServerResponse)
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(opType, error: error)
|
||||
@@ -454,9 +451,7 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
return
|
||||
}
|
||||
|
||||
let escapedPath = sourcePath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? sourcePath
|
||||
let url = NSURL(string: escapedPath, relativeTo: self.baseURL) ?? self.baseURL! as NSURL
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.cannotRemoveFile.rawValue, userInfo: [NSURLErrorFailingURLErrorKey: url])
|
||||
let error = self.throwError(sourcePath, code: URLError.cannotRemoveFile)
|
||||
self.dispatch_queue.async {
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(opType, error: error)
|
||||
@@ -467,6 +462,14 @@ extension FTPFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
// check file is not a folder
|
||||
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
|
||||
dispatch_queue.async {
|
||||
completionHandler?(self.throwError(localFile.path, code: URLError.fileIsDirectory))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
@@ -485,6 +488,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 +509,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 +534,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 +569,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 +611,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 +656,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 {
|
||||
|
||||
+23
-43
@@ -69,8 +69,7 @@ extension FTPFileProvider {
|
||||
if let data = data, let response = String(data: data, encoding: .utf8) {
|
||||
completionHandler(response.trimmingCharacters(in: CharacterSet(charactersIn: FTPFileProvider.carriage)), nil)
|
||||
} else {
|
||||
let badResponseError = NSError(domain: URLError.errorDomain, code: URLError.cannotParseResponse.rawValue, userInfo: nil)
|
||||
completionHandler(nil, badResponseError)
|
||||
completionHandler(nil, self.throwError("", code: URLError.cannotParseResponse))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -92,8 +91,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let data = data, let response = String(data: data, encoding: .utf8) else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.cannotParseResponse.rawValue, userInfo: nil)
|
||||
completionHandler(error)
|
||||
completionHandler(self.throwError("", code: URLError.cannotParseResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -114,8 +112,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let response = response else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
completionHandler(error)
|
||||
completionHandler(self.throwError("", code: URLError.badServerResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -131,8 +128,7 @@ extension FTPFileProvider {
|
||||
if response?.hasPrefix("2") ?? false {
|
||||
completionHandler(nil)
|
||||
} else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.userAuthenticationRequired.rawValue, userInfo: nil)
|
||||
completionHandler(error)
|
||||
completionHandler(self.throwError("", code: URLError.userAuthenticationRequired))
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -166,8 +162,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let response = response else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
completionHandler(error)
|
||||
completionHandler(self.throwError(path, code: URLError.badServerResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -200,15 +195,13 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let response = response, let destString = response.components(separatedBy: " ").flatMap({ $0 }).last else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
completionHandler(nil, error)
|
||||
completionHandler(nil, self.throwError("", code: URLError.badServerResponse))
|
||||
return
|
||||
}
|
||||
|
||||
let destArray = destString.components(separatedBy: ",").flatMap({ UInt32(trimmedNumber($0)) })
|
||||
guard destArray.count == 6 else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
completionHandler(nil, error)
|
||||
completionHandler(nil, self.throwError("", code: URLError.badServerResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -239,14 +232,12 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let response = response else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
completionHandler(nil, error)
|
||||
completionHandler(nil, self.throwError("", code: URLError.badServerResponse))
|
||||
return
|
||||
}
|
||||
|
||||
guard !response.hasPrefix("5") else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.cannotConnectToHost.rawValue, userInfo: nil)
|
||||
completionHandler(nil, error)
|
||||
completionHandler(nil, self.throwError("", code: URLError.cannotConnectToHost))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -296,8 +287,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let dataTask = dataTask else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
completionHandler([], error)
|
||||
completionHandler([], self.throwError(path, code: URLError.badServerResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -329,15 +319,13 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
if waitResult == .timedOut {
|
||||
error = NSError(domain: URLError.errorDomain, code: URLError.timedOut.rawValue, userInfo: nil)
|
||||
completionHandler([], error)
|
||||
completionHandler([], self.throwError(path, code: URLError.timedOut))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
guard let response = String(data: finalData, encoding: .utf8) else {
|
||||
error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
completionHandler([], error)
|
||||
completionHandler([], self.throwError(path, code: URLError.badServerResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -353,8 +341,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let response = response else {
|
||||
let badResponseError = NSError(domain: URLError.errorDomain, code: URLError.cannotParseResponse.rawValue, userInfo: nil)
|
||||
completionHandler([], badResponseError)
|
||||
completionHandler([], self.throwError(path, code: URLError.cannotParseResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -402,8 +389,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let dataTask = dataTask else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
completionHandler(nil, error)
|
||||
completionHandler(nil, self.throwError(filePath, code: URLError.badServerResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -440,8 +426,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
if waitResult == .timedOut {
|
||||
error = NSError(domain: URLError.errorDomain, code: URLError.timedOut.rawValue, userInfo: nil)
|
||||
completionHandler(nil, error)
|
||||
completionHandler(nil, self.throwError(filePath, code: URLError.timedOut))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -463,8 +448,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let response = response else {
|
||||
let badResponseError = NSError(domain: URLError.errorDomain, code: URLError.cannotParseResponse.rawValue, userInfo: nil)
|
||||
completionHandler(nil, badResponseError)
|
||||
completionHandler(nil, self.throwError(filePath, code: URLError.cannotParseResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -511,8 +495,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let dataTask = dataTask else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
completionHandler(nil, error)
|
||||
completionHandler(nil, self.throwError(filePath, code: URLError.badServerResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -549,7 +532,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
if waitResult == .timedOut {
|
||||
error = NSError(domain: URLError.errorDomain, code: URLError.timedOut.rawValue, userInfo: nil)
|
||||
error = self.throwError("", code: URLError.timedOut)
|
||||
completionHandler(nil, error)
|
||||
return
|
||||
}
|
||||
@@ -580,8 +563,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let response = response else {
|
||||
let badResponseError = NSError(domain: URLError.errorDomain, code: URLError.cannotParseResponse.rawValue, userInfo: nil)
|
||||
completionHandler(nil, badResponseError)
|
||||
completionHandler(nil, self.throwError(filePath, code: URLError.cannotParseResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -610,8 +592,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let dataTask = dataTask else {
|
||||
let error = NSError(domain: URLError.errorDomain, code: URLError.badServerResponse.rawValue, userInfo: nil)
|
||||
completionHandler(error)
|
||||
completionHandler(self.throwError(filePath, code: URLError.badServerResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -658,7 +639,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
if waitResult == .timedOut {
|
||||
error = NSError(domain: URLError.errorDomain, code: URLError.timedOut.rawValue, userInfo: nil)
|
||||
error = self.throwError(filePath, code: URLError.timedOut)
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
@@ -674,8 +655,7 @@ extension FTPFileProvider {
|
||||
}
|
||||
|
||||
guard let response = response else {
|
||||
let badResponseError = NSError(domain: URLError.errorDomain, code: URLError.cannotParseResponse.rawValue, userInfo: nil)
|
||||
completionHandler(badResponseError)
|
||||
completionHandler(self.throwError(filePath, code: URLError.cannotParseResponse))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -828,7 +808,7 @@ extension FTPFileProvider {
|
||||
file.size = Int64(attribute) ?? -1
|
||||
|
||||
case "media-type":
|
||||
file.allValues[.mimeType] = attribute
|
||||
file.allValues[.mimeTypeKey] = attribute
|
||||
|
||||
default:
|
||||
break
|
||||
|
||||
@@ -24,20 +24,14 @@ 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? {
|
||||
get {
|
||||
return allValues[.fileURL] as? URL
|
||||
return allValues[.fileURLKey] as? URL
|
||||
}
|
||||
set {
|
||||
allValues[.fileURL] = newValue
|
||||
allValues[.fileURLKey] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,8 +146,8 @@ open class FileObject: Equatable {
|
||||
}
|
||||
|
||||
internal func mapPredicate() -> [String: Any] {
|
||||
let mapDict: [URLResourceKey: String] = [.fileURL: "url", .nameKey: "name", .pathKey: "path", .fileSizeKey: "filesize", .creationDateKey: "creationDate",
|
||||
.contentModificationDateKey: "modifiedDate", .isHiddenKey: "isHidden", .isWritableKey: "isWritable", .serverDate: "serverDate", .entryTag: "entryTag", .mimeType: "mimeType"]
|
||||
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 {
|
||||
@@ -172,9 +166,9 @@ 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: .fileURL, NSMetadataItemFSNameKey: .nameKey, NSMetadataItemPathKey: .pathKey,
|
||||
let mapDict: [String: URLResourceKey] = [NSMetadataItemURLKey: .fileURLKey, NSMetadataItemFSNameKey: .nameKey, NSMetadataItemPathKey: .pathKey,
|
||||
NSMetadataItemFSSizeKey: .fileSizeKey, NSMetadataItemFSCreationDateKey: .creationDateKey,
|
||||
NSMetadataItemFSContentChangeDateKey: .contentModificationDateKey, "kMDItemFSInvisible": .isHiddenKey, "kMDItemFSIsWriteable": .isWritableKey, "kMDItemKind": .mimeType]
|
||||
NSMetadataItemFSContentChangeDateKey: .contentModificationDateKey, "kMDItemFSInvisible": .isHiddenKey, "kMDItemFSIsWriteable": .isWritableKey, "kMDItemKind": .mimeTypeKey]
|
||||
|
||||
if let cQuery = query as? NSCompoundPredicate {
|
||||
let newSub = cQuery.subpredicates.map { convertPredicate(fromSpotlight: $0 as! NSPredicate) }
|
||||
|
||||
@@ -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
|
||||
@@ -938,6 +939,8 @@ public enum FileOperationType: CustomStringConvertible {
|
||||
}
|
||||
let dest = json["dest"] as? String
|
||||
switch type {
|
||||
case "Fetch":
|
||||
self = .fetch(path: source)
|
||||
case "Create":
|
||||
self = .create(path: source)
|
||||
case "Modify":
|
||||
|
||||
@@ -38,10 +38,19 @@ extension URLFileResourceType {
|
||||
}
|
||||
|
||||
internal extension URLResourceKey {
|
||||
static let fileURL = URLResourceKey(rawValue: "NSURLFileURLKey")
|
||||
static let serverDate = URLResourceKey(rawValue: "NSURLServerDateKey")
|
||||
static let entryTag = URLResourceKey(rawValue: "NSURLEntryTagKey")
|
||||
static let mimeType = URLResourceKey(rawValue: "NSURLMIMETypeIdentifierKey")
|
||||
static let fileURLKey = URLResourceKey(rawValue: "NSURLFileURLKey")
|
||||
static let serverDateKey = URLResourceKey(rawValue: "NSURLServerDateKey")
|
||||
static let entryTagKey = URLResourceKey(rawValue: "NSURLEntryTagKey")
|
||||
static let mimeTypeKey = URLResourceKey(rawValue: "NSURLMIMETypeIdentifierKey")
|
||||
|
||||
@available(*, deprecated, renamed: "fileURLKey")
|
||||
static let fileURL = fileURLKey
|
||||
@available(*, deprecated, renamed: "serverDateKey")
|
||||
static let serverDate = serverDateKey
|
||||
@available(*, deprecated, renamed: "entryTagKey")
|
||||
static let entryTag = entryTagKey
|
||||
@available(*, deprecated, renamed: "mimeTypeKey")
|
||||
static let mimeType = mimeTypeKey
|
||||
}
|
||||
|
||||
internal extension URL {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -37,7 +37,7 @@ public final class LocalFileObject: FileObject {
|
||||
/// Initiates a `LocalFileObject` with attributes of file in url.
|
||||
public convenience init?(fileWithURL fileURL: URL) {
|
||||
do {
|
||||
let values = try fileURL.resourceValues(forKeys: [.nameKey, .fileSizeKey, .fileAllocatedSizeKey, .creationDateKey, .contentModificationDateKey, .fileResourceTypeKey, .isHiddenKey, .isWritableKey, .typeIdentifierKey, .generationIdentifierKey, .documentIdentifierKey])
|
||||
let values = try fileURL.resourceValues(forKeys: [.nameKey, .fileSizeKey, .totalFileSizeKey, .fileAllocatedSizeKey, .totalFileAllocatedSizeKey, .creationDateKey, .contentModificationDateKey, .fileResourceTypeKey, .isHiddenKey, .isWritableKey, .typeIdentifierKey, .generationIdentifierKey, .documentIdentifierKey])
|
||||
let path = fileURL.relativePath.hasPrefix("/") ? fileURL.relativePath : "/" + fileURL.relativePath
|
||||
|
||||
self.init(url: fileURL, name: values.name ?? fileURL.lastPathComponent, path: path)
|
||||
@@ -216,9 +216,11 @@ internal class LocalFileProviderManagerDelegate: NSObject, FileManagerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
/// Local operation handling is limited. Please don't use as much as possible.
|
||||
/// - Note: Local operation handling is limited. Please don't use as much as possible.
|
||||
open class LocalOperationHandle: OperationHandle {
|
||||
/// Url of file which operation is doing on
|
||||
public let baseURL: URL
|
||||
/// Type of operation
|
||||
public let operationType: FileOperationType
|
||||
|
||||
init (operationType: FileOperationType, baseURL: URL?) {
|
||||
|
||||
@@ -33,7 +33,11 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
open var credential: URLCredential?
|
||||
open var credential: URLCredential? {
|
||||
didSet {
|
||||
sessionDelegate?.credential = self.credential
|
||||
}
|
||||
}
|
||||
open private(set) var cache: URLCache?
|
||||
public var useCache: Bool
|
||||
public var validatingCache: Bool
|
||||
@@ -41,16 +45,30 @@ 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)
|
||||
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
|
||||
}
|
||||
self.sessionDelegate = newValue.delegate as? SessionDelegate
|
||||
initEmptySessionHandler(_session!.sessionDescription!)
|
||||
}
|
||||
return _session!
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,6 +132,10 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let sessionuuid = _session?.sessionDescription {
|
||||
removeSessionHandler(for: sessionuuid)
|
||||
}
|
||||
|
||||
if fileProviderCancelTasksOnInvalidating {
|
||||
_session?.invalidateAndCancel()
|
||||
} else {
|
||||
@@ -279,6 +301,14 @@ extension OneDriveFileProvider: FileProviderOperations {
|
||||
}
|
||||
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
// check file is not a folder
|
||||
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
|
||||
dispatch_queue.async {
|
||||
completionHandler?(self.throwError(localFile.path, code: URLError.fileIsDirectory))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
@@ -292,11 +322,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 +364,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])
|
||||
@@ -61,20 +61,20 @@ public final class OneDriveFileObject: FileObject {
|
||||
/// MIME type of file contents returned by OneDrive server.
|
||||
open internal(set) var contentType: String {
|
||||
get {
|
||||
return allValues[.mimeType] as? String ?? ""
|
||||
return allValues[.mimeTypeKey] as? String ?? ""
|
||||
}
|
||||
set {
|
||||
allValues[.mimeType] = newValue
|
||||
allValues[.mimeTypeKey] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP E-Tag, can be used to mark changed files.
|
||||
open internal(set) var entryTag: String? {
|
||||
get {
|
||||
return allValues[.entryTag] as? String
|
||||
return allValues[.entryTagKey] as? String
|
||||
}
|
||||
set {
|
||||
allValues[.entryTag] = newValue
|
||||
allValues[.entryTagKey] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,37 +113,8 @@ internal extension OneDriveFileProvider {
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
if data.count > 100 * 1024 * 1024 {
|
||||
let error = FileProviderOneDriveError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
self.delegateNotify(.create(path: targetPath), error: error)
|
||||
return nil
|
||||
}
|
||||
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
|
||||
let url = self.url(of: targetPath, modifier: "content\(queryStr)")
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PUT"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = data
|
||||
let task = session.uploadTask(with: request, from: data)
|
||||
completionHandlersForTasks[task.taskIdentifier] = completionHandler
|
||||
dataCompletionHandlersForTasks[task.taskIdentifier] = { [weak self] data in
|
||||
var responseError: FileProviderOneDriveError?
|
||||
if let code = (task.response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: String(data: data, encoding: .utf8))
|
||||
}
|
||||
completionHandler?(responseError)
|
||||
self?.delegateNotify(.create(path: targetPath), error: responseError)
|
||||
}
|
||||
task.taskDescription = operation.json
|
||||
task.resume()
|
||||
return RemoteOperationHandle(operationType: operation, tasks: [task])
|
||||
}
|
||||
|
||||
func upload_simple(_ targetPath: String, localFile: URL, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let size = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1
|
||||
func upload_simple(_ targetPath: String, data: Data? = nil , localFile: URL? = nil, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
let size = data?.count ?? (try? localFile?.resourceValues(forKeys: [.fileSizeKey]))??.fileSize ?? -1
|
||||
if size > 100 * 1024 * 1024 {
|
||||
let error = FileProviderOneDriveError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
|
||||
completionHandler?(error)
|
||||
@@ -156,15 +127,23 @@ internal extension OneDriveFileProvider {
|
||||
request.httpMethod = "PUT"
|
||||
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||
let task = session.uploadTask(with: request, fromFile: localFile)
|
||||
completionHandlersForTasks[task.taskIdentifier] = completionHandler
|
||||
dataCompletionHandlersForTasks[task.taskIdentifier] = { [weak self] data in
|
||||
let task: URLSessionUploadTask
|
||||
if let data = data {
|
||||
task = session.uploadTask(with: request, from: data)
|
||||
} else if let localFile = localFile {
|
||||
task = session.uploadTask(with: request, fromFile: localFile)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
completionHandlersForTasks[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) {
|
||||
responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: String(data: data, encoding: .utf8))
|
||||
// We can't fetch server result from delegate!
|
||||
responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: nil)
|
||||
}
|
||||
completionHandler?(responseError)
|
||||
self?.delegateNotify(.create(path: targetPath), error: responseError)
|
||||
completionHandler?(responseError ?? error)
|
||||
self?.delegateNotify(.create(path: targetPath), error: responseError ?? error)
|
||||
}
|
||||
task.taskDescription = operation.json
|
||||
task.resume()
|
||||
|
||||
+69
-35
@@ -90,58 +90,83 @@ 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?) {
|
||||
public init(fileProvider: FileProviderBasicRemote & FileProviderOperations) {
|
||||
self.fileProvider = fileProvider
|
||||
self.credential = credential
|
||||
self.credential = fileProvider.credential
|
||||
}
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
if error != nil {
|
||||
let completionHandler = completionHandlersForTasks[task.taskIdentifier] ?? nil
|
||||
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.removeValue(forKey: task.taskIdentifier)
|
||||
_ = completionHandlersForTasks[session.sessionDescription!]?.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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if !(task is URLSessionDownloadTask), case FileOperationType.fetch = op {
|
||||
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(),
|
||||
@@ -149,6 +174,15 @@ class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDeleg
|
||||
return
|
||||
}
|
||||
|
||||
switch op {
|
||||
case .create(path: let path):
|
||||
if path.hasSuffix("/") { return }
|
||||
case .modify:
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
@@ -156,7 +190,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(),
|
||||
@@ -169,11 +203,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)
|
||||
}
|
||||
|
||||
@@ -189,7 +223,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)
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -11,11 +11,11 @@ import Foundation
|
||||
// This client implementation is for little-endian platform, namely x86, x64 & arm
|
||||
// For big-endian platforms like PowerPC, there must be a huge overhaul
|
||||
|
||||
protocol SMBProtocolClientDelegate: class {
|
||||
func receivedResponse(client: SMB2ProtocolClient, response: SMBResponse, for: SMBRequest)
|
||||
protocol FileProviderSMBTaskDelegate: class {
|
||||
func receivedResponse(client: FileProviderSMBTask, response: SMBResponse, for: SMBRequest)
|
||||
}
|
||||
|
||||
class SMB2ProtocolClient: FileProviderStreamTask {
|
||||
class FileProviderSMBTask: FileProviderStreamTask {
|
||||
var timeout: TimeInterval = 30
|
||||
|
||||
private(set) var lastMessageID: UInt64 = 0
|
||||
@@ -31,14 +31,14 @@ class SMB2ProtocolClient: FileProviderStreamTask {
|
||||
private(set) var requestStack = [Int: SMBRequest]()
|
||||
private(set) var responseStack = [Int: SMBResponse]()
|
||||
|
||||
weak var delegate: SMBProtocolClientDelegate?
|
||||
weak var delegate: FileProviderSMBTaskDelegate?
|
||||
|
||||
func sendNegotiate(completionHandler: SimpleCompletionHandler) -> UInt64 {
|
||||
let mId = messageId()
|
||||
let smbHeader = SMB2.Header(command: .NEGOTIATE, creditRequestResponse: UInt16(126), messageId: mId, treeId: UInt32(0), sessionId: UInt64(0))
|
||||
let msg = SMB2.NegotiateRequest()
|
||||
let data = createSMB2Message(header: smbHeader, message: msg)
|
||||
self.write(data, timeout: 0, completionHandler: { (e) in
|
||||
self.write(data, timeout: timeout, completionHandler: { (e) in
|
||||
completionHandler?(e)
|
||||
})
|
||||
return mId
|
||||
@@ -50,7 +50,7 @@ class SMB2ProtocolClient: FileProviderStreamTask {
|
||||
let smbHeader = SMB2.Header(command: SMB2.Command.SESSION_SETUP, creditRequestResponse: credit, messageId: mId, treeId: UInt32(0), sessionId: sessionId)
|
||||
let msg = SMB2.SessionSetupRequest(singing: [])
|
||||
let data = createSMB2Message(header: smbHeader, message: msg)
|
||||
self.write(data, timeout: 0, completionHandler: { (e) in
|
||||
self.write(data, timeout: timeout, completionHandler: { (e) in
|
||||
if self.sessionId == 0 {
|
||||
self.readData(ofMinLength: 64, maxLength: 65536, timeout: self.timeout, completionHandler: { (data, eof, e2) in
|
||||
// TODO: set session id
|
||||
@@ -73,7 +73,7 @@ class SMB2ProtocolClient: FileProviderStreamTask {
|
||||
let tcHeader = SMB2.TreeConnectRequest.Header(flags: [])
|
||||
let msg = SMB2.TreeConnectRequest(header: tcHeader, host: host, share: share)
|
||||
let data = createSMB2Message(header: smbHeader, message: msg!)
|
||||
self.write(data, timeout: 0, completionHandler: { (e) in
|
||||
self.write(data, timeout: timeout, completionHandler: { (e) in
|
||||
completionHandler?(e)
|
||||
|
||||
})
|
||||
@@ -85,7 +85,7 @@ class SMB2ProtocolClient: FileProviderStreamTask {
|
||||
let smbHeader = SMB2.Header(command: .TREE_DISCONNECT, creditRequestResponse: 111, messageId: mId, treeId: treeId, sessionId: sessionId)
|
||||
let msg = SMB2.TreeDisconnect()
|
||||
let data = createSMB2Message(header: smbHeader, message: msg)
|
||||
self.write(data, timeout: 0, completionHandler: { (e) in
|
||||
self.write(data, timeout: timeout, completionHandler: { (e) in
|
||||
completionHandler?(e)
|
||||
})
|
||||
return mId
|
||||
@@ -96,7 +96,7 @@ class SMB2ProtocolClient: FileProviderStreamTask {
|
||||
let smbHeader = SMB2.Header(command: .LOGOFF, creditRequestResponse: 0, messageId: mId, treeId: 0, sessionId: sessionId)
|
||||
let msg = SMB2.LogOff()
|
||||
let data = createSMB2Message(header: smbHeader, message: msg)
|
||||
self.write(data, timeout: 0, completionHandler: { (e) in
|
||||
self.write(data, timeout: timeout, completionHandler: { (e) in
|
||||
completionHandler?(e)
|
||||
})
|
||||
return mId
|
||||
@@ -108,7 +108,7 @@ class SMB2ProtocolClient: FileProviderStreamTask {
|
||||
}
|
||||
|
||||
// MARK: create and analyse messages
|
||||
extension SMB2ProtocolClient {
|
||||
extension FileProviderSMBTask {
|
||||
func determineSMBVersion(_ data: Data) -> Float {
|
||||
let smbverChar: Int8 = Int8(bitPattern: data.first ?? 0)
|
||||
let version = 0 - smbverChar
|
||||
|
||||
@@ -44,16 +44,30 @@ 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)
|
||||
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
|
||||
}
|
||||
self.sessionDelegate = newValue.delegate as? SessionDelegate
|
||||
initEmptySessionHandler(_session!.sessionDescription!)
|
||||
}
|
||||
return _session!
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,8 +108,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 +127,10 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let sessionuuid = _session?.sessionDescription {
|
||||
removeSessionHandler(for: sessionuuid)
|
||||
}
|
||||
|
||||
if fileProviderCancelTasksOnInvalidating {
|
||||
_session?.invalidateAndCancel()
|
||||
} else {
|
||||
@@ -120,14 +138,29 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
}
|
||||
|
||||
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
public func contentsOfDirectory(path: String, completionHandler: @escaping (([FileObject], Error?) -> Void)) {
|
||||
self.contentsOfDirectory(path: path, including: [], completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an Array of `FileObject`s identifying the the directory entries via asynchronous completion handler.
|
||||
|
||||
If the directory contains no entries or an error is occured, this method will return the empty array.
|
||||
|
||||
- Parameter path: path to target directory. If empty, `currentPath` value will be used.
|
||||
- Parameter including: An array which determines which file properties should be considered to fetch.
|
||||
- Parameter completionHandler: a closure with result of directory entries or error.
|
||||
- `contents`: An array of `FileObject` identifying the the directory entries.
|
||||
- `error`: Error returned by system.
|
||||
*/
|
||||
open func contentsOfDirectory(path: String, including: [URLResourceKey], completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
||||
let opType = FileOperationType.fetch(path: path)
|
||||
let url = self.url(of: path).appendingPathComponent("")
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PROPFIND"
|
||||
request.setValue("1", forHTTPHeaderField: "Depth")
|
||||
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n\(WebDavFileObject.propString(including))\n</D:propfind>".data(using: .utf8)
|
||||
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
|
||||
runDataTask(with: request, operationHandle: RemoteOperationHandle(operationType: opType, tasks: []), completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
@@ -149,12 +182,27 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
|
||||
}
|
||||
|
||||
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
self.attributesOfItem(path: path, including: [], completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a `FileObject` containing the attributes of the item (file, directory, symlink, etc.) at the path in question via asynchronous completion handler.
|
||||
|
||||
If the directory contains no entries or an error is occured, this method will return the empty `FileObject`.
|
||||
|
||||
- Parameter path: path to target directory. If empty, `currentPath` value will be used.
|
||||
- Parameter including: An array which determines which file properties should be considered to fetch.
|
||||
- Parameter completionHandler: a closure with result of directory entries or error.
|
||||
- `attributes`: A `FileObject` containing the attributes of the item.
|
||||
- `error`: Error returned by system.
|
||||
*/
|
||||
open func attributesOfItem(path: String, including: [URLResourceKey], completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
||||
let url = self.url(of: path)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "PROPFIND"
|
||||
request.setValue("1", forHTTPHeaderField: "Depth")
|
||||
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
|
||||
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n\(WebDavFileObject.propString(including))\n</D:propfind>".data(using: .utf8)
|
||||
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
|
||||
runDataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderWebDavError?
|
||||
@@ -345,6 +393,14 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
|
||||
@discardableResult
|
||||
open func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
|
||||
// check file is not a folder
|
||||
guard (try? localFile.resourceValues(forKeys: [.fileResourceTypeKey]))?.fileResourceType ?? .unknown == .regular else {
|
||||
dispatch_queue.async {
|
||||
completionHandler?(self.throwError(localFile.path, code: URLError.fileIsDirectory))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
|
||||
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
|
||||
return nil
|
||||
@@ -356,14 +412,14 @@ extension WebDAVFileProvider: FileProviderOperations {
|
||||
}
|
||||
request.httpMethod = "PUT"
|
||||
let task = session.uploadTask(with: request, fromFile: localFile)
|
||||
completionHandlersForTasks[task.taskIdentifier] = completionHandler
|
||||
dataCompletionHandlersForTasks[task.taskIdentifier] = { [weak self] data in
|
||||
completionHandlersForTasks[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) {
|
||||
responseError = FileProviderWebDavError(code: rCode, path: toPath, errorDescription: String(data: data, encoding: .utf8), url: url)
|
||||
// We can't fetch server result from delegate!
|
||||
responseError = FileProviderWebDavError(code: rCode, path: toPath, errorDescription: nil, url: url)
|
||||
}
|
||||
completionHandler?(responseError)
|
||||
self?.delegateNotify(.create(path: toPath), error: responseError)
|
||||
completionHandler?(responseError ?? error)
|
||||
self?.delegateNotify(.create(path: toPath), error: responseError ?? error)
|
||||
}
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
@@ -379,8 +435,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
|
||||
@@ -413,20 +469,35 @@ 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)
|
||||
}
|
||||
}
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
return handle
|
||||
}
|
||||
|
||||
@@ -444,14 +515,14 @@ extension WebDAVFileProvider: FileProviderReadWrite {
|
||||
request.setValue("F", forHTTPHeaderField: "Overwrite")
|
||||
}
|
||||
let task = session.uploadTask(with: request, from: data ?? Data())
|
||||
completionHandlersForTasks[task.taskIdentifier] = completionHandler
|
||||
dataCompletionHandlersForTasks[task.taskIdentifier] = { [weak self] data in
|
||||
completionHandlersForTasks[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) {
|
||||
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data, encoding: .utf8), url: url)
|
||||
// We can't fetch server result from delegate!
|
||||
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: nil, url: url)
|
||||
}
|
||||
completionHandler?(responseError)
|
||||
self?.delegateNotify(.create(path: path), error: responseError)
|
||||
completionHandler?(responseError ?? error)
|
||||
self?.delegateNotify(opType, error: responseError ?? error)
|
||||
}
|
||||
task.taskDescription = opType.json
|
||||
task.resume()
|
||||
@@ -591,7 +662,7 @@ struct DavResponse {
|
||||
public final class WebDavFileObject: FileObject {
|
||||
internal init(_ davResponse: DavResponse) {
|
||||
let href = davResponse.href
|
||||
let name = davResponse.prop["displayname"] ?? (davResponse.hrefString.removingPercentEncoding! as NSString).lastPathComponent
|
||||
let name = davResponse.prop["displayname"] ?? davResponse.href.lastPathComponent
|
||||
let relativePath = href.relativePath
|
||||
let path = relativePath.hasPrefix("/") ? relativePath : ("/" + relativePath)
|
||||
super.init(url: href, name: name, path: path)
|
||||
@@ -607,22 +678,54 @@ public final class WebDavFileObject: FileObject {
|
||||
/// MIME type of the file.
|
||||
open internal(set) var contentType: String {
|
||||
get {
|
||||
return allValues[.mimeType] as? String ?? ""
|
||||
return allValues[.mimeTypeKey] as? String ?? ""
|
||||
}
|
||||
set {
|
||||
allValues[.mimeType] = newValue
|
||||
allValues[.mimeTypeKey] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP E-Tag, can be used to mark changed files.
|
||||
open internal(set) var entryTag: String? {
|
||||
get {
|
||||
return allValues[.entryTag] as? String
|
||||
return allValues[.entryTagKey] as? String
|
||||
}
|
||||
set {
|
||||
allValues[.entryTag] = newValue
|
||||
allValues[.entryTagKey] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
internal class func resourceKeyToDAVProp(_ key: URLResourceKey) -> String? {
|
||||
switch key {
|
||||
case URLResourceKey.fileSizeKey:
|
||||
return "getcontentlength"
|
||||
case URLResourceKey.creationDateKey:
|
||||
return "creationdate"
|
||||
case URLResourceKey.contentModificationDateKey:
|
||||
return "getlastmodified"
|
||||
case URLResourceKey.fileResourceTypeKey, URLResourceKey.mimeTypeKey:
|
||||
return "getcontenttype"
|
||||
case URLResourceKey.isHiddenKey:
|
||||
return "ishidden"
|
||||
case URLResourceKey.entryTagKey:
|
||||
return "getetag"
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
internal class func propString(_ keys: [URLResourceKey]) -> String {
|
||||
var propKeys = ""
|
||||
for item in keys {
|
||||
if let prop = WebDavFileObject.resourceKeyToDAVProp(item) {
|
||||
propKeys += "<D:prop><D:\(prop)/></D:prop>"
|
||||
}
|
||||
}
|
||||
if propKeys.isEmpty {
|
||||
propKeys = "<D:allprop/>"
|
||||
}
|
||||
return propKeys
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by WebDAV server when trying to access or do operations on a file or folder.
|
||||
|
||||
Reference in New Issue
Block a user