Refactored paginated listing in Dropbox and OneDrive
- Removed currentPath
This commit is contained in:
@@ -94,20 +94,17 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
|
||||
return nil
|
||||
}
|
||||
self.init(containerId: containerId, scope: scope)
|
||||
self.currentPath = aDecoder.decodeObject(forKey: "currentPath") as? String ?? ""
|
||||
self.isCoorinating = aDecoder.decodeBool(forKey: "isCoorinating")
|
||||
}
|
||||
|
||||
open override func encode(with aCoder: NSCoder) {
|
||||
aCoder.encode(self.containerId, forKey: "containerId")
|
||||
aCoder.encode(self.scope.rawValue, forKey: "scope")
|
||||
aCoder.encode(self.currentPath, forKey: "currentPath")
|
||||
aCoder.encode(self.isCoorinating, forKey: "isCoorinating")
|
||||
}
|
||||
|
||||
open override func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = CloudFileProvider(containerId: self.containerId, scope: self.scope)
|
||||
copy?.currentPath = self.currentPath
|
||||
copy?.delegate = self.delegate
|
||||
copy?.fileOperationDelegate = self.fileOperationDelegate
|
||||
return copy as Any
|
||||
|
||||
@@ -43,14 +43,12 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
|
||||
|
||||
public required convenience init?(coder aDecoder: NSCoder) {
|
||||
self.init(credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
|
||||
self.currentPath = aDecoder.decodeObject(forKey: "currentPath") as? String ?? ""
|
||||
self.useCache = aDecoder.decodeBool(forKey: "useCache")
|
||||
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
|
||||
}
|
||||
|
||||
override open func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = DropboxFileProvider(credential: self.credential, cache: self.cache)
|
||||
copy.currentPath = self.currentPath
|
||||
copy.delegate = self.delegate
|
||||
copy.fileOperationDelegate = self.fileOperationDelegate
|
||||
copy.useCache = self.useCache
|
||||
@@ -60,10 +58,28 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
|
||||
|
||||
open override func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
|
||||
// We don't want this progress became in another progress' hierarchy
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
list(path, progress: progress) { (contents, cursor, error) in
|
||||
completionHandler(contents, error)
|
||||
}
|
||||
_ = paginated(path, requestHandler: self.listRequest(path: path),
|
||||
pageHandler: { [weak self] (data, progress) -> (files: [FileObject], error: Error?, newToken: String?) in
|
||||
guard let json = data?.deserializeJSON(), let entries = json["entries"] as? [AnyObject] else {
|
||||
let err = self?.urlError(path, code: .badServerResponse)
|
||||
return ([], err, nil)
|
||||
}
|
||||
|
||||
var files = [FileObject]()
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = DropboxFileObject(json: entry) {
|
||||
files.append(file)
|
||||
progress.completedUnitCount += 1
|
||||
}
|
||||
}
|
||||
let ncursor: String?
|
||||
if let hasmore = (json["has_more"] as? NSNumber)?.boolValue, hasmore {
|
||||
ncursor = json["cursor"] as? String
|
||||
} else {
|
||||
ncursor = nil
|
||||
}
|
||||
return (files, nil, ncursor)
|
||||
}, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open override func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
|
||||
@@ -109,32 +125,33 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
|
||||
}
|
||||
|
||||
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
|
||||
let progress = Progress(totalUnitCount: -1)
|
||||
var foundFiles = [DropboxFileObject]()
|
||||
if let queryStr = query.findValue(forKey: "name", operator: .beginsWith) as? String {
|
||||
// Dropbox only support searching for file names begin with query in non-enterprise accounts.
|
||||
// We will use it if there is a `name BEGINSWITH[c] "query"` in predicate, then filter to form final result.
|
||||
search(path, query: queryStr, progress: progress, foundItem: { (file) in
|
||||
if query.evaluate(with: file.mapPredicate()) {
|
||||
foundFiles.append(file)
|
||||
let queryStr = query.findValue(forKey: "name", operator: .beginsWith) as? String
|
||||
let requestHandler = self.listRequest(path: path, queryStr: queryStr, recursive: true)
|
||||
return paginated(path, requestHandler: requestHandler,
|
||||
pageHandler: { [weak self] (data, progress) -> (files: [FileObject], error: Error?, newToken: String?) in
|
||||
guard let json = data?.deserializeJSON(), let entries = (json["entries"] ?? json["matches"]) as? [AnyObject] else {
|
||||
let err = self?.urlError(path, code: .badServerResponse)
|
||||
return ([], err, nil)
|
||||
}
|
||||
|
||||
var files = [FileObject]()
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = DropboxFileObject(json: entry), query.evaluate(with: file.mapPredicate()) {
|
||||
files.append(file)
|
||||
progress.completedUnitCount += 1
|
||||
foundItemHandler?(file)
|
||||
}
|
||||
}, completionHandler: { (error) in
|
||||
completionHandler(foundFiles, error)
|
||||
})
|
||||
} else {
|
||||
// Dropbox doesn't support searching attributes natively. The workaround is to fallback to listing all files
|
||||
// and filter it locally. It may have a network burden in case there is many files in Dropbox, so please use it concisely.
|
||||
list(path, recursive: true, progress: progress, progressHandler: { (files, _, error) in
|
||||
for file in files where query.evaluate(with: file.mapPredicate()) {
|
||||
foundItemHandler?(file)
|
||||
}
|
||||
}, completionHandler: { (files, _, error) in
|
||||
let predicatedFiles = files.filter { query.evaluate(with: $0.mapPredicate()) }
|
||||
completionHandler(predicatedFiles, error)
|
||||
})
|
||||
}
|
||||
return progress
|
||||
}
|
||||
let ncursor: String?
|
||||
if let hasmore = (json["has_more"] as? NSNumber)?.boolValue, hasmore {
|
||||
ncursor = json["cursor"] as? String
|
||||
} else if let hasmore = (json["more"] as? NSNumber)?.boolValue, hasmore {
|
||||
ncursor = (json["start"] as? Int).flatMap(String.init)
|
||||
} else {
|
||||
ncursor = nil
|
||||
}
|
||||
return (files, nil, ncursor)
|
||||
}, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
override func request(for operation: FileOperationType, overwrite: Bool = false, attributes: [URLResourceKey : Any] = [:]) -> URLRequest {
|
||||
|
||||
+32
-94
@@ -72,7 +72,6 @@ public final class DropboxFileObject: FileObject {
|
||||
}
|
||||
}
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
internal extension DropboxFileProvider {
|
||||
internal func correctPath(_ path: String?) -> String? {
|
||||
guard let path = path else { return nil }
|
||||
@@ -86,105 +85,44 @@ internal extension DropboxFileProvider {
|
||||
return p
|
||||
}
|
||||
|
||||
func list(_ path: String, cursor: String? = nil, prevContents: [DropboxFileObject] = [], recursive: Bool = false, session: URLSession? = nil, progress: Progress, progressHandler: ((_ contents: [FileObject], _ nextCursor: String?, _ error: Error?) -> Void)? = nil, completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
|
||||
if progress.isCancelled { return }
|
||||
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: URL
|
||||
if let cursor = cursor {
|
||||
url = URL(string: "files/list_folder/continue", relativeTo: apiURL)!
|
||||
requestDictionary["cursor"] = cursor as NSString?
|
||||
internal func listRequest(path: String, queryStr: String? = nil, recursive: Bool = false) -> ((_ token: String?) -> URLRequest?) {
|
||||
if let queryStr = queryStr {
|
||||
return { [weak self] (token) -> URLRequest? in
|
||||
guard let `self` = self else { return nil }
|
||||
let url = URL(string: "files/search", relativeTo: self.apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.set(httpAuthentication: self.credential, with: .oAuth2)
|
||||
request.set(httpContentType: .json)
|
||||
var requestDictionary: [String: AnyObject] = ["path": self.correctPath(path) as NSString!]
|
||||
requestDictionary["query"] = queryStr as NSString
|
||||
requestDictionary["start"] = NSNumber(value: (token.flatMap(Int.init) ?? 0))
|
||||
request.httpBody = Data(jsonDictionary: requestDictionary)
|
||||
return request
|
||||
}
|
||||
} else {
|
||||
url = URL(string: "files/list_folder", relativeTo: apiURL)!
|
||||
requestDictionary["path"] = correctPath(path) as NSString?
|
||||
requestDictionary["recursive"] = recursive as NSNumber?
|
||||
}
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
request.set(httpContentType: .json)
|
||||
request.httpBody = Data(jsonDictionary: requestDictionary)
|
||||
let task = (session ?? self.session).dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderHTTPError?
|
||||
var files = [DropboxFileObject]()
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = self.serverError(with: rCode, path: path, data: data)
|
||||
}
|
||||
if let json = data?.deserializeJSON() {
|
||||
if let entries = json["entries"] as? [AnyObject] , entries.count > 0 {
|
||||
files.reserveCapacity(entries.count)
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = DropboxFileObject(json: entry) {
|
||||
files.append(file)
|
||||
progress.totalUnitCount = Int64(files.count)
|
||||
}
|
||||
}
|
||||
let ncursor = json["cursor"] as? String
|
||||
let hasmore = (json["has_more"] as? NSNumber)?.boolValue ?? false
|
||||
if hasmore && !progress.isCancelled {
|
||||
progressHandler?(files, ncursor, responseError ?? error)
|
||||
self.list(path, cursor: ncursor, prevContents: prevContents + files, progress: progress, completionHandler: completionHandler)
|
||||
return
|
||||
}
|
||||
return { [weak self] (token) -> URLRequest? in
|
||||
guard let `self` = self else { return nil }
|
||||
var requestDictionary = [String: AnyObject]()
|
||||
let url: URL
|
||||
if let token = token {
|
||||
url = URL(string: "files/list_folder/continue", relativeTo: self.apiURL)!
|
||||
requestDictionary["cursor"] = token as NSString?
|
||||
} else {
|
||||
url = URL(string: "files/list_folder", relativeTo: self.apiURL)!
|
||||
requestDictionary["path"] = self.correctPath(path) as NSString?
|
||||
requestDictionary["recursive"] = NSNumber(value: recursive)
|
||||
}
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.set(httpAuthentication: self.credential, with: .oAuth2)
|
||||
request.set(httpContentType: .json)
|
||||
request.httpBody = Data(jsonDictionary: requestDictionary)
|
||||
return request
|
||||
}
|
||||
progressHandler?(files, nil, responseError ?? error)
|
||||
completionHandler(prevContents + files, nil, responseError ?? error)
|
||||
})
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.taskDescription = FileOperationType.fetch(path: path).json
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func search(_ startPath: String = "", query: String, start: Int = 0, maxResultPerPage: Int = 25, maxResults: Int = -1, progress: Progress, foundItem:@escaping ((_ file: DropboxFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
|
||||
if progress.isCancelled { return }
|
||||
|
||||
let url = URL(string: "files/search", relativeTo: apiURL)!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
request.set(httpContentType: .json)
|
||||
var requestDictionary: [String: AnyObject] = ["path": startPath as NSString]
|
||||
requestDictionary["query"] = query as NSString
|
||||
requestDictionary["start"] = start as NSNumber
|
||||
requestDictionary["max_results"] = maxResultPerPage as NSNumber
|
||||
request.httpBody = Data(jsonDictionary: requestDictionary)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderHTTPError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = self.serverError(with: rCode, path: startPath, data: data)
|
||||
}
|
||||
if let json = data?.deserializeJSON() {
|
||||
if let entries = json["matches"] as? [AnyObject] , entries.count > 0 {
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = DropboxFileObject(json: entry) {
|
||||
foundItem(file)
|
||||
progress.completedUnitCount += 1
|
||||
}
|
||||
}
|
||||
let rstart = json["start"] as? Int
|
||||
let hasmore = (json["more"] as? NSNumber)?.boolValue ?? false
|
||||
if hasmore && !progress.isCancelled, let rstart = rstart {
|
||||
self.search(startPath, query: query, start: rstart + entries.count, maxResultPerPage: maxResultPerPage, progress: progress, foundItem: foundItem, completionHandler: completionHandler)
|
||||
} else {
|
||||
completionHandler(responseError ?? error)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
completionHandler(responseError ?? error)
|
||||
})
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
// codebeat:enable[ARITY]
|
||||
|
||||
internal extension DropboxFileProvider {
|
||||
static let dateFormatter = DateFormatter()
|
||||
|
||||
@@ -129,14 +129,14 @@ public struct LocalFileInformationGenerator {
|
||||
|
||||
/// Audio and music extensions supportes for thumbnail.
|
||||
///
|
||||
/// Default: `["mp3", "aac", "m4a"]`
|
||||
static public var audioThumbnailExtensions: [String] = ["mp3", "aac", "m4a"]
|
||||
/// Default: `["mp1", "mp2", "mp3", "mpa", "mpga", "m1a", "m2a", "m4a", "m4b", "m4p", "m4r", "aac", "snd", "caf", "aa", "aax", "adts", "aif", "aifc", "aiff", "au", "flac", "amr", "wav", "wave", "bwf", "ac3", "eac3", "ec3", "cdda"]`
|
||||
static public var audioThumbnailExtensions: [String] = ["mp1", "mp2", "mp3", "mpa", "mpga", "m1a", "m2a", "m4a", "m4b", "m4p", "m4r", "aac", "snd", "caf", "aa", "aax", "adts", "aif", "aifc", "aiff", "au", "flac", "amr", "wav", "wave", "bwf", "ac3", "eac3", "ec3", "cdda"]
|
||||
|
||||
/// Video extensions supportes for thumbnail.
|
||||
///
|
||||
/// Default: `["mov", "mp4", "m4v", "mpg", "mpeg"]`
|
||||
static public var videoThumbnailExtensions: [String] = ["mov", "mp4", "m4v", "mpg", "mpeg"]
|
||||
|
||||
/// Default: `["mov", "mp4", "mpg4", "m4v", "mqv", "mpg", "mpeg", "avi", "vfw", "3g2", "3gp", "3gp2", "3gpp", "qt"]`
|
||||
static public var videoThumbnailExtensions: [String] = ["mov", "mp4", "mpg4", "m4v", "mqv", "mpg", "mpeg", "avi", "vfw", "3g2", "3gp", "3gp2", "3gpp", "qt"]
|
||||
|
||||
/// Portable document file extensions supportes for thumbnail.
|
||||
///
|
||||
/// Default: `["pdf"]`
|
||||
@@ -160,13 +160,13 @@ public struct LocalFileInformationGenerator {
|
||||
|
||||
/// Audio and music extensions supportes for properties.
|
||||
///
|
||||
/// Default: `["mp3", "aac", "m4a", "caf"]`
|
||||
static public var audioPropertiesExtensions: [String] = ["mp3", "aac", "m4a", "caf"]
|
||||
/// Default: `["mp1", "mp2", "mp3", "mpa", "mpga", "m1a", "m2a", "m4a", "m4b", "m4p", "m4r", "aac", "snd", "caf", "aa", "aax", "adts", "aif", "aifc", "aiff", "au", "flac", "amr", "wav", "wave", "bwf", "ac3", "eac3", "ec3", "cdda"]`
|
||||
static public var audioPropertiesExtensions: [String] = ["mp1", "mp2", "mp3", "mpa", "mpga", "m1a", "m2a", "m4a", "m4b", "m4p", "m4r", "aac", "snd", "caf", "aa", "aax", "adts", "aif", "aifc", "aiff", "au", "flac", "amr", "wav", "wave", "bwf", "ac3", "eac3", "ec3", "cdda"]
|
||||
|
||||
/// Video extensions supportes for properties.
|
||||
///
|
||||
/// Default: `["mp4", "mpg", "3gp", "mov", "avi"]`
|
||||
static public var videoPropertiesExtensions: [String] = ["mp4", "mpg", "3gp", "mov", "avi"]
|
||||
/// Default: `["mov", "mp4", "mpg4", "m4v", "mqv", "mpg", "mpeg", "avi", "vfw", "3g2", "3gp", "3gp2", "3gpp", "qt"]`
|
||||
static public var videoPropertiesExtensions: [String] = ["mov", "mp4", "mpg4", "m4v", "mqv", "mpg", "mpeg", "avi", "vfw", "3g2", "3gp", "3gp2", "3gpp", "qt"]
|
||||
|
||||
/// Portable document file extensions supportes for properties.
|
||||
///
|
||||
|
||||
@@ -15,7 +15,9 @@ import Foundation
|
||||
open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite {
|
||||
open class var type: String { return "FTP" }
|
||||
open let baseURL: URL?
|
||||
open var currentPath: String
|
||||
/// **OBSOLETED** Current active path used in `contentsOfDirectory(path:completionHandler:)` method.
|
||||
@available(*, obsoleted: 0.22, message: "This property is redundant with almost no use internally.")
|
||||
open var currentPath: String = ""
|
||||
|
||||
open var dispatch_queue: DispatchQueue
|
||||
open var operation_queue: OperationQueue {
|
||||
@@ -88,7 +90,6 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
|
||||
|
||||
self.baseURL = (urlComponents.url!.path.hasSuffix("/") ? urlComponents.url! : urlComponents.url!.appendingPathComponent("")).absoluteURL
|
||||
self.passiveMode = passive
|
||||
self.currentPath = ""
|
||||
self.useCache = false
|
||||
self.validatingCache = true
|
||||
self.cache = cache
|
||||
@@ -107,7 +108,6 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
|
||||
public required convenience init?(coder aDecoder: NSCoder) {
|
||||
guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else { return nil }
|
||||
self.init(baseURL: baseURL, passive: aDecoder.decodeBool(forKey: "passiveMode"), credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
|
||||
self.currentPath = aDecoder.decodeObject(forKey: "currentPath") as? String ?? ""
|
||||
self.useCache = aDecoder.decodeBool(forKey: "useCache")
|
||||
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
|
||||
self.useAppleImplementation = aDecoder.decodeBool(forKey: "useAppleImplementation")
|
||||
@@ -116,7 +116,6 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
|
||||
public func encode(with aCoder: NSCoder) {
|
||||
aCoder.encode(self.baseURL, forKey: "baseURL")
|
||||
aCoder.encode(self.credential, forKey: "credential")
|
||||
aCoder.encode(self.currentPath, forKey: "currentPath")
|
||||
aCoder.encode(self.useCache, forKey: "useCache")
|
||||
aCoder.encode(self.validatingCache, forKey: "validatingCache")
|
||||
aCoder.encode(self.useAppleImplementation, forKey: "useAppleImplementation")
|
||||
@@ -129,7 +128,6 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
|
||||
|
||||
open func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = FTPFileProvider(baseURL: self.baseURL!, credential: self.credential, cache: self.cache)!
|
||||
copy.currentPath = self.currentPath
|
||||
copy.delegate = self.delegate
|
||||
copy.fileOperationDelegate = self.fileOperationDelegate
|
||||
copy.useCache = self.useCache
|
||||
@@ -315,7 +313,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
|
||||
}
|
||||
|
||||
open func url(of path: String?) -> URL {
|
||||
let path = (path ?? self.currentPath).trimmingCharacters(in: CharacterSet(charactersIn: "/ ")).addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? (path ?? self.currentPath)
|
||||
let path = path?.trimmingCharacters(in: CharacterSet(charactersIn: "/ ")).addingPercentEncoding(withAllowedCharacters: .filePathAllowed) ?? (path ?? "")
|
||||
|
||||
var baseUrlComponent = URLComponents(url: self.baseURL!, resolvingAgainstBaseURL: true)
|
||||
baseUrlComponent?.user = credential?.user
|
||||
|
||||
@@ -789,20 +789,14 @@ internal extension FTPFileProvider {
|
||||
}
|
||||
|
||||
func ftpPath(_ apath: String) -> String {
|
||||
var path = apath.isEmpty ? self.currentPath : apath
|
||||
|
||||
// path of base url should be concreted into file path! And remove final slash
|
||||
path = baseURL!.appendingPathComponent(path).path.replacingOccurrences(of: "/", with: "", options: [.anchored, .backwards])
|
||||
var path = baseURL!.appendingPathComponent(apath).path.replacingOccurrences(of: "/", with: "", options: [.anchored, .backwards])
|
||||
|
||||
// Fixing slashes
|
||||
if !path.hasPrefix("/") {
|
||||
path = "/" + path
|
||||
}
|
||||
|
||||
if path.isEmpty {
|
||||
path = "/"
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,6 @@ public protocol FileProviderBasic: class, NSSecureCoding {
|
||||
/// The url of which paths should resolve against.
|
||||
var baseURL: URL? { get }
|
||||
|
||||
/// **DEPRECATED** Current active path used in `contentsOfDirectory(path:completionHandler:)` method.
|
||||
@available(*, deprecated, message: "This property is redundant with almost no use internally.")
|
||||
var currentPath: String { get set }
|
||||
|
||||
/**
|
||||
Dispatch queue usually used in query methods.
|
||||
Set it to a new object to switch between cuncurrent and serial queues.
|
||||
|
||||
@@ -17,7 +17,10 @@ import Foundation
|
||||
open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite {
|
||||
open class var type: String { fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.") }
|
||||
open let baseURL: URL?
|
||||
open var currentPath: String
|
||||
|
||||
/// **OBSOLETED** Current active path used in `contentsOfDirectory(path:completionHandler:)` method.
|
||||
@available(*, obsoleted: 0.22, message: "This property is redundant with almost no use internally.")
|
||||
open var currentPath: String = ""
|
||||
|
||||
open var dispatch_queue: DispatchQueue
|
||||
open var operation_queue: OperationQueue {
|
||||
@@ -84,7 +87,6 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
|
||||
*/
|
||||
public init(baseURL: URL?, credential: URLCredential?, cache: URLCache?) {
|
||||
self.baseURL = baseURL
|
||||
self.currentPath = ""
|
||||
self.useCache = false
|
||||
self.validatingCache = true
|
||||
self.cache = cache
|
||||
@@ -107,7 +109,6 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
|
||||
public func encode(with aCoder: NSCoder) {
|
||||
aCoder.encode(self.baseURL, forKey: "baseURL")
|
||||
aCoder.encode(self.credential, forKey: "credential")
|
||||
aCoder.encode(self.currentPath, forKey: "currentPath")
|
||||
aCoder.encode(self.useCache, forKey: "useCache")
|
||||
aCoder.encode(self.validatingCache, forKey: "validatingCache")
|
||||
}
|
||||
@@ -317,6 +318,46 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
|
||||
return progress
|
||||
}
|
||||
|
||||
internal func paginated(_ path: String, startToken: String? = nil, currentProgress: Progress? = nil, previousResult: [FileObject] = [], requestHandler: @escaping (_ token: String?) -> URLRequest?, pageHandler: @escaping (_ data: Data?, _ progress: Progress) -> (files: [FileObject], error: Error?, newToken: String?), completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) -> Progress {
|
||||
let progress = currentProgress ?? Progress(totalUnitCount: -1)
|
||||
if progress.isCancelled { return progress }
|
||||
|
||||
guard let request = requestHandler(startToken) else {
|
||||
return progress
|
||||
}
|
||||
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
if let error = error {
|
||||
completionHandler(previousResult, error)
|
||||
return
|
||||
}
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
let responseError = self.serverError(with: rCode, path: path, data: data)
|
||||
completionHandler(previousResult, responseError)
|
||||
return
|
||||
}
|
||||
|
||||
let (newFiles, err, newToken) = pageHandler(data, progress)
|
||||
if let error = err {
|
||||
completionHandler(previousResult, error)
|
||||
return
|
||||
}
|
||||
let files = previousResult + newFiles
|
||||
if let newToken = newToken, !progress.isCancelled {
|
||||
_ = self.paginated(path, startToken: newToken, currentProgress: progress, previousResult: files, requestHandler: requestHandler, pageHandler: pageHandler, completionHandler: completionHandler)
|
||||
} else {
|
||||
completionHandler(files, nil)
|
||||
}
|
||||
|
||||
})
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
return progress
|
||||
}
|
||||
|
||||
internal func upload_simple(_ targetPath: String, request: URLRequest, data: Data? = nil, localFile: URL? = nil, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> Progress? {
|
||||
let size = data?.count ?? Int((try? localFile?.resourceValues(forKeys: [.fileSizeKey]))??.fileSize ?? -1)
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@ import Foundation
|
||||
open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndoable {
|
||||
open class var type: String { return "Local" }
|
||||
open fileprivate(set) var baseURL: URL?
|
||||
open var currentPath: String
|
||||
/// **OBSOLETED** Current active path used in `contentsOfDirectory(path:completionHandler:)` method.
|
||||
@available(*, obsoleted: 0.22, message: "This property is redundant with almost no use internally.")
|
||||
open var currentPath: String = ""
|
||||
open var dispatch_queue: DispatchQueue
|
||||
open var operation_queue: OperationQueue
|
||||
open weak var delegate: FileProviderDelegate?
|
||||
@@ -99,7 +101,6 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
fatalError("Cannot initialize a Local provider from remote URL.")
|
||||
}
|
||||
self.baseURL = (baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")).absoluteURL
|
||||
self.currentPath = ""
|
||||
self.credential = nil
|
||||
self.isCoorinating = false
|
||||
|
||||
@@ -121,13 +122,11 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
return nil
|
||||
}
|
||||
self.init(baseURL: baseURL)
|
||||
self.currentPath = aDecoder.decodeObject(forKey: "currentPath") as? String ?? ""
|
||||
self.isCoorinating = aDecoder.decodeBool(forKey: "isCoorinating")
|
||||
}
|
||||
|
||||
open func encode(with aCoder: NSCoder) {
|
||||
aCoder.encode(self.baseURL, forKey: "currentPath")
|
||||
aCoder.encode(self.currentPath, forKey: "currentPath")
|
||||
aCoder.encode(self.isCoorinating, forKey: "isCoorinating")
|
||||
}
|
||||
|
||||
@@ -137,7 +136,6 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
|
||||
|
||||
public func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = LocalFileProvider(baseURL: self.baseURL!)
|
||||
copy.currentPath = self.currentPath
|
||||
copy.undoManager = self.undoManager
|
||||
copy.isCoorinating = self.isCoorinating
|
||||
copy.delegate = self.delegate
|
||||
|
||||
@@ -139,7 +139,6 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
|
||||
self.init(credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential,
|
||||
serverURL: aDecoder.decodeObject(forKey: "baseURL") as? URL,
|
||||
subAddress: subAddress)
|
||||
self.currentPath = aDecoder.decodeObject(forKey: "currentPath") as? String ?? ""
|
||||
self.useCache = aDecoder.decodeBool(forKey: "useCache")
|
||||
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
|
||||
}
|
||||
@@ -151,7 +150,6 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
|
||||
|
||||
open override func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = OneDriveFileProvider(credential: self.credential, serverURL: self.baseURL, subAddress: self.subAddress, cache: self.cache)
|
||||
copy.currentPath = self.currentPath
|
||||
copy.delegate = self.delegate
|
||||
copy.fileOperationDelegate = self.fileOperationDelegate
|
||||
copy.useCache = self.useCache
|
||||
@@ -160,9 +158,29 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
|
||||
}
|
||||
|
||||
open override func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
|
||||
list(path) { (contents, cursor, error) in
|
||||
completionHandler(contents, error)
|
||||
}
|
||||
_ = paginated(path, requestHandler: { [weak self] (token) -> URLRequest? in
|
||||
guard let `self` = self else { return nil }
|
||||
let url = token.flatMap(URL.init(string:)) ?? self.url(of: path, modifier: "children")
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.set(httpAuthentication: self.credential, with: .oAuth2)
|
||||
return request
|
||||
}, pageHandler: { [weak self] (data, _) -> (files: [FileObject], error: Error?, newToken: String?) in
|
||||
guard let `self` = self else { return ([], nil, nil) }
|
||||
|
||||
guard let json = data?.deserializeJSON(), let entries = json["value"] as? [AnyObject] else {
|
||||
let err = self.urlError(path, code: .badServerResponse)
|
||||
return ([], err, nil)
|
||||
}
|
||||
|
||||
var files = [FileObject]()
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, subAddress: self.subAddress, json: entry) {
|
||||
files.append(file)
|
||||
}
|
||||
}
|
||||
return (files, nil, json["@odata.nextLink"] as? String)
|
||||
}, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open override func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
|
||||
@@ -206,21 +224,45 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
|
||||
}
|
||||
|
||||
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
|
||||
var foundFiles = [OneDriveFileObject]()
|
||||
var queryStr: String?
|
||||
queryStr = query.findValue(forKey: "name") as? String ?? query.findAllValues(forKey: nil).flatMap { $0.value as? String }.first
|
||||
guard let finalQueryStr = queryStr else { return nil }
|
||||
let progress = Progress(totalUnitCount: -1)
|
||||
progress.setUserInfoObject(url(of: path), forKey: .fileURLKey)
|
||||
search(path, query: finalQueryStr, recursive: recursive, progress: progress, foundItem: { (file) in
|
||||
if query.evaluate(with: file.mapPredicate()) {
|
||||
foundFiles.append(file)
|
||||
foundItemHandler?(file)
|
||||
let queryStr = query.findValue(forKey: "name") as? String ?? query.findAllValues(forKey: nil).flatMap { $0.value as? String }.first
|
||||
|
||||
return paginated(path, requestHandler: { [weak self] (token) -> URLRequest? in
|
||||
guard let `self` = self else { return nil }
|
||||
|
||||
let url: URL
|
||||
if let next = token.flatMap(URL.init(string:)) {
|
||||
url = next
|
||||
} else {
|
||||
let bURL = self.baseURL!.appendingPathComponent(self.subAddress.drivePath).appendingPathComponent("root/search")
|
||||
var components = URLComponents(url: bURL, resolvingAgainstBaseURL: false)!
|
||||
let qItem = URLQueryItem(name: "q", value: (queryStr ?? "*"))
|
||||
components.queryItems = [qItem]
|
||||
if recursive {
|
||||
components.queryItems?.append(URLQueryItem(name: "expand", value: "children"))
|
||||
}
|
||||
url = components.url!
|
||||
}
|
||||
}, completionHandler: { (error) in
|
||||
completionHandler(foundFiles, error)
|
||||
})
|
||||
return progress
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
return request
|
||||
}, pageHandler: { [weak self] (data, progress) -> (files: [FileObject], error: Error?, newToken: String?) in
|
||||
guard let `self` = self else { return ([], nil, nil) }
|
||||
guard let json = data?.deserializeJSON(), let entries = json["value"] as? [AnyObject] else {
|
||||
let err = self.urlError(path, code: .badServerResponse)
|
||||
return ([], err, nil)
|
||||
}
|
||||
|
||||
var foundFiles = [FileObject]()
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, subAddress: self.subAddress, json: entry), query.evaluate(with: file.mapPredicate()) {
|
||||
foundFiles.append(file)
|
||||
foundItemHandler?(file)
|
||||
}
|
||||
}
|
||||
|
||||
return (foundFiles, nil, json["@odata.nextLink"] as? String)
|
||||
}, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
open func url(of path: String, modifier: String? = nil) -> URL {
|
||||
|
||||
@@ -90,95 +90,6 @@ public final class OneDriveFileObject: FileObject {
|
||||
}
|
||||
}
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
internal extension OneDriveFileProvider {
|
||||
func list(_ path: String, cursor: URL? = nil, prevContents: [OneDriveFileObject] = [], completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
|
||||
let url = cursor ?? self.url(of: path, modifier: "children")
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderHTTPError?
|
||||
var files = prevContents
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = self.serverError(with: rCode, path: path, data: data)
|
||||
}
|
||||
if let json = data?.deserializeJSON() {
|
||||
if let entries = json["value"] as? [AnyObject] , entries.count > 0 {
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, subAddress: self.subAddress, json: entry) {
|
||||
files.append(file)
|
||||
}
|
||||
}
|
||||
let ncursor: URL? = (json["@odata.nextLink"] as? String).flatMap { URL(string: $0) }
|
||||
let hasmore = ncursor != nil
|
||||
if hasmore {
|
||||
self.list(path, cursor: ncursor, prevContents: files, completionHandler: completionHandler)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
completionHandler(files, nil, responseError ?? error)
|
||||
})
|
||||
task.taskDescription = FileOperationType.fetch(path: path).json
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func search(_ startPath: String = "", query: String, recursive: Bool, next: URL? = nil, progress: Progress, foundItem: @escaping ((_ file: OneDriveFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
|
||||
if progress.isCancelled {
|
||||
return
|
||||
}
|
||||
|
||||
let url: URL
|
||||
if let next = next {
|
||||
url = next
|
||||
} else {
|
||||
let bURL = self.baseURL!.appendingPathComponent(subAddress.drivePath).appendingPathComponent("root/search")
|
||||
var components = URLComponents(url: bURL, resolvingAgainstBaseURL: false)!
|
||||
let qItem = URLQueryItem(name: "q", value: query)
|
||||
components.queryItems = [qItem]
|
||||
if recursive {
|
||||
components.queryItems?.append(URLQueryItem(name: "expand", value: "children"))
|
||||
}
|
||||
url = components.url!
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.set(httpAuthentication: credential, with: .oAuth2)
|
||||
|
||||
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
var responseError: FileProviderHTTPError?
|
||||
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
|
||||
responseError = self.serverError(with: rCode, path: startPath, data: data)
|
||||
}
|
||||
if let json = data?.deserializeJSON() {
|
||||
if let entries = json["value"] as? [AnyObject] , entries.count > 0 {
|
||||
for entry in entries {
|
||||
if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, subAddress: self.subAddress, json: entry) {
|
||||
foundItem(file)
|
||||
}
|
||||
}
|
||||
let next: URL? = (json["@odata.nextLink"] as? String).flatMap { URL(string: $0) }
|
||||
if !progress.isCancelled, let next = next {
|
||||
self.search(startPath, query: query, recursive: recursive, next: next, progress: progress, foundItem: foundItem, completionHandler: completionHandler)
|
||||
} else {
|
||||
completionHandler(responseError ?? error)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
completionHandler(responseError ?? error)
|
||||
})
|
||||
progress.cancellationHandler = { [weak task] in
|
||||
task?.cancel()
|
||||
}
|
||||
progress.setUserInfoObject(Date(), forKey: .startingTimeKey)
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
// codebeat:enable[ARITY]
|
||||
|
||||
internal extension OneDriveFileProvider {
|
||||
static let dateFormatter = DateFormatter()
|
||||
static let decimalFormatter = NumberFormatter()
|
||||
|
||||
@@ -49,14 +49,12 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
|
||||
}
|
||||
self.init(baseURL: baseURL,
|
||||
credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
|
||||
self.currentPath = aDecoder.decodeObject(forKey: "currentPath") as? String ?? ""
|
||||
self.useCache = aDecoder.decodeBool(forKey: "useCache")
|
||||
self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
|
||||
}
|
||||
|
||||
override open func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = WebDAVFileProvider(baseURL: self.baseURL!, credential: self.credential, cache: self.cache)!
|
||||
copy.currentPath = self.currentPath
|
||||
copy.delegate = self.delegate
|
||||
copy.fileOperationDelegate = self.fileOperationDelegate
|
||||
copy.useCache = self.useCache
|
||||
|
||||
Reference in New Issue
Block a user