Added VolumeObject for storageProperties method

- Refined error handling in HTTP provider
- Added contentType and hash to OneDriveFileObject
This commit is contained in:
Amir Abbas
2017-09-05 02:26:09 +04:30
parent 5a5beb6891
commit b380685932
12 changed files with 245 additions and 138 deletions
+4 -4
View File
@@ -123,7 +123,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
`contents`: An array of `FileObject` identifying the the directory entries.
`error`: Error returned by system.
*/
open override func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
open override func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
// FIXME: create runloop for dispatch_queue, start query on it
dispatch_queue.async {
let pathURL = self.url(of: path)
@@ -178,7 +178,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
/// Please don't rely this function to get iCloud drive total and remaining capacity
/// - Important: iCloud Storage size and free space is unavailable, it returns local space
open override func storageProperties(completionHandler: (@escaping (_ total: Int64, _ used: Int64) -> Void)) {
open override func storageProperties(completionHandler: @escaping (VolumeObject?) -> Void) {
super.storageProperties(completionHandler: completionHandler)
}
@@ -192,7 +192,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
`attributes`: A `FileObject` containing the attributes of the item.
`error`: Error returned by system.
*/
open override func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
open override func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
dispatch_queue.async {
let pathURL = self.url(of: path)
let query = NSMetadataQuery()
@@ -249,7 +249,7 @@ open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
- foundItemHandler: Closure which is called when a file is found
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
*/
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let mapDict: [String: String] = ["url": NSMetadataItemURLKey, "name": NSMetadataItemFSNameKey, "path": NSMetadataItemPathKey, "filesize": NSMetadataItemFSSizeKey, "modifiedDate": NSMetadataItemFSContentChangeDateKey, "creationDate": NSMetadataItemFSCreationDateKey, "contentType": NSMetadataItemContentTypeKey]
+12 -10
View File
@@ -57,14 +57,14 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
return copy
}
open override func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
open override func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
let progress = Progress(parent: nil, userInfo: nil)
list(path, progress: progress) { (contents, cursor, error) in
completionHandler(contents, error)
}
}
open override func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
open override func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
@@ -87,24 +87,26 @@ open class DropboxFileProvider: HTTPFileProvider, FileProviderSharing {
task.resume()
}
open override func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
open override func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
let url = URL(string: "users/get_space_usage", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.set(httpAuthentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var totalSize: Int64 = -1
var usedSize: Int64 = 0
if let json = data?.deserializeJSON() {
totalSize = ((json["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.int64Value ?? -1
usedSize = (json["used"] as? NSNumber)?.int64Value ?? 0
guard let json = data?.deserializeJSON() else {
completionHandler(nil)
return
}
completionHandler(totalSize, usedSize)
let volume = VolumeObject(allValues: [:])
volume.totalCapacity = ((json["allocation"] as? NSDictionary)?["allocated"] as? NSNumber)?.int64Value ?? -1
volume.usage = (json["used"] as? NSNumber)?.int64Value ?? 0
completionHandler(volume)
})
task.resume()
}
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let progress = Progress(parent: nil, userInfo: nil)
var foundFiles = [DropboxFileObject]()
if let queryStr = query.findValue(forKey: "name", operator: .beginsWith) as? String {
+2 -5
View File
@@ -51,13 +51,12 @@ public final class DropboxFileObject: FileObject {
/// The document identifier is a value assigned by the Dropbox to a file.
/// This value is used to identify the document regardless of where it is moved on a volume.
/// The identifier persists across system restarts.
open internal(set) var id: String? {
get {
return allValues[.documentIdentifierKey] as? String
return allValues[.fileResourceIdentifierKey] as? String
}
set {
allValues[.documentIdentifierKey] = newValue
allValues[.fileResourceIdentifierKey] = newValue
}
}
@@ -75,8 +74,6 @@ public final class DropboxFileObject: FileObject {
// codebeat:disable[ARITY]
internal extension DropboxFileProvider {
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 }
+7 -7
View File
@@ -152,7 +152,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
internal var serverSupportsRFC3659: Bool = true
open func contentsOfDirectory(path: String, completionHandler: @escaping (([FileObject], Error?) -> Void)) {
open func contentsOfDirectory(path: String, completionHandler: @escaping ([FileObject], Error?) -> Void) {
self.contentsOfDirectory(path: path, rfc3659enabled: serverSupportsRFC3659, completionHandler: completionHandler)
}
@@ -167,7 +167,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
`contents`: An array of `FileObject` identifying the the directory entries.
`error`: Error returned by system.
*/
open func contentsOfDirectory(path apath: String, rfc3659enabled: Bool , completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
open func contentsOfDirectory(path apath: String, rfc3659enabled: Bool , completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
let path = ftpPath(apath)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
@@ -207,7 +207,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
}
}
open func attributesOfItem(path: String, completionHandler: @escaping ((FileObject?, Error?) -> Void)) {
open func attributesOfItem(path: String, completionHandler: @escaping (FileObject?, Error?) -> Void) {
self.attributesOfItem(path: path, rfc3659enabled: serverSupportsRFC3659, completionHandler: completionHandler)
}
@@ -222,7 +222,7 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
`attributes`: A `FileObject` containing the attributes of the item.
`error`: Error returned by system.
*/
open func attributesOfItem(path apath: String, rfc3659enabled: Bool, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
open func attributesOfItem(path apath: String, rfc3659enabled: Bool, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
let path = ftpPath(apath)
let task = session.fpstreamTask(withHostName: baseURL!.host!, port: baseURL!.port!)
@@ -270,13 +270,13 @@ open class FTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fil
}
}
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
open func storageProperties(completionHandler: @escaping (_ volume: VolumeObject?) -> Void) {
dispatch_queue.async {
completionHandler(-1, 0)
completionHandler(nil)
}
}
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let progress = Progress(parent: nil, userInfo: nil)
if recursive {
return self.recursiveList(path: path, useMLST: true, foundItemsHandler: { items in
+91
View File
@@ -207,6 +207,97 @@ open class FileObject: Equatable {
}
}
/// Containts attributes of a provider.
open class VolumeObject {
/// A `Dictionary` contains volume information, using `URLResourceKey` keys.
open internal(set) var allValues: [URLResourceKey: Any]
public init(allValues: [URLResourceKey: Any]) {
self.allValues = allValues
}
/// The root directory of the resources volume, returned as an `URL` object.
open internal(set) var url: URL? {
get {
return allValues[.volumeURLKey] as? URL
}
set {
allValues[.volumeURLKey] = newValue
}
}
/// The name of the volume.
open internal(set) var name: String? {
get {
return allValues[.volumeNameKey] as? String
}
set {
allValues[.volumeNameKey] = newValue
}
}
/// the volumes capacity in bytes, return -1 if is undetermined.
open internal(set) var totalCapacity: Int64 {
get {
return allValues[.volumeTotalCapacityKey] as? Int64 ?? -1
}
set {
allValues[.volumeTotalCapacityKey] = newValue
}
}
/// The volumes available capacity in bytes.
open internal(set) var availableCapacity: Int64 {
get {
return allValues[.volumeAvailableCapacityKey] as? Int64 ?? 0
}
set {
allValues[.volumeAvailableCapacityKey] = newValue
}
}
open internal(set) var usage: Int64 {
get {
return totalCapacity >= 0 ? totalCapacity - availableCapacity : -availableCapacity
}
set {
availableCapacity = totalCapacity >= 0 ? totalCapacity - newValue : -newValue
}
}
/// the volumes creation date, returned as an `Date` object, or NULL if it cannot be determined
open internal(set) var creationDate: Date? {
get {
return allValues[.volumeCreationDateKey] as? Date
}
set {
allValues[.volumeCreationDateKey] = newValue
}
}
/// Determining whether the volume is read-only
open internal(set) var isReadOnly: Bool {
get {
return allValues[.volumeIsReadOnlyKey] as? Bool ?? false
}
set {
allValues[.volumeIsReadOnlyKey] = newValue
}
}
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
open internal(set) var isEncrypted: Bool {
get {
return allValues[.volumeIsEncryptedKey] as? Bool ?? false
}
set {
allValues[.volumeIsEncryptedKey] = !newValue
}
}
}
/// Sorting FileObject array by given criteria, **not thread-safe**
public struct FileObjectSorting {
+18 -28
View File
@@ -82,11 +82,11 @@ public protocol FileProviderBasic: class, NSSecureCoding {
- `attributes`: A `FileObject` containing the attributes of the item.
- `error`: Error returned by system.
*/
func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void))
func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void)
/// Returns total and used capacity in provider container asynchronously.
func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void))
/// Returns volume/provider information asynchronously.
func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void)
/**
Search files inside directory using query asynchronously.
@@ -101,7 +101,7 @@ public protocol FileProviderBasic: class, NSSecureCoding {
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
*/
@discardableResult
func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress?
func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress?
/**
Search files inside directory using query asynchronously.
@@ -126,7 +126,7 @@ public protocol FileProviderBasic: class, NSSecureCoding {
- Returns: An `Progress` to get progress or cancel progress.
*/
@discardableResult
func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress?
func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress?
/**
Returns an independent url to access the file. Some providers like `Dropbox` due to their nature.
@@ -150,7 +150,7 @@ public protocol FileProviderBasic: class, NSSecureCoding {
}
extension FileProviderBasic {
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let predicate = NSPredicate(format: "name BEGINSWITH[c] %@", query)
return self.searchFiles(path: path, recursive: recursive, query: predicate, foundItemHandler: foundItemHandler, completionHandler: completionHandler)
}
@@ -166,6 +166,14 @@ extension FileProviderBasic {
operation_queue.maxConcurrentOperationCount = newValue
}
}
/// Returns total and used capacity in provider container asynchronously.
@available(*, deprecated, message: "Use storageProperties which returns VolumeObject")
func storageProperties(completionHandler: @escaping (_ total: Int64, _ used: Int64) -> Void) {
self.storageProperties { (info) in
completionHandler(info?.totalCapacity ?? -1, info?.usage ?? 0)
}
}
}
/// Checking equality of two file provider, regardless of current path queues and delegates.
@@ -783,7 +791,7 @@ public protocol ExtendedFileProvider: FileProviderBasic {
func propertiesOfFileSupported(path: String) -> Bool
/**
Generates ans returns a thumbnail preview of document asynchronously. The defualt dimension of returned image is different
Generates and returns a thumbnail preview of document asynchronously. The defualt dimension of returned image is different
regarding provider type, usually 64x64 pixels.
- Parameters:
@@ -795,7 +803,7 @@ public protocol ExtendedFileProvider: FileProviderBasic {
func thumbnailOfFile(path: String, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void))
/**
Generates ans returns a thumbnail preview of document asynchronously. The defualt dimension of returned image is different
Generates and returns a thumbnail preview of document asynchronously. The defualt dimension of returned image is different
regarding provider type, usually 64x64 pixels. Default value used when `dimenstion` is `nil`.
- Note: `LocalFileInformationGenerator` variables can be set to change default behavior of
@@ -1027,26 +1035,8 @@ public enum FileOperationType: CustomStringConvertible {
}
/// Allows to get progress or cancel an in-progress operation, useful for remote providers
@available(*, obsoleted: 1.0, message: "Use Progress class class instead.")
public protocol OperationHandle {
/// Operation supposed to be done on files. Contains file paths as associated value.
var operationType: FileOperationType { get }
/// Bytes written/read by operation so far.
var bytesSoFar: Int64 { get }
/// Total bytes of operation.
var totalBytes: Int64 { get }
/// Operation is progress or not, Returns false if operation is done or not initiated yet.
var inProgress: Bool { get }
/// Progress of operation, usually equals with `bytesSoFar/totalBytes`. or NaN if not available.
var progress: Float { get }
/// Cancels operation while in progress, or cancels data/download/upload url session task.
func cancel() -> Bool
}
@available(*, obsoleted: 1.0, message: "Use Foudation.Progress class instead.")
public protocol OperationHandle {}
/// Delegate methods for reporting provider's operation result and progress, when it's ready to update
/// user interface.
+25 -29
View File
@@ -12,7 +12,7 @@ import Foundation
The abstract base class for all REST/Web based providers such as WebDAV, Dropbox, OneDrive, Google Drive, etc. and encapsulates basic
functionalitis such as downloading/uploading.
No instance of this class should (and can) be created. Use derivated classes instead. It leads to a crash with `fatalError()`.
No instance of this class should (and can) be created. Use derived classes instead. It leads to a crash with `fatalError()`.
*/
open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, FileProviderReadWrite {
open class var type: String { fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.") }
@@ -132,25 +132,25 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
}
}
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
open func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
open func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
open func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
fatalError("HTTPFileProvider is an abstract class. Please implement \(#function) in subclass.")
}
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
self.storageProperties { total, _ in
completionHandler(total > 0)
self.storageProperties { volume in
completionHandler(volume != nil)
}
}
@@ -193,20 +193,17 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
open func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> Progress? {
let operation = FileOperationType.copy(source: path, destination: destURL.absoluteString)
let request = self.request(for: operation)
let cantLoadError = throwError(path, code: .cannotLoadFromNetwork)
return self.download_simple(path: path, request: request, operation: operation, completionHandler: { [weak self] (tempURL, error) in
if let error = error {
completionHandler?(error)
self?.delegateNotify(operation, error: error)
return
}
guard let tempURL = tempURL else {
completionHandler?(error)
self?.delegateNotify(operation, error: error)
return
}
do {
if let error = error {
throw error
}
guard let tempURL = tempURL else {
throw cantLoadError
}
try FileManager.default.moveItem(at: tempURL, to: destURL)
completionHandler?(nil)
self?.delegateNotify(operation)
@@ -227,19 +224,18 @@ open class HTTPFileProvider: FileProviderBasicRemote, FileProviderOperations, Fi
let operation = FileOperationType.fetch(path: path)
var request = self.request(for: operation)
let cantLoadError = throwError(path, code: .cannotLoadFromNetwork)
request.set(httpRangeWithOffset: offset, length: length)
return self.download_simple(path: path, request: request, operation: operation, completionHandler: { (tempURL, error) in
if let error = error {
completionHandler(nil, error)
return
}
guard let tempURL = tempURL else {
completionHandler(nil, error)
return
}
do {
if let error = error {
throw error
}
guard let tempURL = tempURL else {
throw cantLoadError
}
let data = try Data(contentsOf: tempURL)
completionHandler(data, nil)
} catch {
+9 -5
View File
@@ -166,11 +166,15 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
}
}
open func storageProperties(completionHandler: (@escaping (_ total: Int64, _ used: Int64) -> Void)) {
let values = try? baseURL?.resourceValues(forKeys: [.volumeTotalCapacityKey, .volumeAvailableCapacityKey])
let totalSize = Int64(values??.volumeTotalCapacity ?? -1)
let freeSize = Int64(values??.volumeAvailableCapacity ?? 0)
completionHandler(totalSize, totalSize - freeSize)
public func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
dispatch_queue.async {
var keys: Set<URLResourceKey> = [.volumeTotalCapacityKey, .volumeAvailableCapacityKey, .volumeURLKey, .volumeNameKey, .volumeIsReadOnlyKey, .volumeCreationDateKey]
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) {
keys.insert(.isEncryptedKey)
}
let values: URLResourceValues? = self.baseURL.flatMap { try? $0.resourceValues(forKeys: keys) }
completionHandler(values.flatMap({ VolumeObject(allValues: $0.allValues) }))
}
}
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
+15 -10
View File
@@ -67,13 +67,13 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
return copy
}
open override func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
open override func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
list(path) { (contents, cursor, error) in
completionHandler(contents, error)
}
}
open override func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
open override func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
var request = URLRequest(url: url(of: path))
request.httpMethod = "GET"
request.set(httpAuthentication: credential, with: .oAuth2)
@@ -92,23 +92,28 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing {
task.resume()
}
open override func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
open override func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
var request = URLRequest(url: url(of: ""))
request.httpMethod = "GET"
request.set(httpAuthentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var totalSize: Int64 = -1
var usedSize: Int64 = 0
if let json = data?.deserializeJSON() {
totalSize = (json["total"] as? NSNumber)?.int64Value ?? -1
usedSize = (json["used"] as? NSNumber)?.int64Value ?? 0
guard let json = data?.deserializeJSON() else {
completionHandler(nil)
return
}
completionHandler(totalSize, usedSize)
let volume = VolumeObject(allValues: [:])
volume.url = request.url
volume.name = json["name"] as? String
volume.creationDate = (json["createdDateTime"] as? String).flatMap { Date(rfcString: $0) }
volume.totalCapacity = (json["quota"]?["total"] as? NSNumber)?.int64Value ?? -1
volume.availableCapacity = (json["quota"]?["remaining"] as? NSNumber)?.int64Value ?? 0
completionHandler(volume)
})
task.resume()
}
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
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
+20 -7
View File
@@ -34,35 +34,38 @@ public final class OneDriveFileObject: FileObject {
internal convenience init? (baseURL: URL?, drive: String, json: [String: AnyObject]) {
guard let name = json["name"] as? String else { return nil }
guard let path = (json["parentReference"] as? NSDictionary)?["path"] as? String else { return nil }
guard let path = json["parentReference"]?["path"] as? String else { return nil }
var lPath = path.replacingOccurrences(of: "/drive/\(drive):", with: "/", options: .anchored, range: nil)
lPath = lPath.replacingOccurrences(of: "/:", with: "", options: .anchored)
lPath = lPath.replacingOccurrences(of: "//", with: "", options: .anchored)
self.init(baseURL: baseURL, name: name, path: lPath)
self.size = (json["size"] as? NSNumber)?.int64Value ?? -1
self.modifiedDate = Date(rfcString: json["lastModifiedDateTime"] as? String ?? "")
self.creationDate = Date(rfcString: json["createdDateTime"] as? String ?? "")
self.modifiedDate = (json["lastModifiedDateTime"] as? String).flatMap { Date(rfcString: $0) }
self.creationDate = (json["createdDateTime"] as? String).flatMap { Date(rfcString: $0) }
self.type = json["folder"] != nil ? .directory : .regular
self.contentType = json["file"]?["mimeType"] as? String ?? "application/octet-stream"
self.id = json["id"] as? String
self.entryTag = json["eTag"] as? String
let hashes = json["file"]?["hashes"] as? NSDictionary
// checks for both sha1 or quickXor. First is available in personal drives, second in business one.
self.hash = (hashes?["sha1Hash"] as? String) ?? (hashes?["quickXorHash"] as? String)
}
/// The document identifier is a value assigned by the OneDrive to a file.
/// This value is used to identify the document regardless of where it is moved on a volume.
/// The identifier persists across system restarts.
open internal(set) var id: String? {
get {
return allValues[.documentIdentifierKey] as? String
return allValues[.fileResourceIdentifierKey] as? String
}
set {
allValues[.documentIdentifierKey] = newValue
allValues[.fileResourceIdentifierKey] = newValue
}
}
/// MIME type of file contents returned by OneDrive server.
open internal(set) var contentType: String {
get {
return allValues[.mimeTypeKey] as? String ?? ""
return allValues[.mimeTypeKey] as? String ?? "application/octet-stream"
}
set {
allValues[.mimeTypeKey] = newValue
@@ -78,6 +81,16 @@ public final class OneDriveFileObject: FileObject {
allValues[.entryTagKey] = newValue
}
}
/// Calculated hash from OneDrive server. Hex string SHA1 in personal or Base65 string [QuickXOR](https://dev.onedrive.com/snippets/quickxorhash.htm) in business drives.
open internal(set) var hash: String? {
get {
return allValues[.documentIdentifierKey] as? String
}
set {
allValues[.documentIdentifierKey] = newValue
}
}
}
// codebeat:disable[ARITY]
+3 -3
View File
@@ -56,15 +56,15 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
return true
}
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObjectClass], _ error: Error?) -> Void)) {
open func contentsOfDirectory(path: String, completionHandler: @escaping (_ contents: [FileObjectClass], _ error: Error?) -> Void) {
NotImplemented()
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObjectClass?, _ error: Error?) -> Void)) {
open func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObjectClass?, _ error: Error?) -> Void) {
NotImplemented()
}
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
open func storageProperties(completionHandler: @escaping (_ volume: VolumeObject?) -> Void) {
NotImplemented()
}
+39 -30
View File
@@ -22,6 +22,9 @@ import CoreGraphics
*/
open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
override open class var type: String { return "WebDAV" }
/// An enum which defines HTTP Authentication method, usually you should it default `.digest`.
/// If the server uses OAuth authentication, credential must be set with token as `password`, like Dropbox.
public var credentialType: URLRequest.AuthenticationType = .digest
/**
@@ -36,8 +39,8 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
if !["http", "https"].contains(baseURL.uw_scheme.lowercased()) {
return nil
}
let refinedBaseURL = (baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")).absoluteURL
super.init(baseURL: refinedBaseURL, credential: credential, cache: cache)
let refinedBaseURL = (baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent(""))
super.init(baseURL: refinedBaseURL.absoluteURL, credential: credential, cache: cache)
}
public required convenience init?(coder aDecoder: NSCoder) {
@@ -76,7 +79,7 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
- `contents`: An array of `FileObject` identifying the the directory entries.
- `error`: Error returned by system.
*/
open func contentsOfDirectory(path: String, including: [URLResourceKey], completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
open func contentsOfDirectory(path: String, including: [URLResourceKey], completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
let operation = FileOperationType.fetch(path: path)
let url = self.url(of: path).appendingPathComponent("")
var request = URLRequest(url: url)
@@ -84,8 +87,7 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
request.setValue("1", forHTTPHeaderField: "Depth")
request.set(httpAuthentication: credential, with: credentialType)
request.set(httpContentType: .xml, charset: .utf8)
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n\(WebDavFileObject.propString(including))\n</D:propfind>".data(using: .utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
request.httpBody = WebDavFileObject.xmlProp(including)
runDataTask(with: request, operation: operation, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
@@ -105,7 +107,7 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
})
}
override open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
override open func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
self.attributesOfItem(path: path, including: [], completionHandler: completionHandler)
}
@@ -120,15 +122,14 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
- `attributes`: A `FileObject` containing the attributes of the item.
- `error`: Error returned by system.
*/
open func attributesOfItem(path: String, including: [URLResourceKey], completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
open func attributesOfItem(path: String, including: [URLResourceKey], completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
let url = self.url(of: path)
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
request.setValue("0", forHTTPHeaderField: "Depth")
request.set(httpAuthentication: credential, with: credentialType)
request.set(httpContentType: .xml, charset: .utf8)
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n\(WebDavFileObject.propString(including))\n</D:propfind>".data(using: .utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
request.httpBody = WebDavFileObject.xmlProp(including)
runDataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode, code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
@@ -145,7 +146,7 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
})
}
override open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
override open func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
// Not all WebDAV clients implements RFC2518 which allows geting storage quota.
// In this case you won't get error. totalSize is NSURLSessionTransferSizeUnknown
// and used space is zero.
@@ -157,23 +158,24 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
request.setValue("0", forHTTPHeaderField: "Depth")
request.set(httpAuthentication: credential, with: credentialType)
request.set(httpContentType: .xml, charset: .utf8)
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop><D:quota-available-bytes/><D:quota-used-bytes/></D:prop>\n</D:propfind>".data(using: .utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
request.httpBody = WebDavFileObject.xmlProp([.volumeTotalCapacityKey, .volumeAvailableCapacityKey, .creationDateKey])
runDataTask(with: request, completionHandler: { (data, response, error) in
var totalSize: Int64 = -1
var usedSize: Int64 = 0
if let data = data {
let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
if let attr = xresponse.first {
totalSize = Int64(attr.prop["quota-available-bytes"] ?? "") ?? -1
usedSize = Int64(attr.prop["quota-used-bytes"] ?? "") ?? 0
}
guard let data = data, let attr = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL).first else {
completionHandler(nil)
return
}
completionHandler(totalSize, usedSize)
let volume = VolumeObject(allValues: [:])
volume.creationDate = attr.prop["creationdate"].flatMap { Date(rfcString: $0) }
volume.availableCapacity = attr.prop["quota-available-bytes"].flatMap({ Int64($0) }) ?? 0
if let usage = attr.prop["quota-used-bytes"].flatMap({ Int64($0) }) {
volume.totalCapacity = volume.availableCapacity + usage
}
completionHandler(volume)
})
}
override open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) -> Progress? {
override open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
let url = self.url(of: path)
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
@@ -181,7 +183,7 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
request.setValue(recursive ? "infinity" : "1", forHTTPHeaderField: "Depth")
request.set(httpAuthentication: credential, with: credentialType)
request.set(httpContentType: .xml, charset: .utf8)
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
request.httpBody = WebDavFileObject.xmlProp([])
let progress = Progress(parent: nil, userInfo: nil)
progress.setUserInfoObject(url, forKey: .fileURLKey)
let task = session.dataTask(with: request) { (data, response, error) in
@@ -222,8 +224,7 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
request.setValue("0", forHTTPHeaderField: "Depth")
request.set(httpAuthentication: credential, with: credentialType)
request.set(httpContentType: .xml, charset: .utf8)
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop><D:quota-available-bytes/><D:quota-used-bytes/></D:prop>\n</D:propfind>".data(using: .utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
request.httpBody = WebDavFileObject.xmlProp([.volumeTotalCapacityKey, .volumeAvailableCapacityKey])
runDataTask(with: request, completionHandler: { (data, response, error) in
let status = (response as? HTTPURLResponse)?.statusCode ?? 400
completionHandler(status < 300)
@@ -245,7 +246,6 @@ open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
request.set(httpContentType: .xml, charset: .utf8)
let body = "<propertyupdate xmlns=\"DAV:\">\n<set><prop>\n<public_url xmlns=\"urn:yandex:disk:meta\">true</public_url>\n</prop></set>\n</propertyupdate>"
request.httpBody = body.data(using: .utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
runDataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode, code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
@@ -497,9 +497,9 @@ public final class WebDavFileObject: FileObject {
let path = relativePath.hasPrefix("/") ? relativePath : ("/" + relativePath)
super.init(url: href, name: name, path: path)
self.size = Int64(davResponse.prop["getcontentlength"] ?? "-1") ?? NSURLSessionTransferSizeUnknown
self.creationDate = Date(rfcString: davResponse.prop["creationdate"] ?? "")
self.modifiedDate = Date(rfcString: davResponse.prop["getlastmodified"] ?? "")
self.contentType = davResponse.prop["getcontenttype"] ?? "octet/stream"
self.creationDate = davResponse.prop["creationdate"].flatMap { Date(rfcString: $0) }
self.modifiedDate = davResponse.prop["getlastmodified"].flatMap { Date(rfcString: $0) }
self.contentType = davResponse.prop["getcontenttype"] ?? "application/octet-stream"
self.isHidden = (Int(davResponse.prop["ishidden"] ?? "0") ?? 0) > 0
self.type = self.contentType == "httpd/unix-directory" ? .directory : .regular
self.entryTag = davResponse.prop["getetag"]
@@ -508,7 +508,7 @@ public final class WebDavFileObject: FileObject {
/// MIME type of the file.
open internal(set) var contentType: String {
get {
return allValues[.mimeTypeKey] as? String ?? ""
return allValues[.mimeTypeKey] as? String ?? "application/octet-stream"
}
set {
allValues[.mimeTypeKey] = newValue
@@ -539,6 +539,11 @@ public final class WebDavFileObject: FileObject {
return "ishidden"
case URLResourceKey.entryTagKey:
return "getetag"
case URLResourceKey.volumeTotalCapacityKey:
// WebDAV doesn't have total capacity, but it's can be calculated via used capacity
return "quota-used-bytes"
case URLResourceKey.volumeAvailableCapacityKey:
return "quota-available-bytes"
default:
return nil
}
@@ -556,6 +561,10 @@ public final class WebDavFileObject: FileObject {
}
return propKeys
}
internal class func xmlProp(_ keys: [URLResourceKey]) -> Data {
return "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n\(WebDavFileObject.propString(keys))\n</D:propfind>".data(using: .utf8)!
}
}
/// Error returned by WebDAV server when trying to access or do operations on a file or folder.