Compare commits

...

10 Commits

Author SHA1 Message Date
Amir Abbas Mousavian b121fe6182 rectified macOS and tvOS version check 2017-01-31 01:42:03 +03:30
Amir Abbas Mousavian 29b7f720f5 Added iCloud provider change monitor and CloudOperationHandle
- fixed copyItem functions for local interoperability
2017-01-31 01:35:38 +03:30
Amir Abbas Mousavian 067e14b76f Added CloudFileProvider and coordination to LocalFileProvider
- changed `operation_queue` type to `OperationQueue`
- added `LocalFileProvider.init(sharedContainerId:)`
- added `destination(ofSymbolicLink:)` to LocalFileProvider
- added expirationDate to `temporaryLink(to:)` methods
- known issue: `CloudFileProvider` monitoring is not implemented
- known issue: `CloudFileProvider` copy from local and to local works imperfect
2017-01-30 18:51:06 +03:30
Amir Abbas Mousavian 09e39ada82 Resolved issue #24, off-by-one bug in Range header 2017-01-30 18:37:25 +03:30
Amir Abbas 6889b302b7 Removed CloudProvider due to incompleteness 2017-01-27 02:47:11 +03:30
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
15 changed files with 1678 additions and 181 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.11.0"
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?
+28 -10
View File
@@ -35,6 +35,9 @@
792572411DF23BDA006A1526 /* LocalHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 792572401DF23BDA006A1526 /* LocalHelper.swift */; };
792572421DF23BDA006A1526 /* LocalHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 792572401DF23BDA006A1526 /* LocalHelper.swift */; };
792572431DF23BDA006A1526 /* LocalHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 792572401DF23BDA006A1526 /* LocalHelper.swift */; };
79480FF61E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79480FF51E3ABDD0007E7275 /* CloudFileProvider.swift */; };
79480FF71E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79480FF51E3ABDD0007E7275 /* CloudFileProvider.swift */; };
79480FF81E3ABDD0007E7275 /* CloudFileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79480FF51E3ABDD0007E7275 /* CloudFileProvider.swift */; };
794C21FE1D58912A00EC49B8 /* DropboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */; };
794C21FF1D58912A00EC49B8 /* DropboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */; };
794C22001D58912A00EC49B8 /* DropboxHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */; };
@@ -98,17 +101,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 */; };
@@ -124,6 +130,7 @@
7924B1921D89DAE000589DB7 /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
7924B1A81D89F79200589DB7 /* FPSStreamTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FPSStreamTask.swift; sourceTree = "<group>"; };
792572401DF23BDA006A1526 /* LocalHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalHelper.swift; sourceTree = "<group>"; };
79480FF51E3ABDD0007E7275 /* CloudFileProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudFileProvider.swift; sourceTree = "<group>"; };
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropboxHelper.swift; sourceTree = "<group>"; };
794C22091D5893F800EC49B8 /* SMB2Notification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2Notification.swift; sourceTree = "<group>"; };
794C220D1D591A4B00EC49B8 /* SMB2QueryTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMB2QueryTypes.swift; sourceTree = "<group>"; };
@@ -166,6 +173,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 +186,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 +197,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 +208,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,15 +285,18 @@
children = (
7924B18B1D89DAE000589DB7 /* AEXML */,
799396991D48C02300086753 /* SMBTypes */,
799396931D48C02300086753 /* DropboxFileProvider.swift */,
794C21FD1D58912A00EC49B8 /* DropboxHelper.swift */,
799396941D48C02300086753 /* FileProvider.h */,
799396951D48C02300086753 /* FileProvider.swift */,
79F5745A1DFDB10A00179ABF /* FileObject.swift */,
799396961D48C02300086753 /* LocalFileProvider.swift */,
792572401DF23BDA006A1526 /* LocalHelper.swift */,
79480FF51E3ABDD0007E7275 /* CloudFileProvider.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 */,
@@ -476,12 +485,15 @@
799396D41D48C02300086753 /* SMB2Tree.swift in Sources */,
7924B1A21D89DAE000589DB7 /* Options.swift in Sources */,
792572411DF23BDA006A1526 /* LocalHelper.swift in Sources */,
79480FF61E3ABDD0007E7275 /* CloudFileProvider.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 */,
@@ -513,12 +525,15 @@
799396D51D48C02300086753 /* SMB2Tree.swift in Sources */,
7924B1A31D89DAE000589DB7 /* Options.swift in Sources */,
792572421DF23BDA006A1526 /* LocalHelper.swift in Sources */,
79480FF71E3ABDD0007E7275 /* CloudFileProvider.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 */,
@@ -550,12 +565,15 @@
799396D61D48C02300086753 /* SMB2Tree.swift in Sources */,
7924B1A41D89DAE000589DB7 /* Options.swift in Sources */,
792572431DF23BDA006A1526 /* LocalHelper.swift in Sources */,
79480FF81E3ABDD0007E7275 /* CloudFileProvider.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 +603,7 @@
799396601D48B7BF00086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.8.3;
BUNDLE_VERSION_STRING = 0.11.0;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
@@ -615,7 +633,7 @@
799396611D48B7BF00086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.8.3;
BUNDLE_VERSION_STRING = 0.11.0;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
+25 -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.
- [ ] **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,17 +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:
@@ -117,7 +130,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 +345,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 +362,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:
+479
View File
@@ -0,0 +1,479 @@
//
// CloudFileProvider.swift
// FileProvider
//
// Created by Amir Abbas Mousavian.
// Copyright © 2017 Mousavian. Distributed under MIT license.
//
import Foundation
open class CloudFileProvider: LocalFileProvider {
public var type: String {
return "iCloudDrive"
}
/// Actually is readonly
override open var isCoorinating: Bool {
get {
return true
}
set {
return
}
}
open var containerId: String?
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
}
self.containerId = containerId
let baseURL = ubiquityURL.standardized.appendingPathComponent("Documents")
super.init(baseURL: baseURL)
self.isCoorinating = true
dispatch_queue = DispatchQueue(label: "FileProvider.\(self.type)", attributes: DispatchQueue.Attributes.concurrent)
operation_queue = OperationQueue()
operation_queue.name = "FileProvider.\(self.type).Operation"
fileManager.url(forUbiquityContainerIdentifier: containerId)
opFileManager.url(forUbiquityContainerIdentifier: containerId)
try? fileManager.createDirectory(at: baseURL, withIntermediateDirectories: true)
}
open override func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
dispatch_queue.async {
let pathURL = self.absoluteURL(path)
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "%K BEGINSWITH %@", NSMetadataItemPathKey, pathURL.path)
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
var finishObserver: NSObjectProtocol?
finishObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
defer {
query.stop()
NotificationCenter.default.removeObserver(finishObserver!)
}
guard let results = query.results as? [NSMetadataItem] else {
return
}
query.disableUpdates()
var contents = [FileObject]()
for result in results {
guard let attribs = result.values(forAttributes: [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]) else {
continue
}
guard let url = (attribs[NSMetadataItemURLKey] as? URL)?.standardized, url.deletingLastPathComponent().path.trimmingCharacters(in: pathTrimSet) == pathURL.path.trimmingCharacters(in: pathTrimSet) else {
continue
}
if let file = self.mapFileObject(attributes: attribs) {
contents.append(file)
}
}
completionHandler(contents, nil)
})
query.start()
}
}
/// iCloud Storage size and free space is unavailable, it returns local space
open override func storageProperties(completionHandler: (@escaping (_ total: Int64, _ used: Int64) -> Void)) {
super.storageProperties(completionHandler: completionHandler)
}
open override func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
dispatch_queue.async {
let pathURL = self.absoluteURL(path)
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "%K LIKE %@", NSMetadataItemPathKey, pathURL.path)
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
var finishObserver: NSObjectProtocol?
finishObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
defer {
query.stop()
NotificationCenter.default.removeObserver(finishObserver!)
}
query.disableUpdates()
guard let result = (query.results as? [NSMetadataItem])?.first, let attribs = result.values(forAttributes: [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]) else {
let error = self.throwError(path, code: CocoaError.fileNoSuchFile)
completionHandler(nil, error)
return
}
if let file = self.mapFileObject(attributes: attribs) {
completionHandler(file, nil)
} else {
let noFileError = self.throwError(path, code: CocoaError.fileNoSuchFile)
completionHandler(nil, noFileError)
}
})
query.start()
}
}
@discardableResult
open override func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let r = super.create(folder: folderName, at: atPath, completionHandler: completionHandler)
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
}
@discardableResult
open override func create(file fileName: String, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let r = super.create(file: fileName, at: atPath, contents: data, completionHandler: completionHandler)
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
}
@discardableResult
open override func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let r = super.moveItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler)
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
}
@discardableResult
open override func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let r = super.copyItem(path: path, to: toPath, overwrite: overwrite, completionHandler: completionHandler)
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
}
@discardableResult
open override func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let r = super.removeItem(path: path, completionHandler: completionHandler)
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
}
@discardableResult
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)
operation_queue.addOperation {
let tempFolder: URL
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) {
tempFolder = FileManager.default.temporaryDirectory
} else {
tempFolder = URL(fileURLWithPath: NSTemporaryDirectory())
}
let tmpFile = tempFolder.appendingPathComponent(UUID().uuidString)
do {
try self.opFileManager.copyItem(at: localFile, to: tmpFile)
let toUrl = self.absoluteURL(toPath)
try self.opFileManager.setUbiquitous(true, itemAt: tmpFile, destinationURL: toUrl)
completionHandler?(nil)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e {
if self.opFileManager.fileExists(atPath: tmpFile.path) {
try? self.opFileManager.removeItem(at: tmpFile)
}
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
return CloudOperationHandle(operationType: opType, baseURL: self.baseURL)
}
@discardableResult
open override func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.copy(source: path, destination: toLocalURL.absoluteString)
do {
try self.opFileManager.startDownloadingUbiquitousItem(at: self.absoluteURL(path))
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderFailed(self, operation: opType)
})
return nil
}
let r = super.copyItem(path: path, toLocalURL: toLocalURL, completionHandler: completionHandler)
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
}
@discardableResult
open override func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
let r = super.contents(path: path, completionHandler: completionHandler)
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
}
@discardableResult
open override func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
let r = super.contents(path: path, offset: offset, length: length, completionHandler: completionHandler)
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
}
@discardableResult
open override func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let r = super.writeContents(path: path, contents: data, atomically: atomically, overwrite: overwrite, completionHandler: completionHandler)
return CloudOperationHandle(operationType: r!.operationType, baseURL: self.baseURL)
}
open override func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
dispatch_queue.async {
let pathURL = self.absoluteURL(path)
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "(%K BEGINSWITH %@) && (%K LIKE %@)", NSMetadataItemPathKey, pathURL.path, NSMetadataItemFSNameKey, query)
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
var lastReportedCount = 0
if let foundItemHandler = foundItemHandler {
var updateObserver: NSObjectProtocol?
updateObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryGatheringProgress, object: query, queue: nil, using: { (notification) in
query.disableUpdates()
guard query.resultCount > lastReportedCount else { return }
for index in lastReportedCount..<query.resultCount {
guard let attribs = (query.result(at: index) as? NSMetadataItem)?.values(forAttributes: [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]) else {
continue
}
guard let url = (attribs[NSMetadataItemURLKey] as? URL)?.standardized, recursive || url.deletingLastPathComponent().path.trimmingCharacters(in: pathTrimSet) == pathURL.path.trimmingCharacters(in: pathTrimSet) else {
continue
}
if let file = self.mapFileObject(attributes: attribs) {
foundItemHandler(file)
}
}
lastReportedCount = query.resultCount
query.enableUpdates()
})
}
var finishObserver: NSObjectProtocol?
finishObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
defer {
query.stop()
NotificationCenter.default.removeObserver(finishObserver!)
}
guard let results = query.results as? [NSMetadataItem] else {
return
}
query.disableUpdates()
var contents = [FileObject]()
for result in results {
guard let attribs = result.values(forAttributes: [NSMetadataItemURLKey, NSMetadataItemFSNameKey, NSMetadataItemPathKey, NSMetadataItemFSSizeKey, NSMetadataItemContentTypeTreeKey, NSMetadataItemFSCreationDateKey, NSMetadataItemFSContentChangeDateKey]) else {
continue
}
guard let url = (attribs[NSMetadataItemURLKey] as? URL)?.standardized, recursive || url.deletingLastPathComponent().path.trimmingCharacters(in: pathTrimSet) == pathURL.path.trimmingCharacters(in: pathTrimSet) else {
continue
}
if let file = self.mapFileObject(attributes: attribs) {
contents.append(file)
}
}
completionHandler(contents, nil)
})
query.start()
}
}
fileprivate var monitors = [URL: (NSMetadataQuery, NSObjectProtocol)]()
//
open override func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
self.unregisterNotifcation(path: path)
let pathURL = self.absoluteURL(path)
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "(%K BEGINSWITH %@)", NSMetadataItemPathKey, pathURL.path)
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
let updateObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidUpdate, object: query, queue: nil, using: { (notification) in
query.disableUpdates()
eventHandler()
query.enableUpdates()
})
query.start()
monitors[pathURL] = (query, updateObserver)
}
open override func unregisterNotifcation(path: String) {
let key = absoluteURL(path)
guard let (query, observer) = monitors[key] else {
return
}
query.disableUpdates()
query.stop()
monitors.removeValue(forKey: key)
NotificationCenter.default.removeObserver(observer)
}
open override func isRegisteredForNotification(path: String) -> Bool {
return monitors[absoluteURL(path)] != nil
}
/// may return nil
open override func copy(with zone: NSZone? = nil) -> Any {
let copy = CloudFileProvider(containerId: self.containerId)
copy?.currentPath = self.currentPath
copy?.delegate = self.delegate
copy?.fileOperationDelegate = self.fileOperationDelegate
copy?.isPathRelative = self.isPathRelative
return copy as Any
}
fileprivate func mapFileObject(attributes attribs: [String: Any]) -> FileObject? {
guard let url = (attribs[NSMetadataItemURLKey] as? URL)?.standardized, let name = attribs[NSMetadataItemFSNameKey] as? String else {
return nil
}
let path = self.relativePathOf(url: url)
let file = FileObject(absoluteURL: url, name: name, path: path)
file.size = (attribs[NSMetadataItemFSSizeKey] as? NSNumber)?.int64Value ?? -1
file.creationDate = attribs[NSMetadataItemFSCreationDateKey] as? Date
file.modifiedDate = attribs[NSMetadataItemFSContentChangeDateKey] as? Date
let isFolder = (attribs[NSMetadataItemContentTypeTreeKey] as? [String])?.contains("public.folder") ?? false
let isSymbolic = (attribs[NSMetadataItemContentTypeTreeKey] as? [String])?.contains("public.symlink") ?? false
file.type = isFolder ? .directory : (isSymbolic ? .symbolicLink : .regular)
return file
}
/// Removes local copy of file, but spares cloud copy
open func evictItem(path: String, completionHandler: SimpleCompletionHandler) {
operation_queue.addOperation {
do {
try self.opFileManager.evictUbiquitousItem(at: self.absoluteURL(path))
completionHandler?(nil)
} catch let e {
completionHandler?(e)
}
}
}
open func temporaryLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
operation_queue.addOperation {
do {
var expiration: NSDate?
let url = try self.opFileManager.url(forPublishingUbiquitousItemAt: self.absoluteURL(path), expiration: &expiration)
completionHandler(url, nil, expiration as Date?, nil)
} catch let e {
completionHandler(nil, nil, nil, e)
}
}
}
}
open class CloudOperationHandle: OperationHandle {
public let baseURL: URL?
public let operationType: FileOperationType
init (operationType: FileOperationType, baseURL: URL?) {
self.baseURL = baseURL
self.operationType = operationType
}
private var sourceURL: URL? {
guard let source = operationType.source, let baseURL = baseURL else { return nil }
return source.hasPrefix("file://") ? URL(fileURLWithPath: source) : baseURL.appendingPathComponent(source)
}
private var destURL: URL? {
guard let dest = operationType.destination, let baseURL = baseURL else { return nil }
return dest.hasPrefix("file://") ? URL(fileURLWithPath: dest) : baseURL.appendingPathComponent(dest)
}
/// Caution: may put pressure on CPU, may have latency
open var bytesSoFar: Int64 {
assert(!Thread.isMainThread, "Don't run \(#function) method on main thread")
guard let url = destURL ?? sourceURL, let item = CloudOperationHandle.getMetadataItem(url: url) else { return 0 }
let downloaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? Double ?? 0
let uploaded = item.value(forAttribute: NSMetadataUbiquitousItemPercentUploadedKey) as? Double ?? 0
guard let size = item.value(forAttribute: NSMetadataItemFSSizeKey) as? Int64 else { return -1 }
if (downloaded == 0 || downloaded == 100) && (uploaded > 0 && uploaded < 100) {
return Int64(uploaded * (Double(size) / 100))
} else if (uploaded == 0 || uploaded == 100) && (downloaded > 0 && downloaded < 100) {
return Int64(downloaded * (Double(size) / 100))
} else if uploaded == 100 || downloaded == 100 {
return size
}
return 0
}
/// Caution: may put pressure on CPU, may have latency
open var totalBytes: Int64 {
assert(!Thread.isMainThread, "Don't run \(#function) method on main thread")
guard let url = destURL ?? sourceURL, let item = CloudOperationHandle.getMetadataItem(url: url) else { return -1 }
return item.value(forAttribute: NSMetadataItemFSSizeKey) as? Int64 ?? -1
}
open var inProgress: Bool {
guard let url = destURL ?? sourceURL, let item = CloudOperationHandle.getMetadataItem(url: url) else { return false }
let downloadStatus = item.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String ?? NSMetadataUbiquitousItemDownloadingStatusNotDownloaded
let isUploading = item.value(forAttribute: NSMetadataUbiquitousItemIsUploadingKey) as? Bool ?? false
return downloadStatus == NSMetadataUbiquitousItemDownloadingStatusCurrent || isUploading
}
/// Not usable in local provider
open func cancel() -> Bool {
return false
}
fileprivate static func getMetadataItem(url: URL) -> NSMetadataItem? {
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "(%K LIKE %@)", NSMetadataItemPathKey, url.path)
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
var item: NSMetadataItem?
let group = DispatchGroup()
var finishObserver: NSObjectProtocol?
finishObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: nil, using: { (notification) in
defer {
query.stop()
group.leave()
NotificationCenter.default.removeObserver(finishObserver!)
}
if query.resultCount > 0 {
item = query.result(at: 0) as? NSMetadataItem
}
query.disableUpdates()
})
group.enter()
query.start()
_ = group.wait(timeout: DispatchTime.now() + 30)
return item
}
}
+52 -29
View File
@@ -13,43 +13,57 @@ 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 dispatch_queue: DispatchQueue {
open var currentPath: String
open let apiURL: URL
open let contentURL: URL
open var dispatch_queue: DispatchQueue
open var operation_queue: OperationQueue {
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 = false
public var validatingCache: Bool = true
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)
_session = URLSession(configuration: config, delegate: sessionDelegate as URLSessionDelegate?, delegateQueue: self.operation_queue)
}
return _session!
}
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)
operation_queue = OperationQueue()
operation_queue.name = "FileProvider.\(DropboxFileProvider.type).Operation"
}
deinit {
@@ -63,7 +77,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 +100,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 +153,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 +201,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,12 +232,12 @@ 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")
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
request.setValue("bytes=\(offset)-\(offset + length - 1)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}
@@ -278,8 +292,15 @@ extension DropboxFileProvider: FileProviderReadWrite {
}
extension DropboxFileProvider {
@available(*, deprecated, message: "Use DropboxFileProvider.temporaryLink(to:, completionHandler: (URL?, DropboxFileObject?, Date?, Error?)) function instead.")
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")!
self.temporaryLink(to: path) { (url, file, _, error) in
completionHandler(url, file, error)
}
}
open func temporaryLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: DropboxFileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
let url = URL(string: "files/get_temporary_link", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(credential?.password ?? "")", forHTTPHeaderField: "Authorization")
@@ -302,13 +323,15 @@ extension DropboxFileProvider {
}
}
}
completionHandler(link, fileObject, dbError ?? error)
let expiration: Date? = link != nil ? Date(timeIntervalSinceNow: 4 * 60 * 60) : nil
completionHandler(link, fileObject, expiration, dbError ?? error)
})
task.resume()
}
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 +393,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 +433,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 {
+37 -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
@@ -19,20 +19,32 @@ public typealias SimpleCompletionHandler = ((_ error: Error?) -> Void)?
public protocol FileProviderBasic: class {
static var type: String { get }
var type: String { get }
var isPathRelative: Bool { get }
var baseURL: URL? { get }
var currentPath: String { get set }
var dispatch_queue: DispatchQueue { get set }
var operation_queue: OperationQueue { get set }
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 extension FileProviderBasic {
public var maximumOperationTasks: Int {
get {
return operation_queue.maxConcurrentOperationCount
}
set {
operation_queue.maxConcurrentOperationCount = newValue
}
}
}
public protocol FileProviderBasicRemote: FileProviderBasic {
@@ -181,32 +193,39 @@ public protocol FileProviderMonitor: FileProviderBasic {
public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite, NSCopying {
}
internal 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 +368,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 +442,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)
+317 -85
View File
@@ -9,37 +9,73 @@
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 var operation_queue: OperationQueue
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential? = nil
open internal(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
/// Forces file operations to use NSFileCoordinating, should be set true if:
/// - Files are on ubiquity(iCloud) container
/// - Multiple processes are accessing same file, recommended always in macOS and when using app extensions in iOS/tvOS (shared container)
open var isCoorinating: Bool
/// 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 convenience init ? (sharedContainerId: String, directory: FileManager.SearchPathDirectory = .userDirectory) {
guard let baseURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: sharedContainerId) else {
return nil
}
var finalBaseURL = baseURL
switch directory {
case .documentDirectory:
finalBaseURL = baseURL.appendingPathComponent("Documents")
case .libraryDirectory:
finalBaseURL = baseURL.appendingPathComponent("Library")
case .cachesDirectory:
finalBaseURL = baseURL.appendingPathComponent("Library/Caches")
case .applicationSupportDirectory:
finalBaseURL = baseURL.appendingPathComponent("Library/Application%20support")
default:
break
}
self.init(baseURL: finalBaseURL)
try? fileManager.createDirectory(at: finalBaseURL, withIntermediateDirectories: true)
}
public init (baseURL: URL) {
self.baseURL = baseURL
self.isPathRelative = true
self.currentPath = ""
self.credential = nil
self.isCoorinating = false
dispatch_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
operation_queue = DispatchQueue(label: "FileProvider.\(LocalFileProvider.type).Operation", attributes: [])
operation_queue = OperationQueue()
operation_queue.name = "FileProvider.\(LocalFileProvider.type).Operation"
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
opFileManager.delegate = fileProviderManagerDelegate
}
open static func defaultBaseURL() -> URL {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true);
return URL(fileURLWithPath: paths[0])
@available(*, deprecated, message: "Overriding this method has no effect anymore.")
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)) {
@@ -51,7 +87,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
return LocalFileObject(fileWithPath: path, relativeTo: self.baseURL)
})
completionHandler(filesAttributes, nil)
} catch let e as NSError {
} catch let e {
completionHandler([], e)
}
}
@@ -75,109 +111,204 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
@discardableResult
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.create(path: (atPath as NSString).appendingPathComponent(folderName) + "/")
operation_queue.async {
let url = self.absoluteURL(atPath).appendingPathComponent(folderName)
let operationHandler: (URL) -> Void = { url in
do {
try self.opFileManager.createDirectory(at: self.absoluteURL(atPath).appendingPathComponent(folderName), withIntermediateDirectories: true, attributes: [:])
try self.opFileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: [:])
completionHandler?(nil)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e as NSError {
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
}
if isCoorinating {
let intent = NSFileAccessIntent.writingIntent(with: url, options: .forReplacing)
self.coordinated(intents: [intent], completionHandler: operationHandler, errorHandler: { error in
completionHandler?(error)
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
}
})
} else {
operation_queue.addOperation {
operationHandler(url)
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
@discardableResult
open func create(file fileName: String, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.create(path: (atPath as NSString).appendingPathComponent(fileName))
operation_queue.async {
let fileURL = self.absoluteURL(atPath).appendingPathComponent(fileName)
let success = self.opFileManager.createFile(atPath: fileURL.path, contents: data, attributes: nil)
let url = self.absoluteURL(atPath).appendingPathComponent(fileName, isDirectory: false)
let operationHandler: (URL) -> Void = { url in
let success = self.opFileManager.createFile(atPath: url.path, contents: data, attributes: nil)
if success {
completionHandler?(nil)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
}
} else {
completionHandler?(self.throwError(atPath, code: URLError.cannotCreateFile as FoundationErrorEnum))
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
}
if isCoorinating {
let intent = NSFileAccessIntent.writingIntent(with:url, options: .forReplacing)
self.coordinated(intents: [intent], completionHandler: operationHandler, errorHandler: { error in
completionHandler?(error)
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
}
})
} else {
operation_queue.addOperation {
operationHandler(url)
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
@discardableResult
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.move(source: path, destination: toPath)
operation_queue.async {
if !overwrite && self.fileManager.fileExists(atPath: self.absoluteURL(toPath).path) {
let sourceUrl = self.absoluteURL(path)
let destUrl = self.absoluteURL(toPath)
let sourceIntent = NSFileAccessIntent.writingIntent(with: sourceUrl, options: .forDeleting)
let destIntent = NSFileAccessIntent.writingIntent(with: destUrl, options: .forReplacing)
let operationHandler: (URL, URL) -> Void = { sourceUrl, destUrl in
if !overwrite && self.fileManager.fileExists(atPath: destUrl.path) {
completionHandler?(self.throwError(toPath, code: URLError.cannotMoveFile as FoundationErrorEnum))
return
}
do {
try self.opFileManager.moveItem(at: self.absoluteURL(path), to: self.absoluteURL(toPath))
try self.opFileManager.moveItem(at: sourceUrl, to: destUrl)
completionHandler?(nil)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e as NSError {
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
}
if isCoorinating {
coordinated(intents: [sourceIntent, destIntent], completionHandler: operationHandler) { (error) in
completionHandler?(error)
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
}
}
} else {
operation_queue.addOperation {
operationHandler(sourceUrl, destUrl)
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
@discardableResult
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.copy(source: path, destination: toPath)
operation_queue.async {
if !overwrite && self.fileManager.fileExists(atPath: self.absoluteURL(toPath).path) {
completionHandler?(self.throwError(toPath, code: URLError.cannotWriteToFile as FoundationErrorEnum))
let sourceUrl = self.absoluteURL(path)
let destUrl = self.absoluteURL(toPath)
let sourceIntent = NSFileAccessIntent.readingIntent(with: sourceUrl, options: .withoutChanges)
let destIntent = NSFileAccessIntent.writingIntent(with: destUrl, options: .forDeleting)
let operationHandler: (URL, URL) -> Void = { sourceUrl, destUrl in
if !overwrite && self.fileManager.fileExists(atPath: destUrl.path) {
completionHandler?(self.throwError(toPath, code: URLError.cannotMoveFile as FoundationErrorEnum))
return
}
do {
try self.opFileManager.copyItem(at: self.absoluteURL(path), to: self.absoluteURL(toPath))
try self.opFileManager.copyItem(at: sourceUrl, to: destUrl)
completionHandler?(nil)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e as NSError {
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
}
if isCoorinating {
coordinated(intents: [sourceIntent, destIntent], moving: true, completionHandler: operationHandler) { (error) in
completionHandler?(error)
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
}
}
} else {
operation_queue.addOperation {
operationHandler(sourceUrl, destUrl)
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
@discardableResult
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.remove(path: path)
operation_queue.async {
let url = self.absoluteURL(path)
let operationHandler: (URL) -> Void = { url in
do {
try self.opFileManager.removeItem(at: self.absoluteURL(path))
let successfulSecurityScopedResourceAccess = url.startAccessingSecurityScopedResource()
try self.opFileManager.removeItem(at: url)
if successfulSecurityScopedResourceAccess {
url.stopAccessingSecurityScopedResource()
}
completionHandler?(nil)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e as NSError {
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
}
if isCoorinating {
let intent = NSFileAccessIntent.writingIntent(with:url, options: .forReplacing)
self.coordinated(intents: [intent], completionHandler: operationHandler, errorHandler: { error in
completionHandler?(error)
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
}
})
} else {
operation_queue.addOperation {
operationHandler(url)
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
@@ -185,18 +316,18 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
open 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)
operation_queue.async {
operation_queue.addOperation {
do {
try self.opFileManager.copyItem(at: localFile, to: self.absoluteURL(toPath))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e as NSError {
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
@@ -205,18 +336,18 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
@discardableResult
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.copy(source: path, destination: toLocalURL.absoluteString)
operation_queue.async {
operation_queue.addOperation {
do {
try self.opFileManager.copyItem(at: self.absoluteURL(path), to: toLocalURL)
completionHandler?(nil)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
} catch let e as NSError {
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
})
}
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
@@ -224,11 +355,29 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
@discardableResult
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
dispatch_queue.async {
let data = self.fileManager.contents(atPath: self.absoluteURL(path).path)
let opType = FileOperationType.fetch(path: path)
let url = self.absoluteURL(path)
let operationHandler: (URL) -> Void = { url in
let data = self.fileManager.contents(atPath: url.path)
completionHandler(data, nil)
}
return nil
if isCoorinating {
let intent = NSFileAccessIntent.readingIntent(with: url, options: .withoutChanges)
coordinated(intents: [intent], completionHandler: operationHandler, errorHandler: { error in
completionHandler(nil, error)
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
}
})
} else {
dispatch_queue.async {
operationHandler(url)
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
@discardableResult
@@ -236,14 +385,16 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
if length < 0 {
return self.contents(path: path, completionHandler: completionHandler)
}
let opType = FileOperationType.fetch(path: path)
dispatch_queue.async {
let aPath = self.absoluteURL(path).path
guard self.fileManager.fileExists(atPath: aPath) && !self.absoluteURL(path).fileIsDirectory else {
let url = self.absoluteURL(path)
let operationHandler: (URL) -> Void = { url in
guard self.fileManager.fileExists(atPath: url.path) && !url.fileIsDirectory else {
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
return
}
guard let handle = FileHandle(forReadingAtPath: aPath) else {
guard let handle = FileHandle(forReadingAtPath: url.path) else {
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
return
}
@@ -253,14 +404,29 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
handle.seek(toFileOffset: UInt64(offset))
let data = handle.readData(ofLength: length)
completionHandler(data, nil)
}
if isCoorinating {
let intent = NSFileAccessIntent.readingIntent(with: url, options: .withoutChanges)
coordinated(intents: [intent], completionHandler: operationHandler, errorHandler: { error in
completionHandler(nil, error)
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
}
})
} else {
dispatch_queue.async {
operationHandler(url)
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
@discardableResult
open func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.modify(path: path)
let url = self.absoluteURL(path)
var options: Data.WritingOptions = []
if atomically {
options.insert(.atomic)
@@ -268,12 +434,36 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
if overwrite {
options.insert(.withoutOverwriting)
}
operation_queue.async {
try? data.write(to: self.absoluteURL(path), options: atomically ? [.atomic] : [])
DispatchQueue.main.async(execute: {
self.delegate?.fileproviderSucceed(self, operation: opType)
})
let operationHandler: (URL) -> Void = { url in
do {
try data.write(to: url, options: atomically ? [.atomic] : [])
completionHandler?(nil)
DispatchQueue.main.async{
self.delegate?.fileproviderSucceed(self, operation: opType)
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
}
}
}
if isCoorinating {
let intent = NSFileAccessIntent.writingIntent(with: url, options: .forReplacing)
coordinated(intents: [intent], completionHandler: operationHandler, errorHandler: { error in
completionHandler?(error)
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: opType)
}
})
} else {
operation_queue.addOperation {
operationHandler(url)
}
}
return LocalOperationHandle(operationType: opType, baseURL: self.baseURL)
}
@@ -340,18 +530,60 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor {
public extension LocalFileProvider {
public func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
operation_queue.async {
operation_queue.addOperation {
do {
try self.opFileManager.createSymbolicLink(at: self.absoluteURL(path), withDestinationURL: self.absoluteURL(destPath))
completionHandler?(nil)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderSucceed(self, operation: .link(link: path, target: destPath))
})
} catch let e as NSError {
}
} catch let e {
completionHandler?(e)
DispatchQueue.main.async(execute: {
DispatchQueue.main.async {
self.delegate?.fileproviderFailed(self, operation: .link(link: path, target: destPath))
})
}
}
}
}
public func destination(ofSymbolicLink path: String, completionHandler: @escaping (_ url: URL?, _ error: Error?) -> Void) {
dispatch_queue.async {
do {
let destPath = try self.opFileManager.destinationOfSymbolicLink(atPath: self.absoluteURL(path).path)
let destUrl = URL(fileURLWithPath: destPath)
completionHandler(destUrl, nil)
} catch let e{
completionHandler(nil, e)
}
}
}
}
internal extension LocalFileProvider {
func coordinated(intents: [NSFileAccessIntent], completionHandler: @escaping (_ url: URL) -> Void, errorHandler: ((_ error: Error) -> Void)? = nil) {
let coordinator = NSFileCoordinator(filePresenter: nil)
coordinator.coordinate(with: intents, queue: operation_queue) { (error) in
if let error = error {
errorHandler?(error)
return
}
completionHandler(intents[0].url)
}
}
func coordinated(intents: [NSFileAccessIntent], moving: Bool = false, completionHandler: @escaping (_ sourceUrl: URL, _ destURL: URL) -> Void, errorHandler: ((_ error: Error) -> Void)? = nil) {
let coordinator = NSFileCoordinator(filePresenter: nil)
coordinator.coordinate(with: intents, queue: operation_queue) { (error) in
if let error = error {
errorHandler?(error)
return
}
if moving {
coordinator.item(at: intents[0].url, willMoveTo: intents[1].url)
}
completionHandler(intents[0].url, intents[1].url)
if moving {
coordinator.item(at: intents[0].url, didMoveTo: intents[1].url)
}
}
}
+1 -1
View File
@@ -190,7 +190,7 @@ open class LocalOperationHandle: OperationHandle {
public let operationType: FileOperationType
init (operationType: FileOperationType, baseURL: URL?) {
self.baseURL = baseURL ?? LocalFileProvider.defaultBaseURL()
self.baseURL = baseURL ?? URL(fileURLWithPath: "/")
self.operationType = operationType
}
+360
View File
@@ -0,0 +1,360 @@
//
// 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
open var operation_queue: OperationQueue {
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)
operation_queue = OperationQueue()
operation_queue.name = "FileProvider.\(DropboxFileProvider.type).Operation"
}
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 - 1)", 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)
}
})
}
}
+5 -1
View File
@@ -9,11 +9,13 @@
import Foundation
class SMBFileProvider: FileProvider, FileProviderMonitor {
open static var type: String = "Samba"
open var isPathRelative: Bool = true
open var baseURL: URL?
open var currentPath: String = ""
open var dispatch_queue: DispatchQueue
open var operation_queue: OperationQueue
open weak var delegate: FileProviderDelegate?
open let credential: URLCredential?
@@ -25,7 +27,9 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
}
self.baseURL = baseURL
dispatch_queue = DispatchQueue(label: "FileProvider.\(SMBFileProvider.type)", attributes: DispatchQueue.Attributes.concurrent)
//let url = baseURL.uw_absoluteString
operation_queue = OperationQueue()
operation_queue.name = "FileProvider.\(SMBFileProvider.type).Operation"
self.credential = credential
}
+18 -10
View File
@@ -35,21 +35,24 @@ 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 = ""
public var dispatch_queue: DispatchQueue {
open var currentPath: String
open var dispatch_queue: DispatchQueue
open var operation_queue: OperationQueue {
willSet {
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
}
}
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 +74,15 @@ 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)
operation_queue = OperationQueue()
operation_queue.name = "FileProvider.\(DropboxFileProvider.type).Operation"
}
deinit {
@@ -340,7 +348,7 @@ extension WebDAVFileProvider: FileProviderReadWrite {
var request = URLRequest(url: url)
request.httpMethod = "GET"
if length > 0 {
request.setValue("bytes=\(offset)-\(offset + length)", forHTTPHeaderField: "Range")
request.setValue("bytes=\(offset)-\(offset + length - 1)", forHTTPHeaderField: "Range")
} else if offset > 0 && length < 0 {
request.setValue("bytes=\(offset)-", forHTTPHeaderField: "Range")
}