Compare commits

...

11 Commits

Author SHA1 Message Date
Amir Abbas a605b0cd85 Added FTP Recursive listing, Implemented ftp search
- Implemented FTP full directory remove
- Fixed FTP STOR bug
- Implemented relativePath(of:) for FTP
- Initial implementation of FTP active mode
2017-04-11 22:21:13 +04:30
Amir Abbas bf7043de29 Fixed relativePath(of:) bug, made it overridable
- Fixed StreamTask.taskDescription bug
2017-04-11 19:43:34 +04:30
Amir Abbas ff5e13931f Fixed WebDAVProvider.contents bug, refactored FTP Error 2017-04-09 14:09:03 +04:30
Amir Abbas 75af738d2e Made SessionDelegate init public, fixed pod issue 2017-04-05 02:05:28 +04:30
Amir Abbas f54a1253e4 Throwing error when trying to upload a directory 2017-04-05 00:24:24 +04:30
Amir Abbas 1394a92662 Add Documentation 2017-04-03 21:03:48 +04:30
Amir Abbas dab171c755 Setting sessionDelgate credential to updated one. 2017-04-03 18:59:50 +04:30
Amir Abbas ea5de2e2aa Added progress for content(path:) method
- Fixed issue with colliding handlers between sessions.
- Sessions can be set.
- SessionDelegate class is now public.
2017-04-03 18:50:13 +04:30
Amir Abbas 2253cca086 Fixed deprecated URLResourceKey items 2017-04-03 12:52:27 +04:30
Amir Abbas e15a900ade Renamed URLResourceKey additions to have Key prefix 2017-04-03 12:49:35 +04:30
Amir Abbas 5c2c56c44c Fixed: Calling completion handler for upload task
- Added including (file object properties) argument to WebDAV provider (resolves #31)
2017-04-03 12:41:11 +04:30
20 changed files with 795 additions and 454 deletions
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
#
s.name = "FileProvider"
s.version = "0.15.0"
s.version = "0.15.4"
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 ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
+11 -10
View File
@@ -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.4;
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.4;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
+3 -4
View File
@@ -28,10 +28,9 @@ All functions do async calls and it wont block your main thread.
- [x] **LocalFileProvider** a wrapper around `FileManager` with some additions like builtin coordinating, searching and reading a portion of file.
- [x] **CloudFileProvider** A wrapper around app's ubiquitous container API of iCloud Drive.
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission standard, supported by some cloud services like `Box.com` and `Yandex.disk`.
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission standard, supported by some cloud services like `ownCloud`, `Box.com` and `Yandex.disk`.
- [x] **FTPFileProvider** While deprecated in 1990s due to serious security concerns, it's still in use on some Web hosts.
* Recursive directory removing & searching is not implemented yet.
* Active mode is not implemented yet (and probably won`t).
* Active mode is not implemented yet.
- [x] **DropboxFileProvider** A wrapper around Dropbox Web API.
* For now it has limitation in uploading files up to 150MB.
- [x] **OneDriveFileProvider** A wrapper around OneDrive REST API, works with `onedrive.com` and compatible (business) servers.
@@ -69,7 +68,7 @@ github "amosavian/FileProvider"
Or to use in Swift Package Manager add this line in `Dependencies`:
```swift
.Package(url: "https://github.com/amosavian/FileProvider.git", majorVersion: 0, minorVersion: 12)
.Package(url: "https://github.com/amosavian/FileProvider.git", majorVersion: 0)
```
### Manually
+3
View File
@@ -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?) {
+61 -33
View File
@@ -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
View File
@@ -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()
+27
View File
@@ -23,6 +23,7 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
return (_underlyingSession.delegate as? FPSStreamDelegate)
}
fileprivate var _taskIdentifier: Int
fileprivate var _taskDescription: String?
/// Force using `URLSessionStreamTask` for iOS 9 and later
public var useURLSession = true
@@ -50,6 +51,32 @@ public class FileProviderStreamTask: URLSessionTask, StreamDelegate {
return _taskIdentifier
}
/// An app-provided description of the current task.
///
/// This value may be nil. It is intended to contain human-readable strings that you can
/// then display to the user as part of your apps user interface.
open override var taskDescription: String? {
get {
if #available(iOS 9.0, OSX 10.11, *) {
if self.useURLSession {
return _underlyingTask!.taskDescription
}
}
return _taskDescription
}
set {
if #available(iOS 9.0, OSX 10.11, *) {
if self.useURLSession {
_underlyingTask!.taskDescription = newValue
return
}
}
_taskDescription = newValue
}
}
fileprivate var _state: URLSessionTask.State = .suspended
/**
* The current state of the taskactive, suspended, in the process
+173 -99
View File
@@ -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!
}
/**
@@ -86,7 +83,7 @@ open class FTPFileProvider: FileProviderBasicRemote {
urlComponents.port = urlComponents.port ?? 21
urlComponents.scheme = urlComponents.scheme ?? "ftp"
self.baseURL = urlComponents.url!
self.baseURL = (urlComponents.url!.path.hasSuffix("/") ? urlComponents.url! : urlComponents.url!.appendingPathComponent("")).absoluteURL
self.currentPath = ""
self.useCache = false
self.validatingCache = true
@@ -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
}
@@ -263,7 +263,21 @@ open class FTPFileProvider: FileProviderBasicRemote {
}
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
NotImplemented()
self.recursiveList(path: path, useMLST: true, foundItemsHandler: { items in
if let foundItemHandler = foundItemHandler {
for item in items where query.evaluate(with: item.mapPredicate()) {
foundItemHandler(item)
}
}
}, completionHandler: {files, error in
if let error = error {
completionHandler([], error)
return
}
let foundFiles = files.filter { query.evaluate(with: $0.mapPredicate()) }
completionHandler(foundFiles, nil)
})
}
public func url(of path: String?) -> URL {
@@ -272,7 +286,21 @@ open class FTPFileProvider: FileProviderBasicRemote {
var baseUrlComponent = URLComponents(url: self.baseURL!, resolvingAgainstBaseURL: true)
baseUrlComponent?.user = credential?.user
baseUrlComponent?.password = credential?.password
return URL(string: path, relativeTo: baseURL) ?? baseURL!
return URL(string: path, relativeTo: baseUrlComponent?.url ?? baseURL) ?? baseUrlComponent?.url ?? baseURL!
}
public func relativePathOf(url: URL) -> String {
// check if url derieved from current base url
let relativePath = url.relativePath
if !relativePath.isEmpty, url.baseURL == self.baseURL {
return (relativePath.removingPercentEncoding ?? relativePath).replacingOccurrences(of: "/", with: "", options: .anchored)
}
if !relativePath.isEmpty, self.baseURL == self.url(of: "/") {
return (relativePath.removingPercentEncoding ?? relativePath).replacingOccurrences(of: "/", with: "", options: .anchored)
}
return relativePath.replacingOccurrences(of: "/", with: "", options: .anchored)
}
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
@@ -346,10 +374,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 +408,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)
@@ -424,49 +449,80 @@ extension FTPFileProvider: FileProviderOperations {
return operationHandle
}
private func fallbackRemove(_ opType: FileOperationType, on task: FileProviderStreamTask, recursive: Bool = false, completionHandler: SimpleCompletionHandler) {
private func fallbackRemove(_ opType: FileOperationType, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
guard let sourcePath = opType.source else { return }
switch recursive {
case true:
NotImplemented()
case false:
self.execute(command: "SITE RMDIR \(ftpPath(sourcePath))", on: task) { (response, error) in
if let error = error {
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(opType, error: error)
}
return
}
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)
}
return
}
if response.hasPrefix("50") {
self.fallbackRemove(opType, on: task, recursive: true, completionHandler: completionHandler)
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])
self.execute(command: "SITE RMDIR \(ftpPath(sourcePath))", on: task) { (response, error) in
if let error = error {
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(opType, error: error)
}
return
}
guard let response = response else {
let error = self.throwError(sourcePath, code: URLError.badServerResponse)
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(opType, error: error)
}
return
}
if response.hasPrefix("50") {
self.fallbackRecursiveRemove(opType, on: task, completionHandler: completionHandler)
return
}
let error = self.throwError(sourcePath, code: URLError.cannotRemoveFile)
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(opType, error: error)
}
}
return
}
private func fallbackRecursiveRemove(_ opType: FileOperationType, on task: FileProviderStreamTask, completionHandler: SimpleCompletionHandler) {
guard let sourcePath = opType.source else { return }
self.recursiveList(path: sourcePath, useMLST: true, completionHandler: { (contents, error) in
if let error = error {
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(opType, error: error)
}
return
}
let sortedContents = contents.sorted(by: {
$0.path.localizedStandardCompare($1.path) == .orderedDescending
})
var command = ""
for file in sortedContents {
command += (file.isDirectory ? "RMD \(self.ftpPath(file.path))" : "DELE \(self.ftpPath(file.path))") + "\r\n"
}
command += "RMD \(self.ftpPath(sourcePath))"
self.execute(command: command, on: task, completionHandler: { (response, error) in
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(opType, error: error)
}
// TODO: Digest response
})
})
}
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 +541,11 @@ extension FTPFileProvider: FileProviderOperations {
self.ftpStore(task, filePath: self.ftpPath(toPath), fromData: nil, fromFile: localFile, onTask: {
operation.add(task: $0)
$0.taskDescription = opType.json
}, onProgress: { bytesSent, totalSent, expectedBytes in
DispatchQueue.main.async {
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(Double(totalSent) / Double(expectedBytes)))
}
}, completionHandler: { (error) in
self.ftpQuit(task)
self.dispatch_queue.async {
@@ -505,29 +566,38 @@ extension FTPFileProvider: FileProviderOperations {
let operation = RemoteOperationHandle(operationType: opType, tasks: [])
if self.useAppleImplementation {
let task = session.downloadTask(with: url(of: path)) { (tempDest, response, error) in
self.attributesOfItem(path: path, completionHandler: { (file, error) in
if let error = error {
self.dispatch_queue.async {
completionHandler?(error)
self.delegateNotify(opType, error: error)
}
return
}
if let tempDest = tempDest {
if file?.isDirectory ?? false {
self.dispatch_queue.async {
let error = self.throwError(path, code: URLError.fileIsDirectory)
completionHandler?(error)
self.delegateNotify(opType, error: error)
}
return
}
let task = self.session.downloadTask(with: self.url(of: path))
completionHandlersForTasks[self.session.sessionDescription!]?[task.taskIdentifier] = completionHandler
downloadCompletionHandlersForTasks[self.session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
do {
try FileManager.default.moveItem(at: tempDest, to: destURL)
self.dispatch_queue.async {
completionHandler?(nil)
}
} catch let error {
self.dispatch_queue.async {
completionHandler?(error)
}
try FileManager.default.moveItem(at: tempURL, to: destURL)
completionHandler?(nil)
} catch let e {
completionHandler?(e)
}
}
}
operation.add(task: task)
task.resume()
operation.add(task: task)
task.taskDescription = opType.json
task.resume()
})
} else {
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
self.ftpLogin(task) { (error) in
@@ -540,6 +610,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 +645,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 +687,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 +732,11 @@ 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
}, onProgress: { bytesSent, totalSent, expectedBytes in
DispatchQueue.main.async {
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(Double(totalSent) / Double(expectedBytes)))
}
}, completionHandler: { (error) in
self.ftpQuit(task)
self.dispatch_queue.async {
+139 -74
View File
@@ -9,8 +9,6 @@
import Foundation
extension FTPFileProvider {
private static let carriage = "\r\n"
func delegateNotify(_ operation: FileOperationType, error: Error?) {
DispatchQueue.main.async(execute: {
if error == nil {
@@ -48,7 +46,7 @@ extension FTPFileProvider {
func execute(command: String, on task: FileProviderStreamTask, minLength: Int = 4, afterSend: ((_ error: Error?) -> Void)? = nil, completionHandler: @escaping (_ response: String?, _ error: Error?) -> Void) {
let timeout = session.configuration.timeoutIntervalForRequest
let terminalcommand = command + FTPFileProvider.carriage
let terminalcommand = command + "\r\n"
task.write(terminalcommand.data(using: .utf8)!, timeout: timeout) { (error) in
if let error = error {
completionHandler(nil, error)
@@ -67,10 +65,9 @@ extension FTPFileProvider {
}
if let data = data, let response = String(data: data, encoding: .utf8) {
completionHandler(response.trimmingCharacters(in: CharacterSet(charactersIn: FTPFileProvider.carriage)), nil)
completionHandler(response.trimmingCharacters(in: CharacterSet(charactersIn: "\r\n")), 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 +89,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 +110,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 +126,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 +160,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 +193,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
}
@@ -230,31 +221,35 @@ extension FTPFileProvider {
}
func ftpActive(_ task: FileProviderStreamTask, completionHandler: @escaping (_ dataTask: FileProviderStreamTask?, _ error: Error?) -> Void) {
NotImplemented()
let port = 0
var port: Int32 = 0
var _activeTask: FileProviderStreamTask?
while (_activeTask?.state ?? .suspended) == .suspended {
port = 32000 + Int32(arc4random_uniform(16384))
let service = NetService(domain: "", type: "_tcp.", name: "", port: port)
_activeTask = self.session.fpstreamTask(withNetService: service)
_activeTask?.resume()
}
guard let activeTask = _activeTask else { return }
if self.baseURL?.scheme == "ftps" || self.baseURL?.port == 990 {
task.startSecureConnection()
}
self.execute(command: "PORT \(port)", on: task) { (response, error) in
if let error = error {
activeTask.cancel()
completionHandler(nil, error)
return
}
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
}
let activeTask = self.session.fpstreamTask(withHostName: self.baseURL!.host!, port: 20)
activeTask.resume()
if self.baseURL?.scheme == "ftps" || self.baseURL?.port == 990 {
task.startSecureConnection()
}
completionHandler(activeTask, nil)
}
}
@@ -296,11 +291,11 @@ 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
}
var success = false
let command = useMLST ? "MLSD \(path)" : "LIST \(path)"
self.execute(command: command, on: task, minLength: 70, afterSend: { error in
// starting passive task
@@ -329,20 +324,18 @@ 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
}
let contents = response.components(separatedBy: "\n").flatMap({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })
success = true
completionHandler(contents, nil)
return
}
@@ -353,8 +346,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
}
@@ -363,11 +355,11 @@ extension FTPFileProvider {
return
}
if !response.hasPrefix("25") {
let spaceIndex = response.characters.index(of: "-") ?? response.startIndex
if !success && !(response.hasPrefix("25") || response.hasPrefix("15")) {
let spaceIndex = response.characters.index(of: " ") ?? response.startIndex
let code = Int(response.substring(to: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines)) ?? -1
let description = response.substring(from: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines)
let error = FileProviderFTPError(code: code, path: "", errorDescription: description)
let error = FileProviderFTPError(code: code, path: path, errorDescription: description)
self.dispatch_queue.async {
completionHandler([], error)
@@ -378,8 +370,50 @@ extension FTPFileProvider {
}
}
func ftpRecursiveList(_ task: FileProviderStreamTask, of path: String, useMLST: Bool, completionHandler: @escaping (_ contents: [String], _ error: Error?) -> Void) {
// TODO: Implement recursive listing for search and removing function
func recursiveList(path: String, useMLST: Bool, foundItemsHandler: ((_ contents: [FileObject]) -> Void)? = nil, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
let queue = DispatchQueue(label: "test")
queue.async {
let group = DispatchGroup()
var result = [FileObject]()
var success = true
group.enter()
self.contentsOfDirectory(path: path, completionHandler: { (files, error) in
success = success && (error == nil)
if let error = error {
completionHandler([], error)
group.leave()
return
}
result.append(contentsOf: files)
foundItemsHandler?(files)
let directories: [FileObject] = files.filter { $0.isDirectory }
for dir in directories {
group.enter()
self.recursiveList(path: dir.path, useMLST: useMLST, foundItemsHandler: foundItemsHandler, completionHandler: { (contents, error) in
success = success && (error == nil)
if let error = error {
completionHandler([], error)
group.leave()
return
}
foundItemsHandler?(files)
result.append(contentsOf: contents)
group.leave()
})
}
group.leave()
})
group.wait()
if success {
self.dispatch_queue.async {
completionHandler(result, nil)
}
}
}
}
func ftpRetrieveData(_ task: FileProviderStreamTask, filePath: String, from position: Int64 = 0, length: Int = -1, onTask: ((_ task: FileProviderStreamTask) -> Void)?, onProgress: ((_ bytesReceived: Int64, _ totalReceived: Int64, _ expectedBytes: Int64) -> Void)?, completionHandler: @escaping (_ data: Data?, _ error: Error?) -> Void) {
@@ -402,13 +436,12 @@ 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
}
// Send retreive command
self.execute(command: "TYPE L" + FTPFileProvider.carriage + "REST \(position)" + FTPFileProvider.carriage + "RETR \(filePath)", on: task, minLength: 75, afterSend: { error in
self.execute(command: "TYPE I" + "\r\n" + "REST \(position)" + "\r\n" + "RETR \(filePath)", on: task, minLength: 75, afterSend: { error in
// starting passive task
onTask?(dataTask)
@@ -440,8 +473,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 +495,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,13 +542,12 @@ 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
}
// Send retreive command
self.execute(command: "TYPE I" + FTPFileProvider.carriage + "REST \(position)" + FTPFileProvider.carriage + "RETR \(filePath)", on: task, minLength: 75, afterSend: { error in
self.execute(command: "TYPE I" + "\r\n" + "REST \(position)" + "\r\n" + "RETR \(filePath)", on: task, minLength: 75, afterSend: { error in
// starting passive task
onTask?(dataTask)
@@ -549,7 +579,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 +610,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
}
@@ -601,7 +630,7 @@ extension FTPFileProvider {
}
}
func ftpStore(_ task: FileProviderStreamTask, filePath: String, fromData: Data?, fromFile: URL?, onTask: ((_ task: FileProviderStreamTask) -> Void)?, completionHandler: @escaping (_ error: Error?) -> Void) {
func ftpStore(_ task: FileProviderStreamTask, filePath: String, fromData: Data?, fromFile: URL?, onTask: ((_ task: FileProviderStreamTask) -> Void)?, onProgress: ((_ bytesSent: Int64, _ totalSent: Int64, _ expectedBytes: Int64) -> Void)?, completionHandler: @escaping (_ error: Error?) -> Void) {
self.ftpDataConnect(task) { (dataTask, error) in
if let error = error {
@@ -610,13 +639,12 @@ 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
}
// Send retreive command
self.execute(command: "TYPE L" + FTPFileProvider.carriage + "STOR \(filePath)", on: task, minLength: 75, afterSend: { error in
self.execute(command: "TYPE I" + "\r\n" + "STOR \(filePath)", on: task, minLength: 75, afterSend: { error in
// starting passive task
let timeout = self.session.configuration.timeoutIntervalForRequest
if self.baseURL?.scheme == "ftps" || self.baseURL?.port == 990 {
@@ -626,31 +654,64 @@ extension FTPFileProvider {
DispatchQueue.global().async {
var error: Error?
let chunkSize = 65536
if let data = fromData {
dataTask.write(data, timeout: timeout, completionHandler: { (error) in
completionHandler(error)
})
if let data = fromData, data.count > 0 {
var eof = false
var sent: Int = 0
while !eof {
let group = DispatchGroup()
group.enter()
let endIndex = min(data.count, sent + chunkSize)
eof = endIndex == data.count
let subdata = data.subdata(in: sent..<endIndex)
dataTask.write(subdata, timeout: timeout, completionHandler: { (serror) in
error = serror
sent += subdata.count
group.leave()
})
let waitResult = group.wait(timeout: .now() + timeout)
onProgress?(Int64(subdata.count), Int64(sent), Int64(data.count))
if let error = error {
completionHandler(error)
return
}
if waitResult == .timedOut {
error = self.throwError(fromFile?.relativePath ?? filePath, code: URLError.timedOut)
completionHandler(error)
return
}
}
dataTask.closeRead()
dataTask.closeWrite()
completionHandler(nil)
return
}
guard let file = fromFile, let fileHandle = FileHandle(forReadingAtPath: file.path) else { return }
let size = file.fileSize
fileHandle.seek(toFileOffset: 0)
var eof = false
var sent: Int64 = 0
while !eof {
let group = DispatchGroup()
group.enter()
let data = fileHandle.readData(ofLength: 65536)
eof = data.count < 65536
let data = fileHandle.readData(ofLength: chunkSize)
eof = Int64(fileHandle.offsetInFile) == size
dataTask.write(data, timeout: timeout, completionHandler: { (serror) in
error = serror
sent += Int64(data.count)
group.leave()
})
let waitResult = group.wait(timeout: .now() + timeout)
onProgress?(Int64(data.count), sent, size)
if let error = error {
completionHandler(error)
@@ -658,11 +719,12 @@ extension FTPFileProvider {
}
if waitResult == .timedOut {
error = NSError(domain: URLError.errorDomain, code: URLError.timedOut.rawValue, userInfo: nil)
error = self.throwError(fromFile?.relativePath ?? filePath, code: URLError.timedOut)
completionHandler(error)
return
}
}
dataTask.closeRead()
dataTask.closeWrite()
completionHandler(nil)
return
@@ -674,12 +736,11 @@ 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
}
if !(response.hasPrefix("1") || !response.hasPrefix("2")) {
if !(response.hasPrefix("1") || response.hasPrefix("2")) {
let spaceIndex = response.characters.index(of: "-") ?? response.startIndex
let code = Int(response.substring(to: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines)) ?? -1
let description = response.substring(from: spaceIndex).trimmingCharacters(in: .whitespacesAndNewlines)
@@ -778,7 +839,8 @@ extension FTPFileProvider {
guard components.count > 1 else { return nil }
let nameOrPath = components.removeLast().trimmingCharacters(in: .whitespacesAndNewlines)
let correctedPath: String, name: String
var correctedPath: String
let name: String
if nameOrPath.hasPrefix("/") {
correctedPath = nameOrPath.replacingOccurrences(of: baseURL!.path, with: "", options: .anchored)
name = (nameOrPath as NSString).lastPathComponent
@@ -786,6 +848,9 @@ extension FTPFileProvider {
name = nameOrPath
correctedPath = (path as NSString).appendingPathComponent(nameOrPath)
}
if correctedPath.hasPrefix("/") {
correctedPath.characters.removeFirst()
}
var attributes = [String: String]()
for component in components {
@@ -794,7 +859,7 @@ extension FTPFileProvider {
attributes[keyValue[0].lowercased()] = keyValue.dropFirst().joined(separator: "=")
}
let file = FileObject(url: url(of: path), name: name, path: correctedPath)
let file = FileObject(url: url(of: correctedPath), name: name, path: correctedPath)
let dateFormatter = DateFormatter()
dateFormatter.calendar = Calendar(identifier: .gregorian)
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
@@ -828,7 +893,7 @@ extension FTPFileProvider {
file.size = Int64(attribute) ?? -1
case "media-type":
file.allValues[.mimeType] = attribute
file.allValues[.mimeTypeKey] = attribute
default:
break
+6 -12
View File
@@ -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) }
+33 -18
View File
@@ -132,6 +132,15 @@ public protocol FileProviderBasic: class, NSCoding, NSSecureCoding {
*/
func url(of path: String?) -> URL
/// Returns the relative path of url, wothout percent encoding. Even if url is absolute or
/// retrieved from another provider, it will try to resolve the url against `baseURL` of
/// current provider. It's highly recomended to use this method for displaying purposes.
///
/// - Parameter url: Absolute url to file or directory.
/// - Returns: A `String` contains relative path of url against base url.
func relativePathOf(url: URL) -> String
/// Checks the connection to server or permission on local
func isReachable(completionHandler: @escaping(_ success: Bool) -> Void)
}
@@ -195,6 +204,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 +621,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
@@ -624,26 +634,29 @@ extension FileProviderBasic {
}
}
/// Returns the relative path of url, wothout percent encoding. Even if url is absolute or
/// retrieved from another provider, it will try to resolve the url against `baseURL` of
/// current provider. It's highly recomended to use this method for displaying purposes.
///
/// - Parameter url: Absolute url to file or directory.
/// - Returns: A `String` contains relative path of url against base url.
public func relativePathOf(url: URL) -> String {
// check if url derieved from current base url
let relativePath = url.relativePath
if !relativePath.isEmpty, url.baseURL == self.baseURL {
return relativePath.removingPercentEncoding ?? relativePath
return (relativePath.removingPercentEncoding ?? relativePath).replacingOccurrences(of: "/", with: "", options: .anchored)
}
// resolve url string against baseurl
guard let baseURL = self.baseURL?.standardizedFileURL else { return url.absoluteString }
let standardPath = url.absoluteString.replacingOccurrences(of: "file:///private/var/", with: "file:///var/", options: .anchored)
let standardBase = baseURL.absoluteString.replacingOccurrences(of: "file:///private/var/", with: "file:///var/", options: .anchored)
let standardRelativePath = standardPath.replacingOccurrences(of: standardBase, with: "/")
return standardRelativePath.removingPercentEncoding ?? standardRelativePath
if baseURL?.isFileURL ?? false {
guard let baseURL = self.baseURL?.standardizedFileURL else { return url.absoluteString }
let standardPath = url.absoluteString.replacingOccurrences(of: "file:///private/var/", with: "file:///var/", options: .anchored)
let standardBase = baseURL.absoluteString.replacingOccurrences(of: "file:///private/var/", with: "file:///var/", options: .anchored)
let standardRelativePath = standardPath.replacingOccurrences(of: standardBase, with: "/").replacingOccurrences(of: "/", with: "", options: .anchored)
return standardRelativePath.removingPercentEncoding ?? standardRelativePath
} else {
guard let baseURL = self.baseURL else { return url.absoluteString }
let standardRelativePath = url.absoluteString.replacingOccurrences(of: baseURL.absoluteString, with: "/").replacingOccurrences(of: "/", with: "", options: .anchored)
if URLComponents(string: standardRelativePath)?.host?.isEmpty ?? true {
return standardRelativePath.removingPercentEncoding ?? standardRelativePath
} else {
return relativePath.replacingOccurrences(of: "/", with: "", options: .anchored)
}
}
}
internal func correctPath(_ path: String?) -> String? {
@@ -938,6 +951,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":
+13 -4
View File
@@ -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 {
-6
View File
@@ -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 {
+4 -2
View File
@@ -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])
+20 -41
View File
@@ -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
View File
@@ -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
View File
@@ -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
+142 -39
View File
@@ -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)) {
open 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.