Compare commits

...

12 Commits

Author SHA1 Message Date
Amir Abbas f94719deb0 Fixes #39 (FTP listing), Error domain determination 2017-05-05 13:02:07 +04:30
Amir Abbas b13df0a977 Fixes #38 (Creating folder on WebDAV) 2017-05-05 10:06:17 +04:30
Amir Abbas 24af7aa4c2 Fixed LocalFileProvider init baseURL issue 2017-04-20 01:27:59 +04:30
Amir Abbas 06039ad993 CloudFileProvider operation handle improvements 2017-04-20 01:06:36 +04:30
Amir Abbas Mousavian d8fec3e346 Merge pull request #37 from hansvdam/master
Making `inProgress` of LocalFileProvider working
2017-04-18 14:22:08 +04:30
Hans van Dam 5c93bc8731 making 'inProgress' of LocalFileProvider work more consistently 2017-04-18 11:03:35 +02:00
Hans van Dam 61ba245189 making 'inProgress' of LocalFileProvider work 2017-04-17 18:16:04 +02:00
Amir Abbas 02e6cd37dd WebDAV OAuth 1,2 support 2017-04-16 19:19:17 +04:30
Amir Abbas 55608fb8d0 New FileProviderSharing protocol for publicLinks
- fixed DropboxFileProvider.propertiesOfFile() bug
- minor URLRequest refactors
- Added documentation and scope declaration
2017-04-16 19:07:45 +04:30
Amir Abbas 3e3582f6fa Changed DropboxFileProvider.type constant to “Dropbox” 2017-04-15 21:31:47 +04:30
Amir Abbas dd7a9d20b6 Fixed macOS build error 2017-04-14 23:26:40 +04:30
Amir Abbas d4a9b4a34f Fixed Dropbox thumbnail issue 2017-04-14 22:07:35 +04:30
17 changed files with 389 additions and 195 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
#
s.name = "FileProvider"
s.version = "0.16.0"
s.version = "0.16.2"
s.summary = "FileManager replacement for Local and Remote (WebDAV/FTP/Dropbox/OneDrive/SMB2) files on iOS and macOS."
# This description is used to generate tags and improve search results.
+2 -2
View File
@@ -621,7 +621,7 @@
799396601D48B7BF00086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.16.0;
BUNDLE_VERSION_STRING = 0.16.2;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
@@ -652,7 +652,7 @@
799396611D48B7BF00086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.16.0;
BUNDLE_VERSION_STRING = 0.16.2;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
+55 -32
View File
@@ -15,7 +15,7 @@ import Foundation
To setup a functional iCloud container, please
[read this page](https://medium.com/ios-os-x-development/icloud-drive-documents-1a46b5706fe1).
*/
open class CloudFileProvider: LocalFileProvider {
open class CloudFileProvider: LocalFileProvider, FileProviderSharing {
/// An string to identify type of provider.
open override class var type: String { return "iCloudDrive" }
@@ -374,8 +374,7 @@ open class CloudFileProvider: LocalFileProvider {
*/
@discardableResult
open override func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
guard let r = super.create(folder: folderName, at: atPath, completionHandler: completionHandler) else { return nil }
return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL)
return super.create(folder: folderName, at: atPath, completionHandler: completionHandler)
}
/**
@@ -392,8 +391,7 @@ open class CloudFileProvider: LocalFileProvider {
*/
@discardableResult
open override func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
guard let r = super.moveItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler) else { return nil }
return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL)
return super.moveItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler)
}
/**
@@ -410,8 +408,7 @@ open class CloudFileProvider: LocalFileProvider {
*/
@discardableResult
open override func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
guard let r = super.copyItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler) else { return nil }
return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL)
return super.copyItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler)
}
/**
@@ -429,8 +426,7 @@ open class CloudFileProvider: LocalFileProvider {
*/
@discardableResult
open override func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
guard let r = super.removeItem(path: path, completionHandler: completionHandler) else { return nil }
return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL)
return super.removeItem(path: path, completionHandler: completionHandler)
}
/**
@@ -448,6 +444,7 @@ open class CloudFileProvider: LocalFileProvider {
open override func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
// TODO: Make use of overwrite parameter
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
let operationHandle = CloudOperationHandle(operationType: opType, baseURL: self.baseURL)
operation_queue.addOperation {
let tempFolder: URL
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) {
@@ -475,7 +472,7 @@ open class CloudFileProvider: LocalFileProvider {
})
}
}
return CloudOperationHandle(operationType: opType, baseURL: self.baseURL)
return operationHandle
}
/**
@@ -501,9 +498,8 @@ open class CloudFileProvider: LocalFileProvider {
})
return nil
}
guard let r = super.copyItem(path: path, toLocalURL: toLocalURL, completionHandler: completionHandler) else { return nil }
return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL)
let handle = super.copyItem(path: path, toLocalURL: toLocalURL, completionHandler: completionHandler)
return handle
}
/**
@@ -519,8 +515,7 @@ open class CloudFileProvider: LocalFileProvider {
*/
@discardableResult
open override func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
guard let r = super.contents(path: path, completionHandler: completionHandler) else { return nil }
return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL)
return super.contents(path: path, completionHandler: completionHandler)
}
/**
@@ -538,8 +533,7 @@ open class CloudFileProvider: LocalFileProvider {
*/
@discardableResult
open override func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
guard let r = super.contents(path: path, offset: offset, length: length, completionHandler: completionHandler) else { return nil }
return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL)
return super.contents(path: path, offset: offset, length: length, completionHandler: completionHandler)
}
/**
@@ -555,8 +549,7 @@ open class CloudFileProvider: LocalFileProvider {
*/
@discardableResult
open override func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
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)
return super.writeContents(path: path, contents: data, atomically: atomically, overwrite: overwrite, completionHandler: completionHandler)
}
fileprivate var monitors = [String: (NSMetadataQuery, NSObjectProtocol)]()
@@ -641,6 +634,48 @@ open class CloudFileProvider: LocalFileProvider {
return file
}
func monitorFile(path: String, opType: FileOperationType) {
dispatch_queue.async {
let pathURL = self.url(of: path)
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "%K LIKE %@", NSMetadataItemPathKey, pathURL.path)
query.valueListAttributes = [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataUbiquitousItemPercentDownloadedKey, NSMetadataUbiquitousItemPercentUploadedKey, NSMetadataItemFSSizeKey]
query.searchScopes = [self.scope.rawValue]
var updateObserver: NSObjectProtocol?
updateObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
query.disableUpdates()
guard let item = (query.results as? [NSMetadataItem])?.first else {
return
}
let downloaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? Double ?? 0
let uploaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentUploadedKey) as? Double ?? 0
if (downloaded == 0 || downloaded == 100) && (uploaded > 0 && uploaded < 100) {
DispatchQueue.main.async {
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(uploaded / 100))
}
} else if (uploaded == 0 || uploaded == 100) && (downloaded > 0 && downloaded < 100) {
DispatchQueue.main.async {
self.delegate?.fileproviderProgress(self, operation: opType, progress: Float(downloaded / 100))
}
} else if uploaded == 100 || downloaded == 100 {
query.stop()
NotificationCenter.default.removeObserver(updateObserver!)
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
}
}
query.enableUpdates()
})
DispatchQueue.main.async {
query.start()
}
}
}
/// Removes local copy of file, but spares cloud copy/
/// - Parameter path: Path of file or directory to be remoed from local
/// - Parameter completionHandler: If an error parameter was provided, a presentable `Error` will be returned.
@@ -655,19 +690,6 @@ open class CloudFileProvider: LocalFileProvider {
}
}
/**
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, determined in `expiration` argument.
- Parameters:
- to: path of file, including file/directory name.
- completionHandler: a closure 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: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
operation_queue.addOperation {
do {
@@ -685,6 +707,7 @@ open class CloudFileProvider: LocalFileProvider {
}
}
/// Scope of iCloud, wrapper for NSMetadataQueryUbiquitous...Scope constants
public enum UbiquitousScope: RawRepresentable {
/// Search all files not in the Documents directories of the apps iCloud container directories.
/// Use this scope to store user-related data files that your app needs to share
+44 -49
View File
@@ -17,7 +17,7 @@ import CoreGraphics
- Note: Uploading files and data are limited to 150MB, for now.
*/
open class DropboxFileProvider: FileProviderBasicRemote {
open class var type: String { return "DropBox" }
open class var type: String { return "Dropbox" }
open let baseURL: URL?
open var currentPath: String
@@ -157,8 +157,8 @@ open class DropboxFileProvider: FileProviderBasicRemote {
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
@@ -180,7 +180,7 @@ open class DropboxFileProvider: FileProviderBasicRemote {
let url = URL(string: "users/get_space_usage", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
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
@@ -268,8 +268,8 @@ extension DropboxFileProvider: FileProviderOperations {
}
var request = URLRequest(url: URL(string: url, relativeTo: apiURL)!)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(contentType: .json)
var requestDictionary = [String: AnyObject]()
if let dest = correctPath(destPath) as NSString? {
requestDictionary["from_path"] = correctPath(sourcePath) as NSString?
@@ -314,10 +314,8 @@ extension DropboxFileProvider: FileProviderOperations {
}
let url = URL(string: "files/download", relativeTo: contentURL)!
var request = URLRequest(url: url)
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
let requestDictionary: [String: AnyObject] = ["path": path as NSString]
let requestJson = String(jsonDictionary: requestDictionary) ?? ""
request.setValue(requestJson, forHTTPHeaderField: "Dropbox-API-Arg")
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(dropboxArgKey: ["path": path as NSString])
let task = session.downloadTask(with: request)
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = completionHandler
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
@@ -353,14 +351,9 @@ extension DropboxFileProvider: FileProviderReadWrite {
let opType = FileOperationType.fetch(path: path)
let url = URL(string: "files/download", relativeTo: contentURL)!
var request = URLRequest(url: url)
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + Int64(length) - 1)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString]
request.setValue(String(jsonDictionary: requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(rangeWithOffset: offset, length: length)
request.set(dropboxArgKey: ["path": correctPath(path)! as NSString])
let task = session.downloadTask(with: request)
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
completionHandler(nil, error)
@@ -411,26 +404,13 @@ extension DropboxFileProvider: FileProviderReadWrite {
// TODO: Implement /get_account & /get_current_account
}
extension DropboxFileProvider {
/**
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 closure 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)) {
extension DropboxFileProvider: FileProviderSharing {
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
let url = URL(string: "files/get_temporary_link", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
@@ -475,8 +455,8 @@ extension DropboxFileProvider {
let url = URL(string: "files/save_url", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(toPath)! as NSString, "url" : remoteURL.absoluteString as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
@@ -510,8 +490,8 @@ extension DropboxFileProvider {
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")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(toPath)! as NSString, "copy_reference" : reference as NSString]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
@@ -559,26 +539,37 @@ extension DropboxFileProvider: ExtendedFileProvider {
/// Default value for dimension is 64x64, according to Dropbox documentation
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let url: URL
let thumbAPI: Bool
switch (path as NSString).pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
url = URL(string: "files/get_thumbnail", relativeTo: contentURL)!
thumbAPI = true
case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
fallthrough
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
fallthrough
case "rtf":
url = URL(string: "files/get_preview", relativeTo: contentURL)!
thumbAPI = false
default:
return
}
var request = URLRequest(url: url)
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.set(httpAuthentication: credential, with: .oAuth2)
var requestDictionary: [String: AnyObject] = ["path": path as NSString]
requestDictionary["format"] = "jpeg" as NSString
if let dimension = dimension {
requestDictionary["size"] = "w\(Int(dimension.width))h\(Int(dimension.height))" as NSString
if thumbAPI {
requestDictionary["format"] = "jpeg" as NSString
let size: String
switch dimension?.height ?? 64 {
case 0...32: size = "w32h32"
case 33...64: size = "w64h64"
case 65...128: size = "w128h128"
case 129...480: size = "w640h480"
default: size = "w1024h768"
}
requestDictionary["size"] = size as NSString
}
request.setValue(String(jsonDictionary: requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
request.set(dropboxArgKey: requestDictionary)
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
var image: ImageClass? = nil
if let r = response as? HTTPURLResponse, let result = r.allHeaderFields["Dropbox-API-Result"] as? String, let jsonResult = result.deserializeJSON() {
@@ -591,8 +582,12 @@ extension DropboxFileProvider: ExtendedFileProvider {
image = pageImage
} else if let contentType = (response as? HTTPURLResponse)?.allHeaderFields["Content-Type"] as? String, contentType.contains("text/html") {
// TODO: Implement converting html returned type of get_preview to image
} else {
image = ImageClass(data: data)
} else if let fetchedimage = ImageClass(data: data){
if let dimension = dimension {
image = DropboxFileProvider.scaleDown(image: fetchedimage, toSize: dimension)
} else {
image = fetchedimage
}
}
}
completionHandler(image, error)
@@ -604,8 +599,8 @@ extension DropboxFileProvider: ExtendedFileProvider {
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(contentType: .json)
let requestDictionary: [String: AnyObject] = ["path": correctPath(path)! as NSString, "include_media_info": NSNumber(value: true)]
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
@@ -615,7 +610,7 @@ extension DropboxFileProvider: ExtendedFileProvider {
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
serverError = code != nil ? FileProviderDropboxError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil
if let json = data?.deserializeJSON(), let properties = json["media_info"] as? [String: Any] {
if let json = data?.deserializeJSON(), let properties = (json["media_info"] as? [String: Any])?["metadata"] as? [String: Any] {
(dic, keys) = self.mapMediaInfo(properties)
}
}
+7 -7
View File
@@ -84,8 +84,8 @@ internal extension DropboxFileProvider {
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(contentType: .json)
request.httpBody = Data(jsonDictionary: requestDictionary)
let task = (session ?? self.session).dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderDropboxError?
@@ -133,9 +133,9 @@ internal extension DropboxFileProvider {
requestDictionary["client_modified"] = modifiedDate.rfc3339utc() as NSString
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
request.setValue(String(jsonDictionary: requestDictionary), forHTTPHeaderField: "Dropbox-API-Arg")
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(contentType: .stream)
request.set(dropboxArgKey: requestDictionary)
let task: URLSessionUploadTask
if let data = data {
task = session.uploadTask(with: request, from: data)
@@ -163,8 +163,8 @@ internal extension DropboxFileProvider {
let url = URL(string: "files/search", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(contentType: .json)
var requestDictionary: [String: AnyObject] = ["path": startPath as NSString]
requestDictionary["query"] = query as NSString
requestDictionary["start"] = start as NSNumber
+4 -4
View File
@@ -12,7 +12,7 @@ import CoreGraphics
import AVFoundation
extension LocalFileProvider: ExtendedFileProvider {
public func thumbnailOfFileSupported(path: String) -> Bool {
open func thumbnailOfFileSupported(path: String) -> Bool {
switch (path as NSString).pathExtension.lowercased() {
case LocalFileInformationGenerator.imageThumbnailExtensions:
return true
@@ -31,7 +31,7 @@ extension LocalFileProvider: ExtendedFileProvider {
}
}
public func propertiesOfFileSupported(path: String) -> Bool {
open func propertiesOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
switch fileExt {
case LocalFileInformationGenerator.imagePropertiesExtensions:
@@ -54,7 +54,7 @@ extension LocalFileProvider: ExtendedFileProvider {
}
}
public func thumbnailOfFile(path: String, dimension: CGSize? = nil, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
open func thumbnailOfFile(path: String, dimension: CGSize? = nil, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let dimension = dimension ?? CGSize(width: 64, height: 64)
(dispatch_queue).async {
var thumbnailImage: ImageClass? = nil
@@ -86,7 +86,7 @@ extension LocalFileProvider: ExtendedFileProvider {
}
}
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void)) {
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String: Any], _ keys: [String], _ error: Error?) -> Void)) {
(dispatch_queue).async {
let fileExt = (path as NSString).pathExtension.lowercased()
var getter: ((_ fileURL: URL) -> (prop: [String: Any], keys: [String]))?
+10 -5
View File
@@ -140,7 +140,7 @@ open class FTPFileProvider: FileProviderBasicRemote {
}
}
public func contentsOfDirectory(path: String, completionHandler: @escaping (([FileObject], Error?) -> Void)) {
open func contentsOfDirectory(path: String, completionHandler: @escaping (([FileObject], Error?) -> Void)) {
self.contentsOfDirectory(path: path, rfc3659enabled: true, completionHandler: completionHandler)
}
@@ -173,6 +173,11 @@ open class FTPFileProvider: FileProviderBasicRemote {
self.ftpQuit(task)
}
if let error = error {
if ((error as NSError).domain == URLError.errorDomain && (error as NSError).code == URLError.unsupportedURL.rawValue) {
self.contentsOfDirectory(path: path, rfc3659enabled: false, completionHandler: completionHandler)
return
}
self.dispatch_queue.async {
completionHandler([], error)
}
@@ -191,7 +196,7 @@ open class FTPFileProvider: FileProviderBasicRemote {
}
}
public func attributesOfItem(path: String, completionHandler: @escaping ((FileObject?, Error?) -> Void)) {
open func attributesOfItem(path: String, completionHandler: @escaping ((FileObject?, Error?) -> Void)) {
self.attributesOfItem(path: path, rfc3659enabled: true, completionHandler: completionHandler)
}
@@ -642,7 +647,7 @@ extension FTPFileProvider: FileProviderOperations {
}
extension FTPFileProvider: FileProviderReadWrite {
public func contents(path: String, completionHandler: @escaping ((Data?, Error?) -> Void)) -> OperationHandle? {
open func contents(path: String, completionHandler: @escaping ((Data?, Error?) -> Void)) -> OperationHandle? {
let opType = FileOperationType.fetch(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
@@ -715,7 +720,7 @@ extension FTPFileProvider: FileProviderReadWrite {
return operation
}
public func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open func writeContents(path: String, contents data: Data?, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.modify(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
@@ -763,7 +768,7 @@ extension FTPFileProvider: FileProviderReadWrite {
}
}
extension FTPFileProvider {
public extension FTPFileProvider {
/**
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 destination path, making it possible
+8 -5
View File
@@ -8,7 +8,7 @@
import Foundation
extension FTPFileProvider {
internal extension FTPFileProvider {
func delegateNotify(_ operation: FileOperationType, error: Error?) {
DispatchQueue.main.async(execute: {
if error == nil {
@@ -297,7 +297,7 @@ extension FTPFileProvider {
var success = false
let command = useMLST ? "MLSD \(path)" : "LIST \(path)"
self.execute(command: command, on: task, minLength: 70, afterSend: { error in
self.execute(command: command, on: task, minLength: 20, afterSend: { error in
// starting passive task
let timeout = self.session.configuration.timeoutIntervalForRequest
@@ -308,7 +308,7 @@ extension FTPFileProvider {
while !eof {
let group = DispatchGroup()
group.enter()
dataTask.readData(ofMinLength: 0, maxLength: 65535, timeout: timeout, completionHandler: { (data, seof, serror) in
dataTask.readData(ofMinLength: 1, maxLength: 65535, timeout: timeout, completionHandler: { (data, seof, serror) in
if let data = data {
finalData.append(data)
}
@@ -319,7 +319,9 @@ extension FTPFileProvider {
let waitResult = group.wait(timeout: .now() + timeout)
if let error = error {
completionHandler([], error)
if !((error as NSError).domain == URLError.errorDomain && (error as NSError).code == URLError.cancelled.rawValue) {
completionHandler([], error)
}
return
}
@@ -351,7 +353,8 @@ extension FTPFileProvider {
}
if response.hasPrefix("50") && useMLST {
self.ftpList(task, of: path, useMLST: false, completionHandler: completionHandler)
dataTask.cancel()
completionHandler([], self.throwError(path, code: URLError.unsupportedURL))
return
}
+1 -1
View File
@@ -13,7 +13,7 @@ open class FileObject: Equatable {
/// A `Dictionary` contains file information, using `URLResourceKey` keys.
open internal(set) var allValues: [URLResourceKey: Any]
internal init(allValues: [URLResourceKey: Any]) {
public init(allValues: [URLResourceKey: Any]) {
self.allValues = allValues
}
+27 -5
View File
@@ -611,6 +611,25 @@ public extension FileProvideUndoable {
}
}
/// This protocol defines method to share a public link with other users
public protocol FileProviderSharing {
/**
Genrates a public url to a file to be shared with other users and can be downloaded without authentication.
- Important: In some providers url will be available for a limitied time, determined in `expiration` argument.
e.g. Dropbox links will be expired after 4 hours.
- Parameters:
- to: path of file, including file/directory name.
- completionHandler: a closure 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 server.
*/
func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void))
}
/// Defines protocol for provider allows all common operations.
public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite, NSCopying {
}
@@ -691,7 +710,7 @@ extension FileProviderBasic {
}
var i = number ?? 2
let similiar = contents.map {
$0.url.lastPathComponent ?? $0.name
$0.url.lastPathComponent.isEmpty ? $0.name : $0.url.lastPathComponent
}.filter {
$0.hasPrefix(result)
}
@@ -711,6 +730,8 @@ extension FileProviderBasic {
let domain: String
switch code {
case is URLError:
fallthrough
case is URLError.Code:
domain = NSURLErrorDomain
default:
domain = NSCocoaErrorDomain
@@ -842,19 +863,20 @@ extension ExtendedFileProvider {
return resultingImage
#else
let ppp = Int(UIScreen.main.scale) // fetch device is retina or not
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
size.width *= CGFloat(ppp)
size.height *= CGFloat(ppp)
UIGraphicsBeginImageContext(size)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.saveGState()
let transform = pdfPage.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true)
context.concatenate(transform)
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: CGFloat(ppp), y: CGFloat(-ppp))
context.setFillColor(UIColor.white.cgColor)
context.fill(rect)
context.drawPDFPage(pdfPage)
context.restoreGState()
+68 -19
View File
@@ -8,7 +8,7 @@
import Foundation
extension Array where Element: FileObject {
public extension Array where Element: FileObject {
/// Returns a sorted array of `FileObject`s by criterias set in attributes.
public func sort(by type: FileObjectSorting.SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) -> [Element] {
let sorting = FileObjectSorting(type: type, ascending: ascending, isDirectoriesFirst: isDirectoriesFirst)
@@ -21,8 +21,8 @@ extension Array where Element: FileObject {
}
}
extension URLFileResourceType {
/// Returns corresponding `URLFileResourceType` of a `FileAttributeType` value
public extension URLFileResourceType {
/// **FileProvider** returns corresponding `URLFileResourceType` of a `FileAttributeType` value
public init(fileTypeValue: FileAttributeType) {
switch fileTypeValue {
case FileAttributeType.typeCharacterSpecial: self = .characterSpecial
@@ -37,20 +37,17 @@ extension URLFileResourceType {
}
}
internal extension URLResourceKey {
static let fileURLKey = URLResourceKey(rawValue: "NSURLFileURLKey")
static let serverDateKey = URLResourceKey(rawValue: "NSURLServerDateKey")
static let entryTagKey = URLResourceKey(rawValue: "NSURLEntryTagKey")
static let mimeTypeKey = URLResourceKey(rawValue: "NSURLMIMETypeIdentifierKey")
@available(*, deprecated, renamed: "fileURLKey")
static let fileURL = fileURLKey
@available(*, deprecated, renamed: "serverDateKey")
static let serverDate = serverDateKey
@available(*, deprecated, renamed: "entryTagKey")
static let entryTag = entryTagKey
@available(*, deprecated, renamed: "mimeTypeKey")
static let mimeType = mimeTypeKey
public extension URLResourceKey {
/// **FileProvider** returns url of file object.
public static let fileURLKey = URLResourceKey(rawValue: "NSURLFileURLKey")
/// **FileProvider** returns modification date of file in server
public static let serverDateKey = URLResourceKey(rawValue: "NSURLServerDateKey")
/// **FileProvider** returns HTTP ETag string of remote resource
public static let entryTagKey = URLResourceKey(rawValue: "NSURLEntryTagKey")
/// **FileProvider** returns MIME type of file, if returned by server
public static let mimeTypeKey = URLResourceKey(rawValue: "NSURLMIMETypeIdentifierKey")
/// **FileProvider** returns either file is encrypted or not
public static let isEncryptedKey = URLResourceKey(rawValue: "NSURLIsEncryptedKey")
}
internal extension URL {
@@ -71,6 +68,58 @@ internal extension URL {
}
}
internal extension URLRequest {
mutating func set(httpAuthentication credential: URLCredential?, with type: HTTPAuthenticationType) {
func base64(_ str: String) -> String {
let plainData = str.data(using: .utf8)
let base64String = plainData!.base64EncodedString(options: [])
return base64String
}
guard let credential = credential else { return }
switch type {
case .basic:
let authStr = "\(credential.user ?? ""):\(credential.password ?? "")"
self.setValue("Basic \(authStr)", forHTTPHeaderField: "Authorization")
case .digest:
// handled by RemoteSessionDelegate
break
case .oAuth1:
if let oauth = credential.password {
self.setValue("OAuth \(oauth)", forHTTPHeaderField: "Authorization")
}
case .oAuth2:
if let bearer = credential.password {
self.setValue("Bearer \(bearer)", forHTTPHeaderField: "Authorization")
}
}
}
mutating func set(rangeWithOffset offset: Int64, length: Int) {
if length > 0 {
self.setValue("bytes=\(offset)-\(offset + Int64(length) - 1)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
self.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
}
enum ContentType: String {
case json = "application/json"
case stream = "application/octet-stream"
case xml = "text/xml; charset=\"utf-8\""
}
mutating func set(contentType: ContentType) {
self.setValue(contentType.rawValue, forHTTPHeaderField: "Content-Type")
}
mutating func set(dropboxArgKey requestDictionary: [String: AnyObject]) {
if let requestJson = String(jsonDictionary: requestDictionary) {
self.setValue(requestJson, forHTTPHeaderField: "Dropbox-API-Arg")
}
}
}
internal extension Data {
internal var isPDF: Bool {
return self.count > 4 && self.scanString(length: 4, using: .ascii) == "%PDF"
@@ -160,7 +209,7 @@ internal extension TimeInterval {
}
}
extension Date {
internal extension Date {
init?(rfcString: String) {
let dateFor: DateFormatter = DateFormatter()
dateFor.locale = Locale(identifier: "en_US")
@@ -197,7 +246,7 @@ extension Date {
}
}
extension NSPredicate {
internal extension NSPredicate {
func findValue(forKey key: String?, operator op: NSComparisonPredicate.Operator? = nil) -> Any? {
let val = findAllValues(forKey: key).lazy.filter { (op == nil || $0.operator == op!) && !$0.not }
return val.first?.value
+22 -13
View File
@@ -98,7 +98,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
guard baseURL.isFileURL else {
fatalError("Cannot initialize a Local provider from remote URL.")
}
self.baseURL = baseURL
self.baseURL = (baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")).absoluteURL
self.currentPath = ""
self.credential = nil
self.isCoorinating = false
@@ -259,7 +259,8 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
@discardableResult
fileprivate func doOperation(_ opType: FileOperationType, data: Data? = nil, atomically: Bool = false, forUploading: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let localOperationHandle = LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
func urlofpath(path: String) -> URL {
if path.hasPrefix("file://") {
let removedSchemePath = path.replacingOccurrences(of: "file://", with: "", options: .anchored)
@@ -293,6 +294,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
let operationHandler: (URL, URL?) -> Void = { source, dest in
do {
localOperationHandle.inProgress = true
switch opType {
case .create:
if sourcePath.hasSuffix("/") {
@@ -316,7 +318,8 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
if successfulSecurityScopedResourceAccess {
source.stopAccessingSecurityScopedResource()
}
localOperationHandle.inProgress = false
self.dispatch_queue.async {
completionHandler?(nil)
}
@@ -368,18 +371,20 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
operationHandler(source, dest)
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
return localOperationHandle
}
@discardableResult
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
let opType = FileOperationType.fetch(path: path)
let localOperationHandle = LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
let url = self.url(of: path)
let operationHandler: (URL) -> Void = { url in
do {
localOperationHandle.inProgress = true
let data = try Data(contentsOf: url)
localOperationHandle.inProgress = false
self.dispatch_queue.async {
completionHandler(data, nil)
}
@@ -405,8 +410,8 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
operationHandler(url)
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
return localOperationHandle
}
@discardableResult
@@ -417,12 +422,13 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
}
return nil
}
if offset == 0 && length < 0 {
return self.contents(path: path, completionHandler: completionHandler)
}
let opType = FileOperationType.fetch(path: path)
let localOperationHandle = LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
let url = self.url(of: path)
let operationHandler: (URL) -> Void = { url in
@@ -436,9 +442,11 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
defer {
handle.closeFile()
}
localOperationHandle.inProgress = true
let size = LocalFileObject(fileWithURL: url)?.size ?? -1
guard size > offset else {
localOperationHandle.inProgress = false
self.dispatch_queue.async {
completionHandler(nil, self.throwError(path, code: CocoaError.fileReadTooLarge as FoundationErrorEnum))
}
@@ -446,6 +454,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
}
handle.seek(toFileOffset: UInt64(offset))
guard Int64(handle.offsetInFile) == offset else {
localOperationHandle.inProgress = false
self.dispatch_queue.async {
completionHandler(nil, self.throwError(path, code: CocoaError.fileReadTooLarge as FoundationErrorEnum))
}
@@ -453,7 +462,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
}
let data = handle.readData(ofLength: length)
localOperationHandle.inProgress = false
self.dispatch_queue.async {
completionHandler(data, nil)
}
@@ -473,7 +482,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
return localOperationHandle
}
@discardableResult
+4 -4
View File
@@ -227,6 +227,7 @@ open class LocalOperationHandle: OperationHandle {
init (operationType: FileOperationType, baseURL: URL?) {
self.baseURL = baseURL ?? URL(fileURLWithPath: "/")
self.operationType = operationType
inProgress = false
}
private var sourceURL: URL? {
@@ -280,10 +281,9 @@ open class LocalOperationHandle: OperationHandle {
}
/// Not usable in local provider
open var inProgress: Bool {
return false
}
open var inProgress: Bool
/// Not usable in local provider
open func cancel() -> Bool{
return false
+15 -28
View File
@@ -87,7 +87,7 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
*/
public init(credential: URLCredential?, serverURL: URL? = nil, drive: String = "root", cache: URLCache? = nil) {
let baseURL = serverURL?.absoluteURL ?? URL(string: "https://api.onedrive.com/")!
self.baseURL = baseURL.path.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")
self.baseURL = baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")
self.drive = drive
self.currentPath = ""
self.useCache = false
@@ -152,7 +152,7 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
var request = URLRequest(url: url(of: path))
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.set(httpAuthentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderOneDriveError?
var fileObject: OneDriveFileObject?
@@ -171,7 +171,7 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
var request = URLRequest(url: url())
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
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
@@ -228,7 +228,7 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
var request = URLRequest(url: url())
request.httpMethod = "HEAD"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.set(httpAuthentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
let status = (response as? HTTPURLResponse)?.statusCode ?? 400
completionHandler(status == 200)
@@ -279,10 +279,10 @@ extension OneDriveFileProvider: FileProviderOperations {
return nil
}
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.set(httpAuthentication: credential, with: .oAuth2)
var requestDictionary = [String: AnyObject]()
if let dest = correctPath(destPath) as NSString? {
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.set(contentType: .json)
requestDictionary["parentReference"] = ("/drive/\(drive):" + dest.deletingLastPathComponent) as NSString
requestDictionary["name"] = dest.lastPathComponent as NSString
request.httpBody = Data(jsonDictionary: requestDictionary)
@@ -322,7 +322,7 @@ extension OneDriveFileProvider: FileProviderOperations {
return nil
}
var request = URLRequest(url: self.url(of: path, modifier: "content"))
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.set(httpAuthentication: credential, with: .oAuth2)
let task = session.downloadTask(with: request)
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = completionHandler
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
@@ -358,12 +358,8 @@ extension OneDriveFileProvider: FileProviderReadWrite {
let opType = FileOperationType.fetch(path: path)
var request = URLRequest(url: self.url(of: path, modifier: "content"))
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + Int64(length) - 1)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(rangeWithOffset: offset, length: length)
let task = session.downloadTask(with: request)
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { error in
completionHandler(nil, error)
@@ -409,19 +405,10 @@ extension OneDriveFileProvider: FileProviderReadWrite {
fileprivate func unregisterNotifcation(path: String) {
NotImplemented()
}
/**
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 closure 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)) {
}
extension OneDriveFileProvider: FileProviderSharing {
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
var request = URLRequest(url: self.url(of: path, modifier: "action.createLink"))
request.httpMethod = "POST"
let requestDictionary: [String: AnyObject] = ["type": "view" as NSString]
@@ -474,7 +461,7 @@ extension OneDriveFileProvider: ExtendedFileProvider {
}
var request = URLRequest(url: url)
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.set(httpAuthentication: credential, with: .oAuth2)
let task = self.session.dataTask(with: request, completionHandler: { (data, response, error) in
var image: ImageClass? = nil
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
@@ -493,7 +480,7 @@ extension OneDriveFileProvider: ExtendedFileProvider {
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
var request = URLRequest(url: url(of: path))
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.set(httpAuthentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var serverError: FileProviderOneDriveError?
var dic = [String: Any]()
+4 -4
View File
@@ -85,7 +85,7 @@ internal extension OneDriveFileProvider {
let url = cursor ?? self.url(of: path, modifier: "children")
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.set(httpAuthentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderOneDriveError?
var files = prevContents
@@ -125,8 +125,8 @@ internal extension OneDriveFileProvider {
let url = self.url(of: targetPath, modifier: "content\(queryStr)")
var request = URLRequest(url: url)
request.httpMethod = "PUT"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: .oAuth2)
request.set(contentType: .stream)
let task: URLSessionUploadTask
if let data = data {
task = session.uploadTask(with: request, from: data)
@@ -156,7 +156,7 @@ internal extension OneDriveFileProvider {
url = next ?? self.url(of: startPath, modifier: "view.search?q=\(q)")
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.set(httpAuthentication: credential, with: .oAuth2)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderOneDriveError?
+13 -1
View File
@@ -90,6 +90,18 @@ extension FileProviderHTTPError {
}
}
/// Defines HTTP Authentication method required to access
public enum HTTPAuthenticationType {
/// Basic method for authentication
case basic
/// Digest method for authentication
case digest
/// OAuth 1.0 method for authentication (OAuth)
case oAuth1
/// OAuth 2.0 method for authentication (Bearer)
case oAuth2
}
internal var completionHandlersForTasks = [String: [Int: SimpleCompletionHandler]]()
internal var downloadCompletionHandlersForTasks = [String: [Int: (URL) -> Void]]()
internal var dataCompletionHandlersForTasks = [String: [Int: (Data) -> Void]]()
@@ -142,7 +154,7 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession
if !(task is URLSessionDownloadTask), case FileOperationType.fetch = op {
return
}
if #available(iOSApplicationExtension 9.0, *) {
if #available(iOS 9.0, macOS 10.11, *) {
if task is URLSessionStreamTask {
return
}
+104 -15
View File
@@ -7,12 +7,13 @@
//
import Foundation
import CoreGraphics
/**
Allows accessing to WebDAV server files. This provider doesn't cache or save files internally, however you can
set `useCache` and `cache` properties to use Foundation `NSURLCache` system.
WebDAV system supported by many cloud services including [Box.net](https://www.box.com/home)
WebDAV system supported by many cloud services including [Box.com](https://www.box.com/home)
and [Yandex disk](https://disk.yandex.com) and [ownCloud](https://owncloud.org).
- Important: Because this class uses `URLSession`, it's necessary to disable App Transport Security
@@ -32,6 +33,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
}
public weak var delegate: FileProviderDelegate?
public var credentialType: HTTPAuthenticationType = .digest
open var credential: URLCredential? {
didSet {
sessionDelegate?.credential = credential
@@ -82,7 +84,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
if !["http", "https"].contains(baseURL.uw_scheme.lowercased()) {
return nil
}
self.baseURL = (baseURL.path.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")).absoluteURL
self.baseURL = (baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("")).absoluteURL
self.currentPath = ""
self.useCache = false
self.validatingCache = true
@@ -159,7 +161,8 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
request.setValue("1", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: credentialType)
request.set(contentType: .xml)
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n\(WebDavFileObject.propString(including))\n</D:propfind>".data(using: .utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
runDataTask(with: request, operationHandle: RemoteOperationHandle(operationType: opType, tasks: []), completionHandler: { (data, response, error) in
@@ -201,7 +204,8 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
request.setValue("1", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: credentialType)
request.set(contentType: .xml)
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n\(WebDavFileObject.propString(including))\n</D:propfind>".data(using: .utf8)
request.setValue(String(request.httpBody!.count), forHTTPHeaderField: "Content-Length")
runDataTask(with: request, completionHandler: { (data, response, error) in
@@ -230,7 +234,8 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
var request = URLRequest(url: baseURL)
request.httpMethod = "PROPFIND"
request.setValue("0", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: credentialType)
request.set(contentType: .xml)
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")
runDataTask(with: request, completionHandler: { (data, response, error) in
@@ -252,7 +257,8 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
//request.setValue("1", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: credentialType)
request.set(contentType: .xml)
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
runDataTask(with: request, completionHandler: { (data, response, error) in
// FIXME: paginating results
@@ -283,7 +289,8 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
var request = URLRequest(url: baseURL!)
request.httpMethod = "PROPFIND"
request.setValue("0", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.set(httpAuthentication: credential, with: credentialType)
request.set(contentType: .xml)
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")
runDataTask(with: request, completionHandler: { (data, response, error) in
@@ -302,9 +309,10 @@ extension WebDAVFileProvider: FileProviderOperations {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
let url = self.url(of: atPath).appendingPathComponent(folderName.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? folderName, isDirectory: true)
let url = self.url(of: atPath).appendingPathComponent(folderName, isDirectory: true)
var request = URLRequest(url: url)
request.httpMethod = "MKCOL"
request.set(httpAuthentication: credential, with: credentialType)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
@@ -345,7 +353,7 @@ extension WebDAVFileProvider: FileProviderOperations {
return self.doOperation(operation: opType, completionHandler: completionHandler)
}
func doOperation(operation opType: FileOperationType, overwrite: Bool? = nil, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
fileprivate func doOperation(operation opType: FileOperationType, overwrite: Bool? = nil, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let source = opType.source!
let sourceURL = self.url(of: source)
var request = URLRequest(url: sourceURL)
@@ -363,6 +371,7 @@ extension WebDAVFileProvider: FileProviderOperations {
return nil
}
request.set(httpAuthentication: credential, with: credentialType)
if let overwrite = overwrite, !overwrite {
request.setValue("F", forHTTPHeaderField: "Overwrite")
}
@@ -411,6 +420,7 @@ extension WebDAVFileProvider: FileProviderOperations {
request.setValue("F", forHTTPHeaderField: "Overwrite")
}
request.httpMethod = "PUT"
request.set(httpAuthentication: credential, with: credentialType)
let task = session.uploadTask(with: request, fromFile: localFile)
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { [weak self] error in
var responseError: FileProviderWebDavError?
@@ -433,7 +443,8 @@ extension WebDAVFileProvider: FileProviderOperations {
return nil
}
let url = self.url(of:path)
let request = URLRequest(url: url)
var request = URLRequest(url: url)
request.set(httpAuthentication: credential, with: credentialType)
let task = session.downloadTask(with: request)
completionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = completionHandler
downloadCompletionHandlersForTasks[session.sessionDescription!]?[task.taskIdentifier] = { tempURL in
@@ -469,11 +480,8 @@ extension WebDAVFileProvider: FileProviderReadWrite {
let opType = FileOperationType.fetch(path: path)
let url = self.url(of: path)
var request = URLRequest(url: url)
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + Int64(length) - 1)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
request.set(httpAuthentication: credential, with: credentialType)
request.set(rangeWithOffset: offset, length: length)
let task = session.downloadTask(with: request)
let handle = RemoteOperationHandle(operationType: opType, tasks: [task])
@@ -511,6 +519,7 @@ extension WebDAVFileProvider: FileProviderReadWrite {
let url = atomically ? self.url(of: path).appendingPathExtension("tmp") : self.url(of: path)
var request = URLRequest(url: url)
request.httpMethod = "PUT"
request.set(httpAuthentication: credential, with: credentialType)
if !overwrite {
request.setValue("F", forHTTPHeaderField: "Overwrite")
}
@@ -545,6 +554,86 @@ extension WebDAVFileProvider: FileProviderReadWrite {
// TODO: implements methods for lock mechanism
}
extension WebDAVFileProvider: ExtendedFileProvider {
open func thumbnailOfFileSupported(path: String) -> Bool {
guard self.baseURL?.host?.contains("dav.yandex.") ?? false else {
return false
}
let supportedExt: [String] = ["jpg", "jpeg", "png", "gif"]
return supportedExt.contains((path as NSString).pathExtension)
}
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((ImageClass?, Error?) -> Void)) {
guard self.baseURL?.host?.contains("dav.yandex.") ?? false else {
dispatch_queue.async {
completionHandler(nil, self.throwError(path, code: URLError.resourceUnavailable))
}
return
}
let dimension = dimension ?? CGSize(width: 64, height: 64)
let url = URL(string: self.url(of: path).absoluteString + "?preview&size=\(dimension.width)x\(dimension.height)")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.set(httpAuthentication: credential, with: credentialType)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, path: url.relativePath, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
completionHandler(nil, responseError ?? error)
return
}
completionHandler(data.flatMap({ ImageClass(data: $0) }), nil)
})
task.resume()
}
open func propertiesOfFileSupported(path: String) -> Bool {
return false
}
open func propertiesOfFile(path: String, completionHandler: @escaping (([String : Any], [String], Error?) -> Void)) {
dispatch_queue.async {
completionHandler([:], [], self.throwError(path, code: URLError.resourceUnavailable))
}
}
}
extension WebDAVFileProvider: FileProviderSharing {
open func publicLink(to path: String, completionHandler: @escaping ((URL?, FileObject?, Date?, Error?) -> Void)) {
guard self.baseURL?.host?.contains("dav.yandex.") ?? false else {
dispatch_queue.async {
completionHandler(nil, nil, nil, self.throwError(path, code: URLError.resourceUnavailable))
}
return
}
let url = self.url(of: path)
var request = URLRequest(url: url)
request.httpMethod = "PROPPATCH"
request.set(httpAuthentication: credential, with: credentialType)
request.set(contentType: .xml)
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) {
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
}
if let data = data {
let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
if let urlStr = xresponse.first?.prop["public_url"], let url = URL(string: urlStr) {
completionHandler(url, nil, nil, nil)
return
}
}
completionHandler(nil, nil, nil, responseError ?? error)
})
}
}
extension WebDAVFileProvider: FileProvider { }
// MARK: WEBDAV XML response implementation