Compare commits

..

5 Commits

Author SHA1 Message Date
Amir Abbas 96ce12f226 Added iCloud Drive provider CloudFileProvider
- Refactored initializers
- Replaced `NSSearchPathForDirectoriesInDomains` with `FileManager.urls(for:In:)`
2017-01-27 02:13:51 +03:30
Amir Abbas 091fd14a88 OneDrive drive name bug fix. Added sorting method to [FileObject] array.
- now return `Error` for uploading a file with size higher than limit in OneDrive / Dropbox instead of asserting
2017-01-25 16:04:16 +03:30
Amir Abbas 10aac532f7 Fixed crashing bug 2017-01-18 10:20:10 +03:30
Amir Abbas 3aac0a88c4 Fixed path in url percent encoding 2017-01-16 23:28:32 +03:30
Amir Abbas 24355a4c6c Added OneDrive support
- removed unnecessary MediaPlayer.framework dependency
- fixed multiple Dropbox provider bugs
2017-01-16 21:55:58 +03:30
12 changed files with 869 additions and 99 deletions
+2 -2
View File
@@ -16,8 +16,8 @@ Pod::Spec.new do |s|
#
s.name = "FileProvider"
s.version = "0.9.0"
s.summary = "FileManager replacement for Local and Remote (WebDAV/Dropbox/SMB2) files on iOS and macOS."
s.version = "0.10.3"
s.summary = "FileManager replacement for Local and Remote (WebDAV/Dropbox/OneDrive/SMB2) files on iOS and macOS."
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
+20 -10
View File
@@ -98,17 +98,20 @@
79BD63A81E2CC2940035128C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63A71E2CC2940035128C /* CoreGraphics.framework */; };
79BD63AA1E2CC2BB0035128C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63A91E2CC2BB0035128C /* AVFoundation.framework */; };
79BD63AC1E2CC2C20035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63AB1E2CC2C20035128C /* ImageIO.framework */; };
79BD63AE1E2CC2EB0035128C /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63AD1E2CC2EB0035128C /* MediaPlayer.framework */; };
79BD63B01E2CC3300035128C /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63AF1E2CC3300035128C /* libxml2.tbd */; };
79BD63B21E2CC3350035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B11E2CC3350035128C /* ImageIO.framework */; };
79BD63B41E2CC33D0035128C /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B31E2CC33D0035128C /* MediaPlayer.framework */; };
79BD63B61E2CC3860035128C /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B51E2CC3860035128C /* CoreFoundation.framework */; };
79BD63B81E2CC38D0035128C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B71E2CC38D0035128C /* AVFoundation.framework */; };
79BD63BA1E2CC39B0035128C /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63B91E2CC39B0035128C /* libxml2.tbd */; };
79BD63BC1E2CC3B90035128C /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BB1E2CC3B90035128C /* MediaPlayer.framework */; };
79BD63BE1E2CC3C20035128C /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BD1E2CC3C20035128C /* ImageIO.framework */; };
79BD63C01E2CC3CD0035128C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */; };
79BD63C21E2CC3D30035128C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79BD63C11E2CC3D30035128C /* AVFoundation.framework */; };
79BD63C51E2D17880035128C /* OneDriveFileProvide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */; };
79BD63C61E2D17880035128C /* OneDriveFileProvide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */; };
79BD63C71E2D17880035128C /* OneDriveFileProvide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */; };
79BD63C81E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
79BD63C91E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
79BD63CA1E2D17880035128C /* OneDriveHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD63C41E2D17880035128C /* OneDriveHelper.swift */; };
79F5745B1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; };
79F5745C1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; };
79F5745D1DFDB10B00179ABF /* FileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79F5745A1DFDB10A00179ABF /* FileObject.swift */; };
@@ -166,6 +169,8 @@
79BD63BD1E2CC3C20035128C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; };
79BD63BF1E2CC3CD0035128C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
79BD63C11E2CC3D30035128C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.1.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveFileProvide.swift; sourceTree = "<group>"; };
79BD63C41E2D17880035128C /* OneDriveHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneDriveHelper.swift; sourceTree = "<group>"; };
79F5745A1DFDB10A00179ABF /* FileObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileObject.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -177,7 +182,6 @@
79BD63AA1E2CC2BB0035128C /* AVFoundation.framework in Frameworks */,
79BD63AC1E2CC2C20035128C /* ImageIO.framework in Frameworks */,
79BD63A81E2CC2940035128C /* CoreGraphics.framework in Frameworks */,
79BD63AE1E2CC2EB0035128C /* MediaPlayer.framework in Frameworks */,
791950F51DE58A5400B4426E /* libxml2.tbd in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -189,7 +193,6 @@
79BD63B81E2CC38D0035128C /* AVFoundation.framework in Frameworks */,
79BD63B61E2CC3860035128C /* CoreFoundation.framework in Frameworks */,
79BD63B21E2CC3350035128C /* ImageIO.framework in Frameworks */,
79BD63B41E2CC33D0035128C /* MediaPlayer.framework in Frameworks */,
79BD63B01E2CC3300035128C /* libxml2.tbd in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -201,7 +204,6 @@
79BD63C21E2CC3D30035128C /* AVFoundation.framework in Frameworks */,
79BD63C01E2CC3CD0035128C /* CoreGraphics.framework in Frameworks */,
79BD63BE1E2CC3C20035128C /* ImageIO.framework in Frameworks */,
79BD63BC1E2CC3B90035128C /* MediaPlayer.framework in Frameworks */,
79BD63BA1E2CC39B0035128C /* libxml2.tbd in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -279,8 +281,6 @@
children = (
7924B18B1D89DAE000589DB7 /* AEXML */,
799396991D48C02300086753 /* SMBTypes */,
799396931D48C02300086753 /* DropboxFileProvider.swift */,
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
799396941D48C02300086753 /* FileProvider.h */,
799396951D48C02300086753 /* FileProvider.swift */,
79F5745A1DFDB10A00179ABF /* FileObject.swift */,
@@ -288,6 +288,10 @@
792572401DF23BDA006A1526 /* LocalHelper.swift */,
79BD638B1E2CC2300035128C /* ExtendedLocalFileProvider.swift */,
7902C0851D61B56D00564440 /* RemoteSession.swift */,
799396931D48C02300086753 /* DropboxFileProvider.swift */,
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
79BD63C31E2D17880035128C /* OneDriveFileProvide.swift */,
79BD63C41E2D17880035128C /* OneDriveHelper.swift */,
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */,
799396971D48C02300086753 /* SMBClient.swift */,
799396981D48C02300086753 /* SMBFileProvider.swift */,
@@ -477,11 +481,13 @@
7924B1A21D89DAE000589DB7 /* Options.swift in Sources */,
792572411DF23BDA006A1526 /* LocalHelper.swift in Sources */,
79F5745B1DFDB10B00179ABF /* FileObject.swift in Sources */,
79BD63C51E2D17880035128C /* OneDriveFileProvide.swift in Sources */,
7924B1991D89DAE000589DB7 /* Element.swift in Sources */,
799396C81D48C02300086753 /* SMB2IOCtl.swift in Sources */,
799396D71D48C02300086753 /* SMB2Types.swift in Sources */,
7924B1B21D89FCDA00589DB7 /* FPSStreamTask.swift in Sources */,
799396C51D48C02300086753 /* SMB2FileOperation.swift in Sources */,
79BD63C81E2D17880035128C /* OneDriveHelper.swift in Sources */,
799396BF1D48C02300086753 /* SMB2DataTypes.swift in Sources */,
799396B91D48C02300086753 /* SMBFileProvider.swift in Sources */,
794C220E1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
@@ -514,11 +520,13 @@
7924B1A31D89DAE000589DB7 /* Options.swift in Sources */,
792572421DF23BDA006A1526 /* LocalHelper.swift in Sources */,
79F5745C1DFDB10B00179ABF /* FileObject.swift in Sources */,
79BD63C61E2D17880035128C /* OneDriveFileProvide.swift in Sources */,
7924B1B01D89F7DE00589DB7 /* FPSStreamTask.swift in Sources */,
7924B19A1D89DAE000589DB7 /* Element.swift in Sources */,
799396C91D48C02300086753 /* SMB2IOCtl.swift in Sources */,
799396D81D48C02300086753 /* SMB2Types.swift in Sources */,
799396C61D48C02300086753 /* SMB2FileOperation.swift in Sources */,
79BD63C91E2D17880035128C /* OneDriveHelper.swift in Sources */,
799396C01D48C02300086753 /* SMB2DataTypes.swift in Sources */,
799396BA1D48C02300086753 /* SMBFileProvider.swift in Sources */,
794C220F1D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
@@ -551,11 +559,13 @@
7924B1A41D89DAE000589DB7 /* Options.swift in Sources */,
792572431DF23BDA006A1526 /* LocalHelper.swift in Sources */,
79F5745D1DFDB10B00179ABF /* FileObject.swift in Sources */,
79BD63C71E2D17880035128C /* OneDriveFileProvide.swift in Sources */,
7924B1B11D89F7DF00589DB7 /* FPSStreamTask.swift in Sources */,
7924B19B1D89DAE000589DB7 /* Element.swift in Sources */,
799396CA1D48C02300086753 /* SMB2IOCtl.swift in Sources */,
799396D91D48C02300086753 /* SMB2Types.swift in Sources */,
799396C71D48C02300086753 /* SMB2FileOperation.swift in Sources */,
79BD63CA1E2D17880035128C /* OneDriveHelper.swift in Sources */,
799396C11D48C02300086753 /* SMB2DataTypes.swift in Sources */,
799396BB1D48C02300086753 /* SMBFileProvider.swift in Sources */,
794C22101D591A4B00EC49B8 /* SMB2QueryTypes.swift in Sources */,
@@ -585,7 +595,7 @@
799396601D48B7BF00086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.8.3;
BUNDLE_VERSION_STRING = 0.10.3;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
@@ -615,7 +625,7 @@
799396611D48B7BF00086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.8.3;
BUNDLE_VERSION_STRING = 0.10.3;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
+24 -10
View File
@@ -16,7 +16,7 @@
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
--->
This library provides implementaion of WebDav and SMB2 (incomplete) and local files.
This library provides implementaion of WebDav, Dropbox, OneDrive and SMB2 (incomplete) and local files.
All functions are async calls and it wont block your main thread.
@@ -26,10 +26,17 @@ Local and WebDAV providers are fully tested and can be used in production enviro
- [x] **LocalFileProvider** a wrapper around `FileManager` with some additions like searching and reading a portion of file.
- [x] **WebDAVFileProvider** WebDAV protocol is defacto file transmission standard, replaced FTP.
- [x] **DropboxFileProvider** A wrapper around Dropbox Web API. For now it has limitation in uploading files up to 150MB.
- [ ] **SMBFileProvider** SMB2/3 introduced in 2006, which is a file and printer sharing protocol originated from Microsoft Windows and now is replacing AFP protocol on MacOS. I implemented data types and some basic functions but *main interface is not implemented yet!* SMB1/CIFS is depericated and very tricky to be implemented
- [x] **DropboxFileProvider** A wrapper around Dropbox Web API.
* For now it has limitation in uploading files up to 150MB.
- [x] **OneDriveFileProvider** A wrapper around OneDrive Web API, works with `onedrive.com` and compatible (business) servers.
* For now it has limitation in uploading files up to 100MB.
- [x] **CloudFileProvider** A wrapper around app's ubiquitous container to iCloud Drive in iOS 8+ API.
- [ ] **SMBFileProvider** SMB2/3 introduced in 2006, which is a file and printer sharing protocol originated from Microsoft Windows and now is replacing AFP protocol on MacOS.
* Data types and some basic functions are implemented but *main interface is not implemented yet!*
* SMB1/CIFS is depericated and very tricky to be implemented
- [ ] **FTPFileProvider** while deprecated in 1990s, it's still in use on some Web hosts.
- [ ] **AmazonS3FileProvider**
- [ ] **GoogleDriveFileProvider**
## Requirements
@@ -96,18 +103,23 @@ For LocalFileProvider if you want to deal with `Documents` folder
``` swift
let documentsProvider = LocalFileProvider()
```
is equal to:
// Equals with:
let documentsProvider = LocalFileProvider(directory: .documentDirectory, domainMask: = .userDomainMask)
``` swift
let documentPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true);
let documentsURL = URL(fileURLWithPath: documentPath);
// Equals with:
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let documentsProvider = LocalFileProvider(baseURL: documentsURL)
```
You can't change the base url later. and all paths are related to this base url by default.
To initialize an iCloud Container provider use below code, This will automatically manager creating Documents folder in container:
```swift
let documentsProvider = CloudFileProvider(containerId: nil)
```
For remote file providers authentication may be necessary:
``` swift
@@ -117,7 +129,7 @@ let webdavProvider = WebDAVFileProvider(baseURL: URL(string: "http://www.example
* In case you want to connect non-secure servers for WebDAV (http) in iOS 9+ / macOS 10.11+ you should disable App Transport Security (ATS) according to [this guide.](https://gist.github.com/mlynch/284699d676fe9ed0abfa)
* For Dropbox, user is clientID and password is Token which both must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token. The latter is easier to use and prefered.
* For Dropbox & OneDrive, user is clientID and password is Token which both must be retrieved via [OAuth2 API of Dropbox](https://www.dropbox.com/developers/reference/oauth-guide). There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token. The latter is easier to use and prefered.
For interaction with UI, set delegate variable of `FileProvider` object
@@ -332,7 +344,7 @@ documentsProvider.unregisterNotifcation(path: provider.currentPath)
Providers which conform `ExtendedFileProvider` are able to generate thumbnail or provide file meta-information for images, media and pdf files.
Local and Dropbox providers support this functionality.
Local, OneDrive and Dropbox providers support this functionality.
##### Thumbnails
To check either file thumbnail is supported or not and fetch thumbnail, use (and modify) these example code:
@@ -349,6 +361,8 @@ if documentsProvider.thumbnailOfFileSupported(path: path {
}
```
* Please note it won't cache generated images. if you don't do it yourself, it may hit you app's performance.
##### Meta-informations
To get meta-information like image/video taken date, dimension, etc., use (and modify) these example code:
+34 -23
View File
@@ -13,11 +13,15 @@ import CoreGraphics
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
// in case of using this class with unencrypted HTTP connection.
open class DropboxFileProvider: NSObject, FileProviderBasicRemote {
open static let type: String = "WebDAV"
open let isPathRelative: Bool = true
open class DropboxFileProvider: FileProviderBasicRemote {
open static let type: String = "DropBox"
open let isPathRelative: Bool
open let baseURL: URL?
open var currentPath: String = ""
open var currentPath: String
open let apiURL: URL
open let contentURL: URL
open var dispatch_queue: DispatchQueue {
willSet {
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
@@ -26,8 +30,8 @@ open class DropboxFileProvider: NSObject, FileProviderBasicRemote {
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential?
open private(set) var cache: URLCache?
public var useCache: Bool = false
public var validatingCache: Bool = true
public var useCache: Bool
public var validatingCache: Bool
fileprivate var _session: URLSession?
fileprivate var sessionDelegate: SessionDelegate?
@@ -46,10 +50,17 @@ open class DropboxFileProvider: NSObject, FileProviderBasicRemote {
public init? (credential: URLCredential?, cache: URLCache? = nil) {
self.baseURL = nil
dispatch_queue = DispatchQueue(label: "FileProvider.\(DropboxFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
//let url = baseURL.uw_absoluteString
self.credential = credential
self.isPathRelative = true
self.currentPath = ""
self.useCache = false
self.validatingCache = true
self.cache = cache
self.credential = credential
self.apiURL = URL(string: "https://api.dropboxapi.com/2/")!
self.contentURL = URL(string: "https://content.dropboxapi.com/2")!
dispatch_queue = DispatchQueue(label: "FileProvider.\(DropboxFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
}
deinit {
@@ -63,7 +74,7 @@ open class DropboxFileProvider: NSObject, FileProviderBasicRemote {
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/files/get_metadata")!
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
@@ -86,7 +97,7 @@ open class DropboxFileProvider: NSObject, FileProviderBasicRemote {
}
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/users/get_space_usage")!
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")
@@ -139,17 +150,17 @@ extension DropboxFileProvider: FileProviderOperations {
let destPath = operation.destination
switch operation {
case .create:
url = "https://api.dropboxapi.com/2/files/create_folder"
url = "files/create_folder"
case .copy:
url = "https://api.dropboxapi.com/2/files/copy"
url = "files/copy"
case .move:
url = "https://api.dropboxapi.com/2/files/move"
url = "files/move"
case .remove:
url = "https://api.dropboxapi.com/2/files/delete"
url = "files/delete"
default: // modify, link, fetch
return nil
}
var request = URLRequest(url: URL(string: url)!)
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")
@@ -187,7 +198,7 @@ extension DropboxFileProvider: FileProviderOperations {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
let url = URL(string: "https://content.dropboxapi.com/2/files/download")!
let url = URL(string: "files/download", relativeTo: contentURL)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
@@ -218,7 +229,7 @@ extension DropboxFileProvider: FileProviderOperations {
extension DropboxFileProvider: FileProviderReadWrite {
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
let opType = FileOperationType.fetch(path: path)
let url = URL(string: "https://content.dropboxapi.com/2/files/download")!
let url = URL(string: "files/download", relativeTo: contentURL)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
@@ -279,7 +290,7 @@ extension DropboxFileProvider: FileProviderReadWrite {
extension DropboxFileProvider {
open func temporaryLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: DropboxFileObject?, _ error: Error?) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/files/get_temporary_link")!
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")
@@ -308,7 +319,7 @@ extension DropboxFileProvider {
}
open func copyItem(path: String, toRemoteURL destURL: URL, completionHandler: @escaping ((_ jobId: String?, _ attribute: DropboxFileObject?, _ error: Error?) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/files/save_url")!
let url = URL(string: "files/save_url", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
@@ -370,13 +381,13 @@ extension DropboxFileProvider: ExtendedFileProvider {
let url: URL
switch (path as NSString).pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
url = URL(string: "https://content.dropboxapi.com/2/files/get_thumbnail")!
url = URL(string: "files/get_thumbnail", relativeTo: contentURL)!
case "doc", "docx", "docm", "xls", "xlsx", "xlsm":
fallthrough
case "ppt", "pps", "ppsx", "ppsm", "pptx", "pptm":
fallthrough
case "rtf":
url = URL(string: "https://content.dropboxapi.com/2/files/get_preview")!
url = URL(string: "files/get_preview", relativeTo: contentURL)!
default:
return
}
@@ -410,7 +421,7 @@ extension DropboxFileProvider: ExtendedFileProvider {
}
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/files/get_metadata")!
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
+23 -11
View File
@@ -2,8 +2,8 @@
// DropboxHelper.swift
// FileProvider
//
// Created by Amir Abbas Mousavian on 5/18/95.
//
// Created by Amir Abbas Mousavian.
// Copyright © 2016 Mousavian. Distributed under MIT license.
//
import Foundation
@@ -34,10 +34,10 @@ public final class DropboxFileObject: FileObject {
open internal(set) var id: String? {
get {
return allValues["NSURLDropboxDocumentIdentifyKey"] as? String
return allValues["NSURLDocumentIdentifyKey"] as? String
}
set {
allValues["NSURLDropboxDocumentIdentifyKey"] = newValue
allValues["NSURLDocumentIdentifyKey"] = newValue
}
}
@@ -57,10 +57,10 @@ internal extension DropboxFileProvider {
var requestDictionary = [String: AnyObject]()
let url: URL
if let cursor = cursor {
url = URL(string: "https://api.dropboxapi.com/2/files/list_folder/continue")!
url = URL(string: "files/list_folder/continue", relativeTo: apiURL)!
requestDictionary["cursor"] = cursor as NSString?
} else {
url = URL(string: "https://api.dropboxapi.com/2/files/list_folder")!
url = URL(string: "files/list_folder", relativeTo: apiURL)!
requestDictionary["path"] = correctPath(path) as NSString?
requestDictionary["recursive"] = recursive as NSNumber?
}
@@ -98,10 +98,15 @@ internal extension DropboxFileProvider {
}
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
assert(data.count < 150*1024*1024, "Maximum size of allowed size to upload is 150MB")
if data.count > 150 * 1024 * 1024 {
let error = FileProviderDropboxError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
completionHandler?(error)
self.delegateNotify(.create(path: targetPath), error: error)
return nil
}
var requestDictionary = [String: Any]()
let url: URL
url = URL(string: "https://content.dropboxapi.com/2/files/upload")!
url = URL(string: "files/upload", relativeTo: contentURL)!
requestDictionary["path"] = correctPath(targetPath) as NSString?
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
requestDictionary["client_modified"] = string(from:modifiedDate)
@@ -125,9 +130,16 @@ internal extension DropboxFileProvider {
}
func upload_simple(_ targetPath: String, localFile: URL, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let size = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1
if size > 150 * 1024 * 1024 {
let error = FileProviderDropboxError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
completionHandler?(error)
self.delegateNotify(.create(path: targetPath), error: error)
return nil
}
var requestDictionary = [String: Any]()
let url: URL
url = URL(string: "https://content.dropboxapi.com/2/files/upload")!
url = URL(string: "files/upload", relativeTo: contentURL)!
requestDictionary["path"] = correctPath(targetPath) as NSString?
requestDictionary["mode"] = (overwrite ? "overwrite" : "add") as NSString
requestDictionary["client_modified"] = string(from:modifiedDate)
@@ -150,7 +162,7 @@ internal extension DropboxFileProvider {
}
func search(_ startPath: String = "", query: String, start: Int = 0, maxResultPerPage: Int = 25, maxResults: Int = -1, foundItem:@escaping ((_ file: DropboxFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
let url = URL(string: "https://api.dropboxapi.com/2/files/search")!
let url = URL(string: "files/search", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
@@ -206,7 +218,7 @@ internal extension DropboxFileProvider {
fileObject.type = (json[".tag"] as? String) == "folder" ? .directory : .regular
fileObject.isReadOnly = (json["sharing_info"]?["read_only"] as? NSNumber)?.boolValue ?? false
fileObject.id = json["id"] as? String
fileObject.rev = json["id"] as? String
fileObject.rev = json["rev"] as? String
return fileObject
}
+2 -3
View File
@@ -10,10 +10,9 @@ import Foundation
import ImageIO
import CoreGraphics
import AVFoundation
import MediaPlayer
#if os(iOS) || os(tvOS)
import UIKit
#elseif os(OSX)
#elseif os(macOS)
import Cocoa
#endif
@@ -166,7 +165,7 @@ public struct LocalFileInformationGenerator {
assetImgGenerate.appliesPreferredTrackTransform = true
let time = CMTimeMake(asset.duration.value / 3, asset.duration.timescale)
if let cgImage = try? assetImgGenerate.copyCGImage(at: time, actualTime: nil) {
#if os(OSX)
#if os(macOS)
return ImageClass(cgImage: cgImage, size: NSSize.zero)
#else
return ImageClass(cgImage: cgImage)
+12 -1
View File
@@ -135,7 +135,7 @@ open class FileObject {
}
}
/// Sorting FileObject array by given criteria, not thread-safe
/// Sorting FileObject array by given criteria, **not thread-safe**
public struct FileObjectSorting {
/// Determines sort kind by which item of File object
@@ -222,6 +222,17 @@ public struct FileObjectSorting {
}
}
extension Array where Element: FileObject {
public func sorted(by type: FileObjectSorting.SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) -> [Element] {
let sorting = FileObjectSorting(type: type, ascending: ascending, isDirectoriesFirst: isDirectoriesFirst)
return sorting.sort(self) as! [Element]
}
public mutating func sorted(by type: FileObjectSorting.SortType, ascending: Bool = true, isDirectoriesFirst: Bool = false) {
self = self.sorted(by: type, ascending: ascending, isDirectoriesFirst: isDirectoriesFirst)
}
}
extension URLFileResourceType {
public init(fileTypeValue: FileAttributeType) {
switch fileTypeValue {
+24 -18
View File
@@ -10,7 +10,7 @@ import Foundation
#if os(iOS) || os(tvOS)
import UIKit
public typealias ImageClass = UIImage
#elseif os(OSX)
#elseif os(macOS)
import Cocoa
public typealias ImageClass = NSImage
#endif
@@ -26,13 +26,12 @@ public protocol FileProviderBasic: class {
var delegate: FileProviderDelegate? { get set }
var credential: URLCredential? { get }
/**
*
*/
func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void))
func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void))
func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void))
func url(of path: String?) -> URL
}
public protocol FileProviderBasicRemote: FileProviderBasic {
@@ -181,32 +180,39 @@ public protocol FileProviderMonitor: FileProviderBasic {
public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite, NSCopying {
}
fileprivate let pathTrimSet = CharacterSet(charactersIn: " /")
extension FileProviderBasic {
public var type: String {
return Self.type
}
public var bareCurrentPath: String {
return currentPath.trimmingCharacters(in: CharacterSet(charactersIn: ". /"))
return currentPath.trimmingCharacters(in: pathTrimSet)
}
func escaped(path: String) -> String {
return path.trimmingCharacters(in: pathTrimSet).addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)!
}
public func absoluteURL(_ path: String? = nil) -> URL {
let rpath: String
return url(of: path).absoluteURL
}
public func url(of path: String? = nil) -> URL {
var rpath: String
if let path = path {
rpath = path
} else {
rpath = self.currentPath
}
if isPathRelative, let baseURL = baseURL {
if rpath.hasPrefix("/") && baseURL.absoluteString.hasSuffix("/") {
var npath = rpath
npath.remove(at: npath.startIndex)
return baseURL.appendingPathComponent(npath)
} else {
return baseURL.appendingPathComponent(rpath)
rpath = rpath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? rpath
if let baseURL = baseURL {
if isPathRelative && rpath.hasPrefix("/") {
rpath.remove(at: rpath.startIndex)
}
return URL(string: rpath, relativeTo: baseURL) ?? baseURL
} else {
return URL(fileURLWithPath: rpath).standardizedFileURL
return URL(string: rpath)!
}
}
@@ -349,16 +355,16 @@ extension ExtendedFileProvider {
return data.count > 4 && data.scanString(length: 4, encoding: .ascii) == "%PDF"
}
internal static func convertToImage(pdfData: Data?) -> ImageClass? {
internal static func convertToImage(pdfData: Data?, page: Int = 1) -> ImageClass? {
guard let pdfData = pdfData else { return nil }
let cfPDFData: CFData = pdfData as CFData
if let provider = CGDataProvider(data: cfPDFData), let reference = CGPDFDocument(provider), let pageRef = reference.page(at: 1) {
if let provider = CGDataProvider(data: cfPDFData), let reference = CGPDFDocument(provider), let pageRef = reference.page(at: page) {
let frame = pageRef.getBoxRect(CGPDFBox.mediaBox)
var size = frame.size
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
#if os(OSX)
#if os(macOS)
let ppp = Int(NSScreen.main()?.backingScaleFactor ?? 1) // fetch device is retina or not
size.width *= CGFloat(ppp)
@@ -423,7 +429,7 @@ extension ExtendedFileProvider {
let newSize = CGSize(width: width, height: height)
#if os(OSX)
#if os(macOS)
var imageRect = NSRect(origin: CGPoint.zero, size: image.size)
let imageRef = image.cgImage(forProposedRect: &imageRect, context: nil, hints: nil)
+44 -13
View File
@@ -9,37 +9,38 @@
import Foundation
open class LocalFileProvider: FileProvider, FileProviderMonitor {
open static let type = "Local"
open var isPathRelative: Bool = true
open private(set) var baseURL: URL? = LocalFileProvider.defaultBaseURL()
open var currentPath: String = ""
open static let type: String = "Local"
open var isPathRelative: Bool
open fileprivate(set) var baseURL: URL?
open var currentPath: String
open var dispatch_queue: DispatchQueue
open var operation_queue: DispatchQueue
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential? = nil
open fileprivate(set) var credential: URLCredential?
open private(set) var fileManager = FileManager()
open private(set) var opFileManager = FileManager()
fileprivate var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
public init () {
dispatch_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
operation_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type).Operation", attributes: [])
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
opFileManager.delegate = fileProviderManagerDelegate
/// default values are `directory: .documentDirectory, domainMask: .userDomainMask`
public convenience init (directory: FileManager.SearchPathDirectory = .documentDirectory, domainMask: FileManager.SearchPathDomainMask = .userDomainMask) {
self.init(baseURL: FileManager.default.urls(for: directory, in: domainMask).first!)
}
public init (baseURL: URL) {
self.baseURL = baseURL
self.isPathRelative = true
self.currentPath = ""
self.credential = nil
dispatch_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
operation_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type).Operation", attributes: [])
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
opFileManager.delegate = fileProviderManagerDelegate
}
open static func defaultBaseURL() -> URL {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true);
return URL(fileURLWithPath: paths[0])
open class func defaultBaseURL() -> URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
@@ -356,3 +357,33 @@ public extension LocalFileProvider {
}
}
}
class CloudFileProvider: LocalFileProvider {
// FIXME: convert static var type to class var in next Swift version
open var type: String { return "iCloudDrive" }
public init? (containerId: String?) {
assert(!Thread.isMainThread, "LocalFileProvider.init(containerId:) is not recommended to be executed on Main Thread.")
guard FileManager.default.ubiquityIdentityToken == nil else {
return nil
}
guard let ubiquityURL = FileManager.default.url(forUbiquityContainerIdentifier: containerId) else {
return nil
}
super.init(baseURL: ubiquityURL.appendingPathComponent("Documents"))
dispatch_queue = DispatchQueue(label: "FileProvider.\(self.type)", attributes: DispatchQueue.Attributes.concurrent)
operation_queue = DispatchQueue(label: "FileProvider.\(self.type).Operation", attributes: [])
fileManager.url(forUbiquityContainerIdentifier: containerId)
opFileManager.url(forUbiquityContainerIdentifier: containerId)
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
opFileManager.delegate = fileProviderManagerDelegate
}
open override static func defaultBaseURL() -> URL {
return FileManager.default.url(forUbiquityContainerIdentifier: nil) ?? super.defaultBaseURL()
}
}
+356
View File
@@ -0,0 +1,356 @@
//
// OneDriveFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2017 Mousavian. Distributed under MIT license.
//
import Foundation
import CoreGraphics
// Because this class uses NSURLSession, it's necessary to disable App Transport Security
// in case of using this class with unencrypted HTTP connection.
open class OneDriveFileProvider: FileProviderBasicRemote {
open static let type: String = "OneDrive"
open let isPathRelative: Bool
open let baseURL: URL?
open var serverURL: URL { return baseURL! }
open var drive: String
open var driveURL: URL {
return URL(string: "/drive/\(drive):/", relativeTo: baseURL)!
}
open var currentPath: String
open var dispatch_queue: DispatchQueue {
willSet {
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
}
}
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential?
open private(set) var cache: URLCache?
public var useCache: Bool
public var validatingCache: Bool
fileprivate var _session: URLSession?
fileprivate var sessionDelegate: SessionDelegate?
public var session: URLSession {
if _session == nil {
self.sessionDelegate = SessionDelegate(fileProvider: self, credential: credential)
let queue = OperationQueue()
//queue.underlyingQueue = dispatch_queue
let config = URLSessionConfiguration.default
config.urlCache = cache
config.requestCachePolicy = .returnCacheDataElseLoad
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: queue)
}
return _session!
}
public init? (credential: URLCredential?, serverURL: URL? = nil, drive: String = "root", cache: URLCache? = nil) {
self.baseURL = serverURL ?? URL(string: "https://api.onedrive.com")
self.drive = drive
self.isPathRelative = true
self.currentPath = ""
self.useCache = false
self.validatingCache = true
self.cache = cache
self.credential = credential
dispatch_queue = DispatchQueue(label: "FileProvider.\(OneDriveFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
}
deinit {
_session?.invalidateAndCancel()
}
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
list(path) { (contents, cursor, error) in
completionHandler(contents, error)
}
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
let url = URL(string: escaped(path: path), relativeTo: driveURL)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var dbError: FileProviderOneDriveError?
var fileObject: OneDriveFileObject?
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
dbError = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil
if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr), let file = self.mapToFileObject(json) {
fileObject = file
}
}
completionHandler(fileObject, dbError ?? error)
})
task.resume()
}
open func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void)) {
let url = URL(string: "/drive/root", relativeTo: baseURL)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var totalSize: Int64 = -1
var usedSize: Int64 = 0
if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr) {
totalSize = (json["total"] as? NSNumber)?.int64Value ?? -1
usedSize = (json["used"] as? NSNumber)?.int64Value ?? 0
}
completionHandler(totalSize, usedSize)
})
task.resume()
}
open weak var fileOperationDelegate: FileOperationDelegate?
}
extension OneDriveFileProvider: FileProviderOperations {
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let path = (atPath as NSString).appendingPathComponent(folderName) + "/"
return doOperation(.create(path: path), completionHandler: completionHandler)
}
public func create(file fileName: String, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let filePath = (path as NSString).appendingPathComponent(fileName)
return self.writeContents(path: filePath, contents: data ?? Data(), completionHandler: completionHandler)
}
public func moveItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
return doOperation(.move(source: path, destination: toPath), completionHandler: completionHandler)
}
public func copyItem(path: String, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
return doOperation(.copy(source: path, destination: toPath), completionHandler: completionHandler)
}
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
return doOperation(.remove(path: path), completionHandler: completionHandler)
}
fileprivate func doOperation(_ operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: operation) ?? true == true else {
return nil
}
guard let sourcePath = operation.source else { return nil }
let destPath = operation.destination
var request = URLRequest(url: URL(string: sourcePath, relativeTo: driveURL)!)
switch operation {
case .create:
request.httpMethod = "CREATE"
case .copy:
request.httpMethod = "POST"
case .move:
request.httpMethod = "PATCH"
case .remove:
request.httpMethod = "DELETE"
default: // modify, link, fetch
return nil
}
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
var requestDictionary = [String: AnyObject]()
if let dest = correctPath(destPath) as NSString? {
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
requestDictionary["parentReference"] = ("/drive/\(drive):" + dest.deletingLastPathComponent) as NSString
requestDictionary["name"] = dest.lastPathComponent as NSString
request.httpBody = dictionaryToJSON(requestDictionary)?.data(using: .utf8)
}
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var dbError: FileProviderOneDriveError?
if let response = response as? HTTPURLResponse, response.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) {
dbError = FileProviderOneDriveError(code: code, path: sourcePath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
}
completionHandler?(dbError ?? error)
self.delegateNotify(operation, error: dbError ?? error)
})
task.taskDescription = operation.json
task.resume()
return RemoteOperationHandle(operationType: operation, tasks: [task])
}
public func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.copy(source: localFile.absoluteString, destination: toPath)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
return upload_simple(toPath, localFile: localFile, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
}
public func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.copy(source: path, destination: destURL.absoluteString)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
}
let url = URL(string: escaped(path: path) + ":/content", relativeTo: driveURL)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
let task = session.downloadTask(with: request, completionHandler: { (cacheURL, response, error) in
guard let cacheURL = cacheURL, let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode < 300 else {
let code = FileProviderHTTPErrorCode(rawValue: (response as? HTTPURLResponse)?.statusCode ?? -1)
let errorData : Data? = nil //Data(contentsOf:cacheURL) // TODO: Figure out how to get error response data for the error description
let dbError : FileProviderOneDriveError? = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: errorData ?? Data(), encoding: .utf8)) : nil
completionHandler?(dbError ?? error)
return
}
do {
try FileManager.default.moveItem(at: cacheURL, to: destURL)
completionHandler?(nil)
} catch let e {
completionHandler?(e)
}
})
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
}
extension OneDriveFileProvider: FileProviderReadWrite {
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
let opType = FileOperationType.fetch(path: path)
let url = URL(string: escaped(path: path) + ":/content", relativeTo: driveURL)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var dbError: FileProviderOneDriveError?
if let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode >= 300, let code = FileProviderHTTPErrorCode(rawValue: httpResponse.statusCode) {
dbError = FileProviderOneDriveError(code: code, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
}
let filedata = dbError ?? error == nil ? data : nil
completionHandler(filedata, dbError ?? error)
})
task.taskDescription = opType.json
task.resume()
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public 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
}
// FIXME: remove 150MB restriction
return upload_simple(path, data: data, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
}
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
var foundFiles = [OneDriveFileObject]()
search(path, query: query, foundItem: { (file) in
foundFiles.append(file)
foundItemHandler?(file)
}, completionHandler: { (error) in
completionHandler(foundFiles, error)
})
}
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
/* There is two ways to monitor folders changing in OneDrive. Either using webooks
* which means you have to implement a server to translate it to push notifications
* or using apiv2 list_folder/longpoll method. The second one is implemeted here.
* Tough webhooks are much more efficient, longpoll is much simpler to implement!
* You can implemnt your own webhook service and replace this method accordingly.
*/
NotImplemented()
}
fileprivate func unregisterNotifcation(path: String) {
NotImplemented()
}
// TODO: Implement /copy_reference, /get_account & /get_current_account
}
extension OneDriveFileProvider: ExtendedFileProvider {
public func thumbnailOfFileSupported(path: String) -> Bool {
return true
}
public func propertiesOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
switch fileExt {
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
return true
case "mp3", "aac", "m4a", "wma":
return true
case "mp4", "mpg", "3gp", "mov", "avi", "wmv":
return true
default:
return false
}
}
public func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let url: URL
if let dimension = dimension {
url = URL(string: escaped(path: path) + ":/thumbnails/0/=c\(dimension.width)x\(dimension.height)/content", relativeTo: driveURL)!
} else {
url = URL(string: escaped(path: path) + ":/thumbnails/0/small/content", relativeTo: driveURL)!
}
var request = URLRequest(url: url)
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
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) {
let responseError = FileProviderOneDriveError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
completionHandler(nil, responseError)
return
}
if let data = data {
image = ImageClass(data: data)
}
completionHandler(image, error)
})
task.resume()
}
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
let url = URL(string: escaped(path: path), relativeTo: driveURL)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var dbError: FileProviderOneDriveError?
var dic = [String: Any]()
var keys = [String]()
if let response = response as? HTTPURLResponse {
let code = FileProviderHTTPErrorCode(rawValue: response.statusCode)
dbError = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil
if let data = data, let jsonStr = String(data: data, encoding: .utf8), let json = jsonToDictionary(jsonStr) {
(dic, keys) = self.mapMediaInfo(json)
}
}
completionHandler(dic, keys, dbError ?? error)
})
task.resume()
}
}
extension OneDriveFileProvider: FileProvider {
open func copy(with zone: NSZone? = nil) -> Any {
let copy = OneDriveFileProvider(credential: self.credential, serverURL: self.baseURL, drive: self.drive, cache: self.cache)!
copy.currentPath = self.currentPath
copy.delegate = self.delegate
copy.fileOperationDelegate = self.fileOperationDelegate
copy.useCache = self.useCache
copy.validatingCache = self.validatingCache
return copy
}
}
+317
View File
@@ -0,0 +1,317 @@
//
// OneDriveHelper.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2017 Mousavian. Distributed under MIT license.
//
import Foundation
public struct FileProviderOneDriveError: Error, CustomStringConvertible {
public let code: FileProviderHTTPErrorCode
public let path: String
public let errorDescription: String?
public var description: String {
return code.description
}
}
public final class OneDriveFileObject: FileObject {
internal init(name: String, path: String) {
super.init(absoluteURL: URL(string: path), name: name, path: path)
}
open internal(set) var id: String? {
get {
return allValues["NSURLDocumentIdentifyKey"] as? String
}
set {
allValues["NSURLDocumentIdentifyKey"] = newValue
}
}
open internal(set) var contentType: String {
get {
return allValues["NSURLContentTypeKey"] as? String ?? ""
}
set {
allValues["NSURLContentTypeKey"] = newValue
}
}
open internal(set) var entryTag: String? {
get {
return allValues["NSURLEntryTagKey"] as? String
}
set {
allValues["NSURLEntryTagKey"] = newValue
}
}
}
// codebeat:disable[ARITY]
internal extension OneDriveFileProvider {
func list(_ path: String, cursor: String? = nil, prevContents: [OneDriveFileObject] = [], completionHandler: @escaping ((_ contents: [FileObject], _ cursor: String?, _ error: Error?) -> Void)) {
let url: URL
if let cursor = cursor {
url = URL(string: cursor)!
} else {
url = URL(string: escaped(path: path), relativeTo: driveURL)!
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderOneDriveError?
var files = prevContents
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderOneDriveError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8))
}
if let data = data, let jsonStr = String(data: data, encoding: .utf8) {
let json = jsonToDictionary(jsonStr)
if let entries = json?["value"] as? [AnyObject] , entries.count > 0 {
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
files.append(file)
}
}
let ncursor = json?["@odata.nextLink"] as? String
let hasmore = ncursor != nil
if hasmore {
self.list(path, cursor: ncursor, prevContents: files, completionHandler: completionHandler)
return
}
}
}
completionHandler(files, nil, responseError ?? error)
})
task.taskDescription = FileOperationType.fetch(path: path).json
task.resume()
}
func upload_simple(_ targetPath: String, data: Data, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
if data.count > 100 * 1024 * 1024 {
let error = FileProviderOneDriveError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
completionHandler?(error)
self.delegateNotify(.create(path: targetPath), error: error)
return nil
}
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
let url = URL(string: escaped(path: targetPath) + ":/content" + queryStr, relativeTo: driveURL)!
var request = URLRequest(url: url)
request.httpMethod = "PUT"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
request.httpBody = data
let task = session.uploadTask(with: request, from: data, completionHandler: { (data, response, error) in
var responseError: FileProviderOneDriveError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
}
completionHandler?(responseError ?? error)
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
})
task.taskDescription = operation.json
task.resume()
return RemoteOperationHandle(operationType: operation, tasks: [task])
}
func upload_simple(_ targetPath: String, localFile: URL, modifiedDate: Date = Date(), overwrite: Bool, operation: FileOperationType, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let size = (try? localFile.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? -1
if size > 100 * 1024 * 1024 {
let error = FileProviderOneDriveError(code: .payloadTooLarge, path: targetPath, errorDescription: nil)
completionHandler?(error)
self.delegateNotify(.create(path: targetPath), error: error)
return nil
}
let queryStr = overwrite ? "" : "?@name.conflictBehavior=fail"
let url = URL(string: escaped(path: targetPath) + ":/content" + queryStr, relativeTo: driveURL)!
var request = URLRequest(url: url)
request.httpMethod = "PUT"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
let task = session.uploadTask(with: request, fromFile: localFile, completionHandler: { (data, response, error) in
var responseError: FileProviderOneDriveError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderOneDriveError(code: rCode, path: targetPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
}
completionHandler?(responseError ?? error)
self.delegateNotify(.create(path: targetPath), error: responseError ?? error)
})
task.taskDescription = operation.json
task.resume()
return RemoteOperationHandle(operationType: operation, tasks: [task])
}
func search(_ startPath: String = "", query: String, next: String? = nil, foundItem:@escaping ((_ file: OneDriveFileObject) -> Void), completionHandler: @escaping ((_ error: Error?) -> Void)) {
let url: URL
if let next = next {
url = URL(string: next)!
} else if self.escaped(path: startPath) == "" {
url = URL(string: "/drive/\(drive)/view.search?q=\(query)", relativeTo: baseURL)!
} else {
url = URL(string: "\(escaped(path: startPath))/view.search?q=\(query)", relativeTo: driveURL)!
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
var responseError: FileProviderOneDriveError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderOneDriveError(code: rCode, path: startPath, errorDescription: String(data: data ?? Data(), encoding: .utf8))
}
if let data = data, let jsonStr = String(data: data, encoding: .utf8) {
let json = jsonToDictionary(jsonStr)
if let entries = json?["value"] as? [AnyObject] , entries.count > 0 {
for entry in entries {
if let entry = entry as? [String: AnyObject], let file = self.mapToFileObject(entry) {
foundItem(file)
}
}
let next = json?["@odata.nextLink"] as? String
let hasmore = next != nil
if hasmore, let next = next {
self.search(startPath, query: query, next: next, foundItem: foundItem, completionHandler: completionHandler)
} else {
completionHandler(responseError ?? error)
}
return
}
}
completionHandler(responseError ?? error)
})
task.resume()
}
}
// codebeat:enable[ARITY]
internal extension OneDriveFileProvider {
func mapToFileObject(_ jsonStr: String) -> OneDriveFileObject? {
guard let json = jsonToDictionary(jsonStr) else { return nil }
return self.mapToFileObject(json)
}
func mapToFileObject(_ json: [String: AnyObject]) -> OneDriveFileObject? {
guard let name = json["name"] as? String else { return nil }
guard let path = (json["parentReference"] as? NSDictionary)?["path"] as? String else { return nil }
let lPath = path.replacingOccurrences(of: "/drive/\(drive):", with: "/", options: .anchored, range: nil)
let fileObject = OneDriveFileObject(name: name, path: lPath)
if let webURL = json["webUrl"] as? String, let absolluteURL = URL(string: webURL) {
fileObject.absoluteURL = absolluteURL
}
fileObject.size = (json["size"] as? NSNumber)?.int64Value ?? -1
fileObject.modifiedDate = resolve(dateString: json["lastModifiedDateTime"] as? String ?? "")
fileObject.creationDate = resolve(dateString: json["createdDateTime"] as? String ?? "")
fileObject.type = (json["folder"] as? String) != nil ? .directory : .regular
fileObject.id = json["id"] as? String
fileObject.entryTag = json["eTag"] as? String
return fileObject
}
static let dateFormatter = DateFormatter()
static let decimalFormatter = NumberFormatter()
func mapMediaInfo(_ json: [String: Any]) -> (dictionary: [String: Any], keys: [String]) {
func spaceCamelCase(_ text: String) -> String {
var newString: String = ""
let upperCase = CharacterSet.uppercaseLetters
for scalar in text.unicodeScalars {
if upperCase.contains(scalar) {
newString.append(" ")
}
let character = Character(scalar)
newString.append(character)
}
return newString.capitalized
}
var dic = [String: Any]()
var keys = [String]()
if let parent = json["image"] as? [String: Any] ?? json["video"] as? [String: Any], let height = parent["height"] as? UInt64, let width = parent["width"] as? UInt64 {
keys.append("Dimensions")
dic["Dimensions"] = "\(width)x\(height)"
}
if let location = json["location"] as? [String: Any], let latitude = location["latitude"] as? Double, let longitude = location["longitude"] as? Double {
OneDriveFileProvider.decimalFormatter.numberStyle = .decimal
OneDriveFileProvider.decimalFormatter.maximumFractionDigits = 5
keys.append("Location")
let latStr = OneDriveFileProvider.decimalFormatter.string(from: NSNumber(value: latitude))
let longStr = OneDriveFileProvider.decimalFormatter.string(from: NSNumber(value: longitude))
dic["Location"] = "\(latStr), \(longStr)"
}
if let parent = json["image"] as? [String: Any] ?? json["video"] as? [String: Any], let duration = parent["duration"] as? UInt64 {
keys.append("Duration")
dic["Duration"] = OneDriveFileProvider.formatshort(interval: TimeInterval(duration) / 1000)
}
if let timeTakenStr = json["takenDateTime"] as? String, let timeTaken = self.resolve(dateString: timeTakenStr) {
keys.append("Date taken")
OneDriveFileProvider.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dic["Date taken"] = OneDriveFileProvider.dateFormatter.string(from: timeTaken)
}
if let photo = json["photo"] as? [String: Any] {
if let devicemake = photo["cameraMake"] as? String {
keys.append("Device make")
dic["Device make"] = devicemake
}
if let devicemodel = photo["cameraModel"] as? String {
keys.append("Device model")
dic["Device model"] = devicemodel
}
if let focallen = photo["focalLength"] as? Double {
keys.append("Focal length")
dic["Focal length"] = focallen
}
if let fnum = photo["fNumber"] as? Double {
keys.append("F number")
dic["F number"] = fnum
}
if let expNom = photo["exposureNumerator"] as? Double, let expDen = photo["exposureDenominator"] as? Double {
keys.append("Exposure time")
dic["Exposure time"] = "\(Int(expNom))/\(Int(expDen))"
}
if let iso = photo["iso"] as? Int64 {
keys.append("ISO speed")
dic["ISO speed"] = iso
}
}
if let audio = json["audio"] as? [String: Any] {
for (key, value) in audio {
if key == "bitrate" || key == "isVariableBitrate" { continue }
let casedKey = spaceCamelCase(key)
keys.append(casedKey)
dic[casedKey] = value
}
}
if let video = json["video"] as? [String: Any] {
if let bitRate = video["bitrate"] as? Int {
keys.append("Bitrate")
dic["Bitrate"] = bitRate
}
}
return (dic, keys)
}
func delegateNotify(_ operation: FileOperationType, error: Error?) {
DispatchQueue.main.async(execute: {
if error == nil {
self.delegate?.fileproviderSucceed(self, operation: operation)
} else {
self.delegate?.fileproviderFailed(self, operation: operation)
}
})
}
}
+11 -8
View File
@@ -35,11 +35,11 @@ public final class WebDavFileObject: FileObject {
/// Because this class uses NSURLSession, it's necessary to disable App Transport Security
/// in case of using this class with unencrypted HTTP connection.
open class WebDAVFileProvider: NSObject, FileProviderBasicRemote {
open class WebDAVFileProvider: FileProviderBasicRemote {
open static let type: String = "WebDAV"
open let isPathRelative: Bool = true
open let isPathRelative: Bool
open let baseURL: URL?
open var currentPath: String = ""
open var currentPath: String
public var dispatch_queue: DispatchQueue {
willSet {
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
@@ -48,8 +48,8 @@ open class WebDAVFileProvider: NSObject, FileProviderBasicRemote {
public weak var delegate: FileProviderDelegate?
open let credential: URLCredential?
open private(set) var cache: URLCache?
public var useCache: Bool = false
public var validatingCache: Bool = true
public var useCache: Bool
public var validatingCache: Bool
fileprivate var _session: URLSession?
fileprivate var sessionDelegate: SessionDelegate?
@@ -71,10 +71,13 @@ open class WebDAVFileProvider: NSObject, FileProviderBasicRemote {
return nil
}
self.baseURL = baseURL
dispatch_queue = DispatchQueue(label: "FileProvider.\(WebDAVFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
//let url = baseURL.uw_absoluteString
self.credential = credential
self.isPathRelative = true
self.currentPath = ""
self.useCache = false
self.validatingCache = true
self.cache = cache
self.credential = credential
dispatch_queue = DispatchQueue(label: "FileProvider.\(WebDAVFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
}
deinit {