From e30861ad185e85c76efa074e2bccda4bfa8ea5db Mon Sep 17 00:00:00 2001 From: Amir Abbas Mousavian Date: Fri, 3 Feb 2017 18:29:34 +0330 Subject: [PATCH] Added publicLink method to OneDrive - Added more Documentation, specially initializer - Added option to conserve sessions after provider deinit to finish tasks - Dropbox and OneDrive init are not bailable anymore - Updated AEXML --- README.md | 2 +- Sources/AEXML/AEXML.h | 29 ------ Sources/AEXML/Document.swift | 2 +- Sources/AEXML/Element.swift | 4 +- Sources/AEXML/Error.swift | 2 +- Sources/AEXML/Info.plist | 26 ----- Sources/AEXML/Options.swift | 2 +- Sources/AEXML/Parser.swift | 0 Sources/CloudFileProvider.swift | 64 ++++++------ Sources/DropboxFileProvider.swift | 132 +++++++++++++++++------- Sources/ExtendedLocalFileProvider.swift | 22 ++-- Sources/FileObject.swift | 7 +- Sources/FileProvider.swift | 10 +- Sources/LocalFileProvider.swift | 61 +++++++++-- Sources/OneDriveFileProvide.swift | 101 +++++++++++++----- Sources/SMBFileProvider.swift | 5 +- Sources/WebDAVFileProvider.swift | 18 +++- 17 files changed, 305 insertions(+), 182 deletions(-) delete mode 100755 Sources/AEXML/AEXML.h mode change 100755 => 100644 Sources/AEXML/Document.swift mode change 100755 => 100644 Sources/AEXML/Element.swift mode change 100755 => 100644 Sources/AEXML/Error.swift delete mode 100755 Sources/AEXML/Info.plist mode change 100755 => 100644 Sources/AEXML/Options.swift mode change 100755 => 100644 Sources/AEXML/Parser.swift diff --git a/README.md b/README.md index e59a548..d150377 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ let webdavProvider = WebDAVFileProvider(baseURL: URL(string: "http://www.example * In case you want to connect non-secure servers for WebDAV (http) in iOS 9+ / macOS 10.11+ you should disable App Transport Security (ATS) according to [this guide.](https://gist.github.com/mlynch/284699d676fe9ed0abfa) -* For Dropbox & OneDrive, user is clientID and password is Token which both must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token. The latter is easier to use and prefered. +* For Dropbox & OneDrive, user is clientID and password is Token which both must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token. The latter is easier to use and prefered. Also you can use [auth0/Lock](https://github.com/auth0/Lock.iOS-OSX) which provides graphical user interface. For interaction with UI, set delegate variable of `FileProvider` object diff --git a/Sources/AEXML/AEXML.h b/Sources/AEXML/AEXML.h deleted file mode 100755 index f59d631..0000000 --- a/Sources/AEXML/AEXML.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// AEXML.h -// -// Copyright (c) 2014 Marko Tadić http://tadija.net -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -#import - -FOUNDATION_EXPORT double AEXMLVersionNumber; -FOUNDATION_EXPORT const unsigned char AEXMLVersionString[]; - diff --git a/Sources/AEXML/Document.swift b/Sources/AEXML/Document.swift old mode 100755 new mode 100644 index be769aa..480eaea --- a/Sources/AEXML/Document.swift +++ b/Sources/AEXML/Document.swift @@ -29,7 +29,7 @@ import Foundation XML Parsing is also done with this object. */ -open class AEXMLDocument: AEXMLElement { +internal class AEXMLDocument: AEXMLElement { // MARK: - Properties diff --git a/Sources/AEXML/Element.swift b/Sources/AEXML/Element.swift old mode 100755 new mode 100644 index d45f647..f5f61a2 --- a/Sources/AEXML/Element.swift +++ b/Sources/AEXML/Element.swift @@ -30,7 +30,7 @@ import Foundation You can access its structure by using subscript like this: `element["foo"]["bar"]` which would return `` element from `` XML as an `AEXMLElement` object. */ -open class AEXMLElement { +internal class AEXMLElement { // MARK: - Properties @@ -86,7 +86,7 @@ open class AEXMLElement { /// The first element with given name **(Empty element with error if not exists)**. open subscript(key: String) -> AEXMLElement { guard let - first = children.filter({ $0.name == key }).first + first = children.first(where: { $0.name == key }) else { let errorElement = AEXMLElement(name: key) errorElement.error = AEXMLError.elementNotFound diff --git a/Sources/AEXML/Error.swift b/Sources/AEXML/Error.swift old mode 100755 new mode 100644 index 1154407..06e6787 --- a/Sources/AEXML/Error.swift +++ b/Sources/AEXML/Error.swift @@ -25,7 +25,7 @@ import Foundation /// A type representing error value that can be thrown or inside `error` property of `AEXMLElement`. -public enum AEXMLError: Error { +internal enum AEXMLError: Error { /// This will be inside `error` property of `AEXMLElement` when subscript is used for not-existing element. case elementNotFound diff --git a/Sources/AEXML/Info.plist b/Sources/AEXML/Info.plist deleted file mode 100755 index e0f4bf7..0000000 --- a/Sources/AEXML/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 3.0.0 - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/Sources/AEXML/Options.swift b/Sources/AEXML/Options.swift old mode 100755 new mode 100644 index 48ee83b..6e3caf9 --- a/Sources/AEXML/Options.swift +++ b/Sources/AEXML/Options.swift @@ -9,7 +9,7 @@ import Foundation /// Options used in `AEXMLDocument` -public struct AEXMLOptions { +internal struct AEXMLOptions { /// Values used in XML Document header public struct DocumentHeader { diff --git a/Sources/AEXML/Parser.swift b/Sources/AEXML/Parser.swift old mode 100755 new mode 100644 diff --git a/Sources/CloudFileProvider.swift b/Sources/CloudFileProvider.swift index 683deb8..42965a6 100644 --- a/Sources/CloudFileProvider.swift +++ b/Sources/CloudFileProvider.swift @@ -9,10 +9,7 @@ import Foundation open class CloudFileProvider: LocalFileProvider { - - public var type: String { - return "iCloudDrive" - } + open override class var type: String { return "iCloudDrive" } /// Actually is readonly, value is true override open var isCoorinating: Bool { @@ -24,8 +21,19 @@ open class CloudFileProvider: LocalFileProvider { } } - open var containerId: String? + /// The fully-qualified container identifier for an iCloud container directory. + open fileprivate(set) var containerId: String? + /** + Initializes the provider for the iCloud container associated with the specified identifier and + establishes access to that container. + + - Important: Do not call this method from your app’s main thread. Because this method might take a nontrivial amount of time to set up iCloud and return the requested URL, you should always call it from a secondary thread. + + - Parameter containerId: The fully-qualified container identifier for an iCloud container directory. The string you specify must not contain wildcards and must be of the form `.`, where `` is your development team ID and `` is the bundle identifier of the container you want to access.\ + The container identifiers for your app must be declared in the `com.apple.developer.ubiquity-container-identifiers` array of the `.entitlements` property list file in your Xcode project.\ + If you specify nil for this parameter, this method uses the first container listed in the `com.apple.developer.ubiquity-container-identifiers` entitlement array. + */ public init? (containerId: String?) { assert(!Thread.isMainThread, "LocalFileProvider.init(containerId:) is not recommended to be executed on Main Thread.") guard FileManager.default.ubiquityIdentityToken == nil else { @@ -39,9 +47,9 @@ open class CloudFileProvider: LocalFileProvider { super.init(baseURL: baseURL) self.isCoorinating = true - dispatch_queue = DispatchQueue(label: "FileProvider.\(self.type)", attributes: DispatchQueue.Attributes.concurrent) + dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: .concurrent) operation_queue = OperationQueue() - operation_queue.name = "FileProvider.\(self.type).Operation" + operation_queue.name = "FileProvider.\(type(of: self).type).Operation" fileManager.url(forUbiquityContainerIdentifier: containerId) opFileManager.url(forUbiquityContainerIdentifier: containerId) @@ -89,7 +97,7 @@ open class CloudFileProvider: LocalFileProvider { } } - /// iCloud Storage size and free space is unavailable, it returns local space + /// - Important: iCloud Storage size and free space is unavailable, it returns local space open override func storageProperties(completionHandler: (@escaping (_ total: Int64, _ used: Int64) -> Void)) { super.storageProperties(completionHandler: completionHandler) } @@ -128,32 +136,32 @@ open class CloudFileProvider: LocalFileProvider { @discardableResult open override func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? { - let r = super.create(folder: folderName, at: atPath, completionHandler: completionHandler) - return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL) + guard let r = super.create(folder: folderName, at: atPath, completionHandler: completionHandler) else { return nil } + return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL) } @discardableResult open override func create(file fileName: String, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? { - let r = super.create(file: fileName, at: atPath, contents: data, completionHandler: completionHandler) - return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL) + guard let r = super.create(file: fileName, at: atPath, contents: data, completionHandler: completionHandler) else { return nil } + return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL) } @discardableResult open override func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? { - let r = super.moveItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler) - return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL) + guard let r = super.moveItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler) else { return nil } + return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL) } @discardableResult open override func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? { - let r = super.copyItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler) - return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL) + guard let r = super.copyItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler) else { return nil } + return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL) } @discardableResult open override func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? { - let r = super.removeItem(path: path, completionHandler: completionHandler) - return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL) + guard let r = super.removeItem(path: path, completionHandler: completionHandler) else { return nil } + return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL) } @discardableResult @@ -204,26 +212,26 @@ open class CloudFileProvider: LocalFileProvider { return nil } - let r = super.copyItem(path: path, toLocalURL: toLocalURL, completionHandler: completionHandler) - return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL) + guard let r = super.copyItem(path: path, toLocalURL: toLocalURL, completionHandler: completionHandler) else { return nil } + return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL) } @discardableResult open override func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? { - let r = super.contents(path: path, completionHandler: completionHandler) - return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL) + guard let r = super.contents(path: path, completionHandler: completionHandler) else { return nil } + return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL) } @discardableResult open override func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? { - let r = super.contents(path: path, offset: offset, length: length, completionHandler: completionHandler) - return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL) + guard let r = super.contents(path: path, offset: offset, length: length, completionHandler: completionHandler) else { return nil } + return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL) } @discardableResult open override func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? { - let r = super.writeContents(path: path, contents: data, atomically: atomically, overwrite: overwrite, completionHandler: completionHandler) - return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL) + guard let r = super.writeContents(path: path, contents: data, atomically: atomically, overwrite: overwrite, completionHandler: completionHandler) else { return nil } + return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL) } open override func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) { @@ -336,7 +344,6 @@ open class CloudFileProvider: LocalFileProvider { return monitors[url(of: path)] != nil } - /// may return nil open override func copy(with zone: NSZone? = nil) -> Any { let copy = CloudFileProvider(containerId: self.containerId) copy?.currentPath = self.currentPath @@ -413,7 +420,6 @@ open class CloudOperationHandle: OperationHandle { return dest.hasPrefix("file://") ? URL(fileURLWithPath: dest) : baseURL.appendingPathComponent(dest) } - /// Caution: may put pressure on CPU, may have latency open var bytesSoFar: Int64 { assert(!Thread.isMainThread, "Don't run \(#function) method on main thread") @@ -431,7 +437,6 @@ open class CloudOperationHandle: OperationHandle { return 0 } - /// Caution: may put pressure on CPU, may have latency open var totalBytes: Int64 { assert(!Thread.isMainThread, "Don't run \(#function) method on main thread") guard let url = destURL ?? sourceURL, let item = CloudOperationHandle.getMetadataItem(url: url) else { return -1 } @@ -447,7 +452,6 @@ open class CloudOperationHandle: OperationHandle { /// Not usable in local provider open func cancel() -> Bool { - return false } diff --git a/Sources/DropboxFileProvider.swift b/Sources/DropboxFileProvider.swift index a7c8590..5c1331e 100644 --- a/Sources/DropboxFileProvider.swift +++ b/Sources/DropboxFileProvider.swift @@ -10,16 +10,15 @@ import Foundation import CoreGraphics -// Because this class uses NSURLSession, it's necessary to disable App Transport Security -// in case of using this class with unencrypted HTTP connection. - open class DropboxFileProvider: FileProviderBasicRemote { - open static let type: String = "DropBox" + open class var type: String { return "DropBox" } open let isPathRelative: Bool open let baseURL: URL? open var currentPath: String + /// Dropbox RPC API URL, which is equal with [https://api.dropboxapi.com/2/](https://api.dropboxapi.com/2/) open let apiURL: URL + /// Dropbox contents download/upload API URL, which is equal with [https://content.dropboxapi.com/2/](https://content.dropboxapi.com/2/) open let contentURL: URL open var dispatch_queue: DispatchQueue @@ -48,7 +47,17 @@ open class DropboxFileProvider: FileProviderBasicRemote { return _session! } - public init? (credential: URLCredential?, cache: URLCache? = nil) { + /** + Initializer for Dropbox provider with given client ID and Token. + These parameters must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). + + There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token. + The latter is easier to use and prefered. Also you can use [auth0/Lock](https://github.com/auth0/Lock.iOS-OSX) which provides graphical user interface. + + - Parameter credential: a `URLCredential` object with Client ID set as `user` and Token set as `password`. + - Parameter cache: A URLCache to cache downloaded files and contents. If set to nil, URLCache.shared object will be used. + */ + public init(credential: URLCredential?, cache: URLCache? = nil) { self.baseURL = nil self.isPathRelative = true self.currentPath = "" @@ -60,14 +69,18 @@ open class DropboxFileProvider: FileProviderBasicRemote { self.apiURL = URL(string: "https://api.dropboxapi.com/2/")! self.contentURL = URL(string: "https://content.dropboxapi.com/2/")! - dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: DispatchQueue.Attributes.concurrent) + dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: .concurrent) operation_queue = OperationQueue() operation_queue.name = "FileProvider.\(type(of: self).type).Operation" } deinit { - _session?.invalidateAndCancel() + if fileProviderCancelTasksOnInvalidating { + _session?.invalidateAndCancel() + } else { + _session?.finishTasksAndInvalidate() + } } open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) { @@ -85,16 +98,16 @@ open class DropboxFileProvider: FileProviderBasicRemote { let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString] request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8) let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var dbError: FileProviderDropboxError? + var serverError: FileProviderDropboxError? var fileObject: DropboxFileObject? if let response = response as? HTTPURLResponse { let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) - dbError = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil + serverError = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr), let file = DropboxFileObject(json: json) { fileObject = file } } - completionHandler(fileObject, dbError ?? error) + completionHandler(fileObject, serverError ?? error) }) task.resume() } @@ -176,12 +189,12 @@ extension DropboxFileProvider: FileProviderOperations { } request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8) let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var dbError: FileProviderDropboxError? + var serverError: FileProviderDropboxError? if let response = response as? HTTPURLResponse, response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) { - dbError = FileProviderDropboxError(code: code, path: sourcePath, errorDescription: String(data: data ?? Data(), encoding: .utf8)) + serverError = FileProviderDropboxError(code: code, path: sourcePath, errorDescription: String(data: data ?? Data(), encoding: .utf8)) } - completionHandler?(dbError ?? error) - self.delegateNotify(operation, error: dbError ?? error) + completionHandler?(serverError ?? error) + self.delegateNotify(operation, error: serverError ?? error) }) task.taskDescription = operation.json task.resume() @@ -212,8 +225,8 @@ extension DropboxFileProvider: FileProviderOperations { guard let cacheURL = cacheURL, let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode < 300 else { let code = FileProviderHTTPErrorCode(rawValue: (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 dbError : FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil - completionHandler?(dbError ?? error) + let serverError : FileProviderDropboxError? = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil + completionHandler?(serverError ?? error) return } do { @@ -248,12 +261,12 @@ extension DropboxFileProvider: FileProviderReadWrite { let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString] request.setValue(dictionaryToJSON(requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg") let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var dbError: FileProviderDropboxError? + var serverError: FileProviderDropboxError? if let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: httpResponse.statusCode) { - dbError = FileProviderDropboxError(code: code, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) + serverError = FileProviderDropboxError(code: code, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) } - let filedata = dbError ?? error == nil ? data : nil - completionHandler(filedata, dbError ?? error) + let filedata = serverError ?? error == nil ? data : nil + completionHandler(filedata, serverError ?? error) }) task.taskDescription = opType.json task.resume() @@ -296,14 +309,36 @@ extension DropboxFileProvider: FileProviderReadWrite { } extension DropboxFileProvider { - @available(*, deprecated, message: "Use DropboxFileProvider.temporaryLink(to:, completionHandler: (URL?, DropboxFileObject?, Date?, Error?)) function instead.") + /// *DEPRECATED:* Use `publicLink(to:, completionHandler: (URL?, DropboxFileObject?, Date?, Error?))` function instead. + @available(*, deprecated, 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.temporaryLink(to: path) { (url, file, _, error) in + self.publicLink(to: path) { (url, file, _, error) in completionHandler(url, file, error) } } + /// *DEPRECATED:* Use `publicLink(to:, completionHandler: (URL?, DropboxFileObject?, Date?, Error?))` function instead. + @available(*, deprecated, 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. + + - Important: URL will be available for a limitied time (4 hours according to Dropbox documentation). + + - Parameters: + - to: path of file, including file/directory name. + - completionHandler: a block with result of directory entries or error. + `link`: a url returned by Dropbox to share. + `attribute`: a `FileObject` containing the attributes of the item. + `expiration`: a `Date` object, determines when the public url will expires. + `error`: Error returned by Dropbox. + */ + open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: DropboxFileObject?, _ expiration: Date?, _ error: Error?) -> Void)) { let url = URL(string: "files/get_temporary_link", relativeTo: apiURL)! var request = URLRequest(url: url) request.httpMethod = "POST" @@ -312,12 +347,12 @@ extension DropboxFileProvider { let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString] request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8) let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var dbError: FileProviderDropboxError? + var serverError: FileProviderDropboxError? var link: URL? var fileObject: DropboxFileObject? if let response = response as? HTTPURLResponse { let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) - dbError = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil + serverError = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr) { if let linkStr = json["link"] as? String { link = URL(string: linkStr) @@ -329,12 +364,27 @@ extension DropboxFileProvider { } let expiration: Date? = link != nil ? Date(timeIntervalSinceNow: 4 * 60 * 60) : nil - completionHandler(link, fileObject, expiration, dbError ?? error) + completionHandler(link, fileObject, expiration, serverError ?? error) }) task.resume() } + /** + Downloads a file from remote url to designated path asynchronously. + + - Parameters: + - remoteURL: a valid remote url to file. + - to: Destination path of file, including file/directory name. + - completionHandler: a block with result of directory entries or error. + `jobId`: Job ID returned by Dropbox to monitor the copy/download progress. + `attribute`: A `FileObject` containing the attributes of the item. + `error`: Error returned by Dropbox. + */ open func copyItem(remoteURL: URL, to toPath: String, completionHandler: @escaping ((_ jobId: String?, _ attribute: DropboxFileObject?, _ error: Error?) -> Void)) { + if remoteURL.isFileURL { + completionHandler(nil, nil, self.throwError(remoteURL.path, code: URLError.badURL)) + return + } let url = URL(string: "files/save_url", relativeTo: apiURL)! var request = URLRequest(url: url) request.httpMethod = "POST" @@ -343,12 +393,12 @@ extension DropboxFileProvider { let requestDictionary: [String: AnyObject] = ["path": correctPath(toPath)! as NSString, "url" : remoteURL.absoluteString as NSString] request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8) let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var dbError: FileProviderDropboxError? + var serverError: FileProviderDropboxError? var jobId: String? var fileObject: DropboxFileObject? if let response = response as? HTTPURLResponse { let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) - dbError = code != nil ? FileProviderDropboxError(code: code!, path: toPath, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil + serverError = code != nil ? FileProviderDropboxError(code: code!, path: toPath, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr) { jobId = json["async_job_id"] as? String if let attribDic = json["metadata"] as? [String: AnyObject] { @@ -356,13 +406,21 @@ extension DropboxFileProvider { } } } - completionHandler(jobId, fileObject, dbError ?? error) + completionHandler(jobId, fileObject, serverError ?? error) }) task.resume() } - open func copyItem(reference: String, to toPath: String, completionHandler:SimpleCompletionHandler) { - let url = URL(string: "files/save_url", relativeTo: apiURL)! + /** + Copys a file from another user Dropbox storage to designated path asynchronously. + + - Parameters: + - reference: a valid reference string from another user via `copy_reference/get` REST method. + - to: Destination path of file, including file/directory name. + - completionHandler: If an error parameter was provided, a presentable `Error` will be returned. + */ + open func copyItem(reference: String, to toPath: String, completionHandler: SimpleCompletionHandler) { + let url = URL(string: "files/copy_reference/save", relativeTo: apiURL)! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization") @@ -370,12 +428,12 @@ extension DropboxFileProvider { let requestDictionary: [String: AnyObject] = ["path": correctPath(toPath)! as NSString, "copy_reference" : reference as NSString] request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8) let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var dbError: FileProviderDropboxError? + var serverError: FileProviderDropboxError? if let response = response as? HTTPURLResponse { let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) - dbError = code != nil ? FileProviderDropboxError(code: code!, path: toPath, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil + serverError = code != nil ? FileProviderDropboxError(code: code!, path: toPath, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil } - completionHandler?(dbError ?? error) + completionHandler?(serverError ?? error) }) task.resume() } @@ -464,17 +522,17 @@ extension DropboxFileProvider: ExtendedFileProvider { let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString, "include_media_info": NSNumber(value: true)] request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8) let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var dbError: FileProviderDropboxError? + var serverError: FileProviderDropboxError? var dic = [String: Any]() var keys = [String]() if let response = response as? HTTPURLResponse { let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) - dbError = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil + serverError = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr), let properties = json["media_info"] as? [String: Any] { (dic, keys) = self.mapMediaInfo(properties) } } - completionHandler(dic, keys, dbError ?? error) + completionHandler(dic, keys, serverError ?? error) }) task.resume() } @@ -482,7 +540,7 @@ extension DropboxFileProvider: ExtendedFileProvider { extension DropboxFileProvider: FileProvider { open func copy(with zone: NSZone? = nil) -> Any { - let copy = DropboxFileProvider(credential: self.credential, cache: self.cache)! + let copy = DropboxFileProvider(credential: self.credential, cache: self.cache) copy.currentPath = self.currentPath copy.delegate = self.delegate copy.fileOperationDelegate = self.fileOperationDelegate diff --git a/Sources/ExtendedLocalFileProvider.swift b/Sources/ExtendedLocalFileProvider.swift index 678116e..398cc8a 100644 --- a/Sources/ExtendedLocalFileProvider.swift +++ b/Sources/ExtendedLocalFileProvider.swift @@ -128,20 +128,20 @@ extension LocalFileProvider: ExtendedFileProvider { } public struct LocalFileInformationGenerator { - static public var imageThumbnailExtensions: [String] = ["jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff", "ico"] - static public var audioThumbnailExtensions: [String] = ["mp3", "aac", "m4a"] - static public var videoThumbnailExtensions: [String] = ["mov", "mp4", "m4v", "mpg", "mpeg"] - static public var pdfThumbnailExtensions: [String] = ["pdf"] + static public var imageThumbnailExtensions: [String] = ["jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff", "ico"] + static public var audioThumbnailExtensions: [String] = ["mp3", "aac", "m4a"] + static public var videoThumbnailExtensions: [String] = ["mov", "mp4", "m4v", "mpg", "mpeg"] + static public var pdfThumbnailExtensions: [String] = ["pdf"] static public var officeThumbnailExtensions: [String] = [] static public var customThumbnailExtensions: [String] = [] - static public var imagePropertiesExtensions: [String] = ["jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff"] - static public var audioPropertiesExtensions: [String] = ["mp3", "aac", "m4a", "caf"] - static public var videoPropertiesExtensions: [String] = ["mp4", "mpg", "3gp", "mov", "avi"] - static public var pdfPropertiesExtensions: [String] = ["pdf"] + static public var imagePropertiesExtensions: [String] = ["jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff"] + static public var audioPropertiesExtensions: [String] = ["mp3", "aac", "m4a", "caf"] + static public var videoPropertiesExtensions: [String] = ["mp4", "mpg", "3gp", "mov", "avi"] + static public var pdfPropertiesExtensions: [String] = ["pdf"] static public var archivePropertiesExtensions: [String] = [] - static public var officePropertiesExtensions: [String] = [] - static public var customPropertiesExtensions: [String] = [] + static public var officePropertiesExtensions: [String] = [] + static public var customPropertiesExtensions: [String] = [] static public var imageThumbnail: (_ fileURL: URL) -> ImageClass? = { fileURL in return ImageClass(contentsOfFile: fileURL.path) @@ -439,6 +439,6 @@ public struct LocalFileInformationGenerator { static public var customProperties: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))? = nil } -func ~=(array: [T], value: T) -> Bool { +fileprivate func ~=(array: [T], value: T) -> Bool { return array.contains(value) } diff --git a/Sources/FileObject.swift b/Sources/FileObject.swift index b3ba257..aca297b 100644 --- a/Sources/FileObject.swift +++ b/Sources/FileObject.swift @@ -25,11 +25,13 @@ open class FileObject { } /// url to access the resource, not supported by Dropbox provider - @available(*, deprecated, message: "Use FileObject.url.absoluteURL instead.") + @available(*, deprecated, 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["NSURLFileURLKey"] as? URL @@ -99,7 +101,8 @@ open class FileObject { } } - @available(*, deprecated, message: "Use FileObject.type property instead.") + /// **DEPRECATED:** Use `type` property instead. + @available(*, deprecated, message: "Use type property instead.") open var fileType: URLFileResourceType? { return self.type } diff --git a/Sources/FileProvider.swift b/Sources/FileProvider.swift index a0dc4e7..cd11f42 100644 --- a/Sources/FileProvider.swift +++ b/Sources/FileProvider.swift @@ -111,8 +111,10 @@ public extension FileProviderBasic { } } +public var fileProviderCancelTasksOnInvalidating = true + public protocol FileProviderBasicRemote: FileProviderBasic { - /// + /// Underlying URLSession instance used for HTTP/S requests var session: URLSession { get } /// A `URLCache` to cache downloaded files and contents. @@ -177,6 +179,7 @@ internal extension FileProviderBasicRemote { } public protocol FileProviderOperations: FileProviderBasic { + /// Delgate for managing operations involving the copying, moving, linking, or removal of files and directories. When you use an FileManager object to initiate a copy, move, link, or remove operation, the file provider asks its delegate whether the operation should begin at all and whether it should proceed when an error occurs. var fileOperationDelegate : FileOperationDelegate? { get set } /** @@ -507,7 +510,8 @@ extension FileProviderBasic { return path.trimmingCharacters(in: pathTrimSet).addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)! } - @available(*, deprecated, message: "Use FileProvider.url(of:).absoluteURL instead.") + /// **DEPRECATED:** Use `url(of:).absoluteURL` instead. + @available(*, deprecated, message: "Use url(of:).absoluteURL instead.") public func absoluteURL(_ path: String? = nil) -> URL { return url(of: path).absoluteURL } @@ -535,7 +539,7 @@ extension FileProviderBasic { return url.relativePath.removingPercentEncoding! } - guard let baseURL = self.baseURL else { return url.absoluteString } + guard let baseURL = self.baseURL?.standardizedFileURL else { return url.absoluteString } return url.standardizedFileURL.absoluteString.replacingOccurrences(of: baseURL.absoluteString, with: "/").removingPercentEncoding! } diff --git a/Sources/LocalFileProvider.swift b/Sources/LocalFileProvider.swift index 215aec1..4364629 100644 --- a/Sources/LocalFileProvider.swift +++ b/Sources/LocalFileProvider.swift @@ -9,7 +9,7 @@ import Foundation open class LocalFileProvider: FileProvider, FileProviderMonitor { - open static let type: String = "Local" + open class var type: String { return "Local" } open var isPathRelative: Bool open fileprivate(set) var baseURL: URL? open var currentPath: String @@ -22,17 +22,41 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor { open private(set) var opFileManager = FileManager() fileprivate var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil - /// Forces file operations to use NSFileCoordinating, should be set true if: - /// - Files are on ubiquity(iCloud) container - /// - Multiple processes are accessing same file, recommended always in macOS and when using app extensions in iOS/tvOS (shared container) + /** + Forces file operations to use `NSFileCoordinating`, should be set `true` if: + - Files are on ubiquity (iCloud) container. + - Multiple processes are accessing same file, recommended when accessing a shared/public + user document in macOS and when using app extensions in iOS/tvOS (shared container). + + By default it's `true` when using iCloud or shared container (App Group) initializers, + otherwise it's `false` to accelerate operations. + */ open var isCoorinating: Bool - /// default values are `directory: .documentDirectory, domainMask: .userDomainMask` + /** + Initializes provider for the specified common directory in the requested domains. + default values are `directory: .documentDirectory, domainMask: .userDomainMask`. + + - Parameters: + - directory: The search path directory. The supported values are described in `FileManager.SearchPathDirectory`. + - domainMask: The file system domain to search. The value for this parameter is one or more of the constants described in `FileManager.SearchPathDomainMask`. + */ public convenience init (directory: FileManager.SearchPathDirectory = .documentDirectory, domainMask: FileManager.SearchPathDomainMask = .userDomainMask) { self.init(baseURL: FileManager.default.urls(for: directory, in: domainMask).first!) } - public convenience init ? (sharedContainerId: String, directory: FileManager.SearchPathDirectory = .userDirectory) { + /** + Failable initializer for the specified shared container directory, allows data and files to be shared among app + and extensions regarding sandbox requirements. Container ID is same with app group specified in project `Capabilities` + tab under `App Group` item. If you don't have enough privilage to access container or the app group imply does't exist, + initialing will fail. + default values are `directory: .documentDirectory`. + + - Parameters: + - sharedContainerId: Same with `App Group` identifier defined in project settings. + - directory: The search path directory. The supported values are described in `FileManager.SearchPathDirectory`. + */ + public convenience init? (sharedContainerId: String, directory: FileManager.SearchPathDirectory = .documentDirectory) { guard let baseURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: sharedContainerId) else { return nil } @@ -57,14 +81,20 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor { try? fileManager.createDirectory(at: finalBaseURL, withIntermediateDirectories: true) } + /// Initializes provider for the specified local URL. + /// + /// - Parameter baseURL: Local URL location for base directory. public init (baseURL: URL) { + guard baseURL.isFileURL else { + fatalError("Cannot initialize a Local provider from remote URL.") + } self.baseURL = baseURL self.isPathRelative = true self.currentPath = "" self.credential = nil self.isCoorinating = false - dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: DispatchQueue.Attributes.concurrent) + dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: .concurrent) operation_queue = OperationQueue() operation_queue.name = "FileProvider.\(type(of: self).type).Operation" @@ -73,6 +103,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor { } + /// **DEPRECATED:** No longer is in use and overriding this method has no effect anymore. @available(*, deprecated, message: "Overriding this method has no effect anymore.") open class func defaultBaseURL() -> URL { return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! @@ -549,6 +580,17 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor { } public extension LocalFileProvider { + /** + Creates a symbolic link at the specified path that points to an item at the given path. + This method does not traverse symbolic links contained in destURL, making it possible + to create symbolic links to locations that do not yet exist. + Also, if the final path component in url is a symbolic link, that link is not followed. + + - Parameters: + - path: The file path at which to create the new symbolic link. The last component of the path issued as the name of the link. + - destPath: The path that contains the item to be pointed to by the link. In other words, this is the destination of the link. + - completionHandler: If an error parameter was provided, a presentable `Error` will be returned. + */ public func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) { operation_queue.addOperation { do { @@ -566,6 +608,11 @@ public extension LocalFileProvider { } } + /// Returns the path of the item pointed to by a symbolic link. + /// + /// - Parameters: + /// - path: The path of a file or directory. + /// - completionHandler: Returns destination url of given symbolic link, or an `Error` object if it fails. public func destination(ofSymbolicLink path: String, completionHandler: @escaping (_ url: URL?, _ error: Error?) -> Void) { dispatch_queue.async { do { diff --git a/Sources/OneDriveFileProvide.swift b/Sources/OneDriveFileProvide.swift index 1d24f7d..f381ff2 100644 --- a/Sources/OneDriveFileProvide.swift +++ b/Sources/OneDriveFileProvide.swift @@ -10,15 +10,15 @@ import Foundation import CoreGraphics -// Because this class uses NSURLSession, it's necessary to disable App Transport Security -// in case of using this class with unencrypted HTTP connection. - open class OneDriveFileProvider: FileProviderBasicRemote { - open static let type: String = "OneDrive" + open class var type: String { return "OneDrive" } open let isPathRelative: Bool open let baseURL: URL? + /// OneDrive server url, equals with unwrapped `baseURL` open var serverURL: URL { return baseURL! } + /// Drive name for user, default is `root`. Changing its value will effect on new operations. open var drive: String + /// Generated storage url from server url and drive name open var driveURL: URL { return URL(string: "/drive/\(drive):/", relativeTo: baseURL)! } @@ -52,7 +52,21 @@ open class OneDriveFileProvider: FileProviderBasicRemote { return _session! } - public init? (credential: URLCredential?, serverURL: URL? = nil, drive: String = "root", cache: URLCache? = nil) { + /** + Initializer for Onedrive provider with given client ID and Token. + These parameters must be retrieved via [Authentication for the OneDrive API](https://dev.onedrive.com/auth/readme.htm). + + There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token. + The latter is easier to use and prefered. Also you can use [auth0/Lock](https://github.com/auth0/Lock.iOS-OSX) which provides graphical user interface. + + - Parameters: + - credential: a `URLCredential` object with Client ID set as `user` and Token set as `password`. + - serverURL: server url, Set it if you are trying to connect OneDrive Business server, otherwise leave it + `nil` to connect to OneDrive Personal uses. + - drive: drive name for user on server, default value is `root`. + - cache: A URLCache to cache downloaded files and contents. If set to nil, URLCache.shared object will be used. + */ + public init(credential: URLCredential?, serverURL: URL? = nil, drive: String = "root", cache: URLCache? = nil) { self.baseURL = (serverURL ?? URL(string: "https://api.onedrive.com/")!).appendingPathComponent("") self.drive = drive self.isPathRelative = true @@ -61,13 +75,17 @@ open class OneDriveFileProvider: FileProviderBasicRemote { self.validatingCache = true self.cache = cache self.credential = credential - dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: DispatchQueue.Attributes.concurrent) + dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: .concurrent) operation_queue = OperationQueue() operation_queue.name = "FileProvider.\(type(of: self).type).Operation" } deinit { - _session?.invalidateAndCancel() + if fileProviderCancelTasksOnInvalidating { + _session?.invalidateAndCancel() + } else { + _session?.finishTasksAndInvalidate() + } } open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) { @@ -82,16 +100,16 @@ open class OneDriveFileProvider: FileProviderBasicRemote { request.httpMethod = "GET" request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization") let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var dbError: FileProviderOneDriveError? + var serverError: FileProviderOneDriveError? var fileObject: OneDriveFileObject? if let response = response as? HTTPURLResponse { let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) - dbError = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil + serverError = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr), let file = OneDriveFileObject(baseURL: self.baseURL, drive: self.drive, json: json) { fileObject = file } } - completionHandler(fileObject, dbError ?? error) + completionHandler(fileObject, serverError ?? error) }) task.resume() } @@ -170,12 +188,12 @@ extension OneDriveFileProvider: FileProviderOperations { request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8) } let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var dbError: FileProviderOneDriveError? + var serverError: FileProviderOneDriveError? if let response = response as? HTTPURLResponse, response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) { - dbError = FileProviderOneDriveError(code: code, path: sourcePath, errorDescription: String(data: data ?? Data(), encoding: .utf8)) + serverError = FileProviderOneDriveError(code: code, path: sourcePath, errorDescription: String(data: data ?? Data(), encoding: .utf8)) } - completionHandler?(dbError ?? error) - self.delegateNotify(operation, error: dbError ?? error) + completionHandler?(serverError ?? error) + self.delegateNotify(operation, error: serverError ?? error) }) task.taskDescription = operation.json task.resume() @@ -203,8 +221,8 @@ extension OneDriveFileProvider: FileProviderOperations { guard let cacheURL = cacheURL, let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode < 300 else { let code = FileProviderHTTPErrorCode(rawValue: (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 dbError : FileProviderOneDriveError? = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil - completionHandler?(dbError ?? error) + let serverError : FileProviderOneDriveError? = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil + completionHandler?(serverError ?? error) return } do { @@ -237,12 +255,12 @@ extension OneDriveFileProvider: FileProviderReadWrite { request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range") } let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var dbError: FileProviderOneDriveError? + var serverError: FileProviderOneDriveError? if let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: httpResponse.statusCode) { - dbError = FileProviderOneDriveError(code: code, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) + serverError = FileProviderOneDriveError(code: code, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) } - let filedata = dbError ?? error == nil ? data : nil - completionHandler(filedata, dbError ?? error) + let filedata = serverError ?? error == nil ? data : nil + completionHandler(filedata, serverError ?? error) }) task.taskDescription = opType.json task.resume() @@ -281,7 +299,40 @@ extension OneDriveFileProvider: FileProviderReadWrite { NotImplemented() } - // TODO: Implement /copy_reference, /get_account & /get_current_account + /** + Genrates a public url to a file to be shared with other users and can be downloaded without authentication. + + - Parameters: + - to: path of file, including file/directory name. + - completionHandler: a block with result of directory entries or error. + `link`: a url returned by OneDrive to share. + `attribute`: `nil` for OneDrive. + `expiration`: `nil` for OneDrive, as it doesn't expires. + `error`: Error returned by OneDrive. + */ + open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: OneDriveFileObject?, _ expiration: Date?, _ error: Error?) -> Void)) { + let url = URL(string: escaped(path: path) + ":/action.createLink", relativeTo: driveURL)! + var request = URLRequest(url: url) + request.httpMethod = "POST" + let requestDictionary: [String: AnyObject] = ["type": "view" as NSString] + request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8) + let task = session.dataTask(with: request, completionHandler: { (data, response, error) in + var serverError: FileProviderOneDriveError? + var link: URL? + if let response = response as? HTTPURLResponse { + let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) + serverError = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil + if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr) { + if let linkDic = json["link"] as? NSDictionary, let linkStr = linkDic["webUrl"] as? String { + link = URL(string: linkStr) + } + } + } + + completionHandler(link, nil, nil, serverError ?? error) + }) + task.resume() + } } @@ -335,17 +386,17 @@ extension OneDriveFileProvider: ExtendedFileProvider { request.httpMethod = "GET" request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization") let task = session.dataTask(with: request, completionHandler: { (data, response, error) in - var dbError: FileProviderOneDriveError? + var serverError: FileProviderOneDriveError? var dic = [String: Any]() var keys = [String]() if let response = response as? HTTPURLResponse { let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) - dbError = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil + serverError = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr) { (dic, keys) = self.mapMediaInfo(json) } } - completionHandler(dic, keys, dbError ?? error) + completionHandler(dic, keys, serverError ?? error) }) task.resume() } @@ -353,7 +404,7 @@ extension OneDriveFileProvider: ExtendedFileProvider { extension OneDriveFileProvider: FileProvider { open func copy(with zone: NSZone? = nil) -> Any { - let copy = OneDriveFileProvider(credential: self.credential, serverURL: self.baseURL, drive: self.drive, cache: self.cache)! + let copy = OneDriveFileProvider(credential: self.credential, serverURL: self.baseURL, drive: self.drive, cache: self.cache) copy.currentPath = self.currentPath copy.delegate = self.delegate copy.fileOperationDelegate = self.fileOperationDelegate diff --git a/Sources/SMBFileProvider.swift b/Sources/SMBFileProvider.swift index d460c5d..7c35069 100644 --- a/Sources/SMBFileProvider.swift +++ b/Sources/SMBFileProvider.swift @@ -9,8 +9,7 @@ import Foundation class SMBFileProvider: FileProvider, FileProviderMonitor { - - open static var type: String = "Samba" + open class var type: String { return "SMB" } open var isPathRelative: Bool = true open var baseURL: URL? open var currentPath: String = "" @@ -26,7 +25,7 @@ class SMBFileProvider: FileProvider, FileProviderMonitor { return nil } self.baseURL = baseURL.appendingPathComponent("") - dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: DispatchQueue.Attributes.concurrent) + dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: .concurrent) operation_queue = OperationQueue() operation_queue.name = "FileProvider.\(type(of: self).type).Operation" diff --git a/Sources/WebDAVFileProvider.swift b/Sources/WebDAVFileProvider.swift index 51f7d1e..7a61475 100644 --- a/Sources/WebDAVFileProvider.swift +++ b/Sources/WebDAVFileProvider.swift @@ -12,7 +12,7 @@ import Foundation /// in case of using this class with unencrypted HTTP connection. open class WebDAVFileProvider: FileProviderBasicRemote { - open static let type: String = "WebDAV" + open class var type: String { return "WebDAV" } open let isPathRelative: Bool open let baseURL: URL? open var currentPath: String @@ -45,6 +45,14 @@ open class WebDAVFileProvider: FileProviderBasicRemote { return _session! } + /** + Initializes WebDAV provider. + + - Parameters: + - baseURL: Location of WebDAV server. + - credential: An `URLCredential` object with `user` and `password`. + - cache: A URLCache to cache downloaded files and contents. If set to nil, URLCache.shared object will be used. + */ public init? (baseURL: URL, credential: URLCredential?, cache: URLCache? = nil) { if !["http", "https"].contains(baseURL.uw_scheme.lowercased()) { return nil @@ -56,13 +64,17 @@ open class WebDAVFileProvider: FileProviderBasicRemote { self.validatingCache = true self.cache = cache self.credential = credential - dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: DispatchQueue.Attributes.concurrent) + dispatch_queue = DispatchQueue(label: "FileProvider.\(type(of: self).type)", attributes: .concurrent) operation_queue = OperationQueue() operation_queue.name = "FileProvider.\(type(of: self).type).Operation" } deinit { - _session?.invalidateAndCancel() + if fileProviderCancelTasksOnInvalidating { + _session?.invalidateAndCancel() + } else { + _session?.finishTasksAndInvalidate() + } } open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {