Compare commits

...

3 Commits

Author SHA1 Message Date
Amir Abbas 194673b3b6 Added NSPredicate to searchFiles method
- public functions became open, now is overridable
- fixed urlCache documentation
2017-02-19 13:34:55 +03:30
Amir Abbas 194c8a41aa Fixed readme 2017-02-18 09:08:55 +03:30
Amir Abbas Mousavian c290377433 Updated Readme, travis.yml 2017-02-16 20:56:24 +03:30
15 changed files with 432 additions and 283 deletions
+1 -1
View File
@@ -77,4 +77,4 @@ deploy:
on:
repo: amosavian/$PROJECTNAME
tags: true
comdition: "$CARTHAGEDEPLOY = YES"
condition: "$CARTHAGEDEPLOY = YES"
+1 -1
View File
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
#
s.name = "FileProvider"
s.version = "0.12.9"
s.version = "0.14.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.
+2 -2
View File
@@ -597,7 +597,7 @@
799396601D48B7BF00086753 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.12.9;
BUNDLE_VERSION_STRING = 0.14.0;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
@@ -627,7 +627,7 @@
799396611D48B7BF00086753 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_VERSION_STRING = 0.12.9;
BUNDLE_VERSION_STRING = 0.14.0;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
+12 -6
View File
@@ -42,7 +42,7 @@ All functions are async calls and it wont block your main thread.
## Requirements
- **Swift 3**
- **Swift 3.0 +**
- iOS 8.0 , OSX 10.10
- XCode 8.0
@@ -93,7 +93,7 @@ Then you can do either:
* Copy Source folder to your project and Voila!
* Drop FileProvider.xcodeproj to you Xcode workspace and add the framework to your Embeded Binaries in target.
* Drop `FileProvider.xcodeproj` to you Xcode workspace and add the framework to your Embeded Binaries in target.
## Usage
@@ -118,7 +118,7 @@ Also for using group shared container:
```swift
let documentsProvider = LocalFileProvider(sharedContainerId: "group.yourcompany.appContainer")
// Replace your group identifier with string above
// Replace your group identifier
```
You can't change the base url later. and all paths are related to this base url by default.
@@ -158,7 +158,7 @@ override func viewDidLoad() {
documentsProvider.delegate = self as FileProviderDelegate
}
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperation) {
func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: FileOperationType) {
switch operation {
case .copy(source: let source, destination: let dest):
print("\(source) copied to \(dest).")
@@ -169,7 +169,7 @@ func fileproviderSucceed(_ fileProvider: FileProviderOperations, operation: File
}
}
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperation) {
func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileOperationType) {
switch operation {
case .copy(source: let source, destination: let dest):
print("copy of \(source) failed.")
@@ -180,7 +180,7 @@ func fileproviderFailed(_ fileProvider: FileProviderOperations, operation: FileO
}
}
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperation, progress: Float) {
func fileproviderProgress(_ fileProvider: FileProviderOperations, operation: FileOperationType, progress: Float) {
switch operation {
case .copy(source: let source, destination: let dest):
print("Copy\(source) to \(dest): \(progress * 100) completed.")
@@ -439,6 +439,12 @@ if documentsProvider..propertiesOfFile(path: file.path, completionHandler: { (pr
We would love for you to contribute to **FileProvider**, check the `LICENSE` file for more info.
Things to do:
- [ ] Implement Test-case (XCTest)
- [ ] Add Sample project for iOS
- [ ] Add Sample project for macOS
## Projects in use
* [EDM - Browse and Receive Files](https://itunes.apple.com/us/app/edm-browse-and-receive-files/id948397575?ls=1&mt=8)
+143 -99
View File
@@ -9,7 +9,7 @@
import Foundation
/**
Allows accessing to iCloud Drive stored files. Determine scope when initializing,to either access
Allows accessing to iCloud Drive stored files. Determine scope when initializing, to either access
to public documents folder or files stored as data.
To setup a functional iCloud container, please
@@ -87,7 +87,7 @@ open class CloudFileProvider: LocalFileProvider {
If the directory contains no entries or an error is occured, this method will return the empty array.
- Parameter path: path to target directory. If empty, `currentPath` value will be used.
- Parameter completionHandler: a block with result of directory entries or error.
- Parameter completionHandler: a closure with result of directory entries or error.
`contents`: An array of `FileObject` identifying the the directory entries.
`error`: Error returned by system.
*/
@@ -156,7 +156,7 @@ open class CloudFileProvider: LocalFileProvider {
If the directory contains no entries or an error is occured, this method will return the empty `FileObject`.
- Parameter path: path to target directory. If empty, `currentPath` value will be used.
- Parameter completionHandler: a block with result of directory entries or error.
- Parameter completionHandler: a closure with result of directory entries or error.
`attributes`: A `FileObject` containing the attributes of the item.
`error`: Error returned by system.
*/
@@ -205,6 +205,130 @@ open class CloudFileProvider: LocalFileProvider {
}
}
/**
Search files inside directory using query asynchronously.
- Note: For now only it's limited to file names. `query` parameter may take `NSPredicate` format in near future.
- Parameters:
- path: location of directory to start search
- recursive: Searching subdirectories of path
- query: Simple string of file name to be search (for now).
- foundItemHandler: Closure which is called when a file is found
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
*/
open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
let mapDict: [String: String] = ["url": NSMetadataItemURLKey, "name": NSMetadataItemFSNameKey, "path": NSMetadataItemPathKey, "filesize": NSMetadataItemFSSizeKey, "modifiedDate": NSMetadataItemFSContentChangeDateKey, "creationDate": NSMetadataItemFSCreationDateKey, "contentType": NSMetadataItemContentTypeKey]
func updateQueryKeys(_ queryComponent: NSPredicate) -> NSPredicate {
if let cQuery = queryComponent as? NSCompoundPredicate {
let newSub = cQuery.subpredicates.map { updateQueryKeys($0 as! NSPredicate) }
switch cQuery.compoundPredicateType {
case .and: return NSCompoundPredicate(andPredicateWithSubpredicates: newSub)
case .not: return NSCompoundPredicate(notPredicateWithSubpredicate: newSub[0])
case .or: return NSCompoundPredicate(orPredicateWithSubpredicates: newSub)
}
} else if let cQuery = queryComponent as? NSComparisonPredicate {
var newLeft = cQuery.leftExpression
var newRight = cQuery.rightExpression
if newLeft.expressionType == .keyPath, let newKey = mapDict[newLeft.keyPath] {
newLeft = NSExpression(forKeyPath: newKey)
}
if newRight.expressionType == .keyPath, let newKey = mapDict[newRight.keyPath] {
newRight = NSExpression(forKeyPath: newKey)
}
if newLeft.expressionType == .keyPath, newLeft.keyPath == "type" {
newRight = NSExpression(forConstantValue: newRight.constantValue as? String == "directory" ? "public.directory": "public.data")
}
if newRight.expressionType == .keyPath, newRight.keyPath == "type" {
newLeft = NSExpression(forConstantValue: newLeft.constantValue as? String == "directory" ? "public.directory": "public.data")
}
return NSComparisonPredicate(leftExpression: newLeft, rightExpression: newRight, modifier: cQuery.comparisonPredicateModifier, type: cQuery.predicateOperatorType, options: cQuery.options)
} else {
return queryComponent
}
}
dispatch_queue.async {
let pathURL = self.url(of: path)
let mdquery = NSMetadataQuery()
mdquery.predicate = NSPredicate(format: "(%K BEGINSWITH %@) && (\(updateQueryKeys(query).predicateFormat))", NSMetadataItemPathKey, pathURL.path)
mdquery.searchScopes = [self.scope.rawValue]
var lastReportedCount = 0
if let foundItemHandler = foundItemHandler {
var updateObserver: NSObjectProtocol?
// FIXME: Remove this section as it won't work as expected on iCloud
updateObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryGatheringProgress, object: mdquery, queue: nil, using: { (notification) in
mdquery.disableUpdates()
guard mdquery.resultCount > lastReportedCount else { return }
for index in lastReportedCount..<mdquery.resultCount {
guard let attribs = (mdquery.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 = mdquery.resultCount
mdquery.enableUpdates()
})
}
var finishObserver: NSObjectProtocol?
finishObserver = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: mdquery, queue: nil, using: { (notification) in
defer {
mdquery.stop()
NotificationCenter.default.removeObserver(finishObserver!)
}
guard let results = mdquery.results as? [NSMetadataItem] else {
return
}
mdquery.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)
}
}
self.dispatch_queue.async {
completionHandler(contents, nil)
}
})
DispatchQueue.main.async {
if !mdquery.start() {
self.dispatch_queue.async {
completionHandler([], self.throwError(path, code: CocoaError.fileReadNoPermission))
}
}
}
}
}
open override func isReachable(completionHandler: @escaping (Bool) -> Void) {
dispatch_queue.async {
completionHandler(self.fileManager.ubiquityIdentityToken != nil)
@@ -378,7 +502,7 @@ open class CloudFileProvider: LocalFileProvider {
- Parameters:
- path: Path of file.
- completionHandler: a block with result of file contents or error.
- completionHandler: a closure with result of file contents or error.
`contents`: contents of file in a `Data` object.
`error`: Error returned by system.
- Returns: An `OperationHandle` to get progress or cancel progress.
@@ -397,7 +521,7 @@ open class CloudFileProvider: LocalFileProvider {
- path: Path of file.
- offset: First byte index which should be read. **Starts from 0.**
- length: Bytes count of data. Pass `-1` to read until the end of file.
- completionHandler: a block with result of file contents or error.
- completionHandler: a closure with result of file contents or error.
`contents`: contents of file in a `Data` object.
`error`: Error returned by system.
- Returns: An `OperationHandle` to get progress or cancel progress.
@@ -425,98 +549,6 @@ open class CloudFileProvider: LocalFileProvider {
return CloudOperationHandle(operationType: r.operationType, baseURL: self.baseURL)
}
/**
Search files inside directory using query asynchronously.
- Note: For now only it's limited to file names. `query` parameter may take `NSPredicate` format in near future.
- Parameters:
- path: location of directory to start search
- recursive: Searching subdirectories of path
- query: Simple string of file name to be search (for now).
- foundItemHandler: Block which is called when a file is found
- completionHandler: Block which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
*/
open override func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
dispatch_queue.async {
let pathURL = self.url(of: path)
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "(%K BEGINSWITH %@) && (%K LIKE %@)", NSMetadataItemPathKey, pathURL.path, NSMetadataItemFSNameKey, query)
query.searchScopes = [self.scope.rawValue]
var lastReportedCount = 0
if let foundItemHandler = foundItemHandler {
var updateObserver: NSObjectProtocol?
// FIXME: Remove this section as it won't work as expected on iCloud
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)
}
}
self.dispatch_queue.async {
completionHandler(contents, nil)
}
})
DispatchQueue.main.async {
if !query.start() {
self.dispatch_queue.async {
completionHandler([], self.throwError(path, code: CocoaError.fileReadNoPermission))
}
}
}
}
}
fileprivate var monitors = [String: (NSMetadataQuery, NSObjectProtocol)]()
/**
@@ -532,7 +564,7 @@ open class CloudFileProvider: LocalFileProvider {
- Parameters:
- path: path of directory.
- eventHandler: Block executed after change, on a secondary thread.
- eventHandler: Closure executed after change, on a secondary thread.
*/
open override func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
self.unregisterNotifcation(path: path)
@@ -622,7 +654,19 @@ open class CloudFileProvider: LocalFileProvider {
}
}
/// Returns a pulic url with expiration date, can be shared with other people.
/**
Genrates a public url to a file to be shared with other users and can be downloaded without authentication.
- Important: URL will be available for a limitied time, determined in `expiration` argument.
- Parameters:
- to: path of file, including file/directory name.
- completionHandler: a closure with result of directory entries or error.
`link`: a url returned by Dropbox to share.
`attribute`: a `FileObject` containing the attributes of the item.
`expiration`: a `Date` object, determines when the public url will expires.
`error`: Error returned by Dropbox.
*/
open func publicLink(to path: String, completionHandler: @escaping ((_ link: URL?, _ attribute: FileObject?, _ expiration: Date?, _ error: Error?) -> Void)) {
operation_queue.addOperation {
do {
+28 -25
View File
@@ -61,7 +61,7 @@ open class DropboxFileProvider: FileProviderBasicRemote {
The latter is easier to use and prefered. Also you can use [auth0/Lock](https://github.com/auth0/Lock.iOS-OSX) which provides graphical user interface.
- Parameter credential: a `URLCredential` object with Client ID set as `user` and Token set as `password`.
- Parameter cache: A URLCache to cache downloaded files and contents. If set to nil, URLCache.shared object will be used.
- Parameter cache: A URLCache to cache downloaded files and contents.
*/
public init(credential: URLCredential?, cache: URLCache? = nil) {
self.baseURL = nil
@@ -135,6 +135,19 @@ open class DropboxFileProvider: FileProviderBasicRemote {
task.resume()
}
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
var foundFiles = [DropboxFileObject]()
guard let queryStr = self.findNameQuery(query, key: "name") as? String else { return }
search(path, query: queryStr, foundItem: { (file) in
if query.evaluate(with: file.mapPredicate()) {
foundFiles.append(file)
foundItemHandler?(file)
}
}, completionHandler: { (error) in
completionHandler(foundFiles, error)
})
}
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
self.storageProperties { total, _ in
completionHandler(total > 0)
@@ -145,25 +158,25 @@ open class DropboxFileProvider: FileProviderBasicRemote {
}
extension DropboxFileProvider: FileProviderOperations {
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open 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? {
open 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? {
open 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? {
open 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? {
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
return doOperation(.remove(path: path), completionHandler: completionHandler)
}
@@ -211,7 +224,7 @@ extension DropboxFileProvider: FileProviderOperations {
return RemoteOperationHandle(operationType: operation, tasks: [task])
}
public func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open 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
@@ -219,7 +232,7 @@ extension DropboxFileProvider: FileProviderOperations {
return upload_simple(toPath, localFile: localFile, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
}
public func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open 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
@@ -253,7 +266,7 @@ extension DropboxFileProvider: FileProviderOperations {
}
extension DropboxFileProvider: FileProviderReadWrite {
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
if length == 0 || offset < 0 {
dispatch_queue.async {
completionHandler(Data(), nil)
@@ -295,16 +308,6 @@ extension DropboxFileProvider: FileProviderReadWrite {
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 = [DropboxFileObject]()
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 Dropbox. Either using webooks
@@ -346,7 +349,7 @@ extension DropboxFileProvider {
- Parameters:
- to: path of file, including file/directory name.
- completionHandler: a block with result of directory entries or error.
- completionHandler: a closure with result of directory entries or error.
`link`: a url returned by Dropbox to share.
`attribute`: a `FileObject` containing the attributes of the item.
`expiration`: a `Date` object, determines when the public url will expires.
@@ -389,7 +392,7 @@ extension DropboxFileProvider {
- Parameters:
- remoteURL: a valid remote url to file.
- to: Destination path of file, including file/directory name.
- completionHandler: a block with result of directory entries or error.
- completionHandler: a closure with result of directory entries or error.
`jobId`: Job ID returned by Dropbox to monitor the copy/download progress.
`attribute`: A `FileObject` containing the attributes of the item.
`error`: Error returned by Dropbox.
@@ -454,7 +457,7 @@ extension DropboxFileProvider {
}
extension DropboxFileProvider: ExtendedFileProvider {
public func thumbnailOfFileSupported(path: String) -> Bool {
open func thumbnailOfFileSupported(path: String) -> Bool {
switch (path as NSString).pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
return true
@@ -469,7 +472,7 @@ extension DropboxFileProvider: ExtendedFileProvider {
}
}
public func propertiesOfFileSupported(path: String) -> Bool {
open func propertiesOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
switch fileExt {
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
@@ -484,7 +487,7 @@ extension DropboxFileProvider: ExtendedFileProvider {
}
/// Default value for dimension is 64x64, according to Dropbox documentation
public func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
open func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
let url: URL
switch (path as NSString).pathExtension.lowercased() {
case "jpg", "jpeg", "gif", "bmp", "png", "tif", "tiff":
@@ -527,7 +530,7 @@ extension DropboxFileProvider: ExtendedFileProvider {
task.resume()
}
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
let url = URL(string: "files/get_metadata", relativeTo: apiURL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
-5
View File
@@ -10,11 +10,6 @@ import Foundation
import ImageIO
import CoreGraphics
import AVFoundation
#if os(iOS) || os(tvOS)
import UIKit
#elseif os(macOS)
import Cocoa
#endif
extension LocalFileProvider: ExtendedFileProvider {
public func thumbnailOfFileSupported(path: String) -> Bool {
-1
View File
@@ -13,7 +13,6 @@ private var lasttaskIdAssociated = 1_000_000_000
/// This class is a replica of NSURLSessionStreamTask with same api for iOS 7/8
/// while it will fallback to NSURLSessionStreamTask in iOS 9.
@objc
internal class FPSStreamTask: URLSessionTask, StreamDelegate {
fileprivate var inputStream: InputStream?
fileprivate var outputStream: OutputStream?
+19
View File
@@ -156,6 +156,25 @@ open class FileObject: Equatable {
}
return rhs.path == lhs.path && rhs.size == lhs.size && rhs.modifiedDate == lhs.modifiedDate
}
internal func mapPredicate() -> [String: Any] {
let mapDict: [URLResourceKey: String] = [.fileURL: "url", .nameKey: "name", .pathKey: "path", .fileSizeKey: "filesize", .creationDateKey: "creationDate",
.contentModificationDateKey: "modifiedDate", .isHiddenKey: "isHidden", .isWritableKey: "isWritable", .serverDate: "serverDate", .entryTag: "entryTag", .mimeType: "mimeType"]
let typeDict: [URLFileResourceType: String] = [.directory: "directory", .regular: "regular", .symbolicLink: "symbolicLink", .unknown: "unknown"]
var result = [String: Any]()
for (key, value) in allValues {
if let convertkey = mapDict[key] {
result[convertkey] = value
}
}
result["eTag"] = result["entryTag"]
result["isReadOnly"] = self.isReadOnly
result["isDirectory"] = self.isDirectory
result["isRegularFile"] = self.isRegularFile
result["isSymLink"] = self.isSymLink
result["type"] = typeDict[self.type ?? .unknown] ?? "unknown"
return result
}
}
internal func resolve(dateString: String) -> Date? {
+109 -28
View File
@@ -68,7 +68,7 @@ public protocol FileProviderBasic: class {
If the directory contains no entries or an error is occured, this method will return the empty array.
- Parameter path: path to target directory. If empty, `currentPath` value will be used.
- Parameter completionHandler: a block with result of directory entries or error.
- Parameter completionHandler: a closure with result of directory entries or error.
`contents`: An array of `FileObject` identifying the the directory entries.
`error`: Error returned by system.
*/
@@ -80,7 +80,7 @@ public protocol FileProviderBasic: class {
If the directory contains no entries or an error is occured, this method will return the empty `FileObject`.
- Parameter path: path to target directory. If empty, `currentPath` value will be used.
- Parameter completionHandler: a block with result of directory entries or error.
- Parameter completionHandler: a closure with result of directory entries or error.
`attributes`: A `FileObject` containing the attributes of the item.
`error`: Error returned by system.
*/
@@ -90,6 +90,41 @@ public protocol FileProviderBasic: class {
/// Returns total and used capacity in provider container asynchronously.
func storageProperties(completionHandler: @escaping ((_ total: Int64, _ used: Int64) -> Void))
/**
Search files inside directory using query asynchronously.
- Note: Query string is limited to file name, to search based on other file properties, use NSPredicate version.
- Parameters:
- path: location of directory to start search
- recursive: Searching subdirectories of path
- query: Simple string that file name contains to be search, case-insensitive.
- foundItemHandler: Closure which is called when a file is found
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
*/
func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void))
/**
Search files inside directory using query asynchronously.
Sample predicates:
```
NSPredicate(format: "(name CONTAINS[c] 'hello') && (filesize >= 10000)")
NSPredicate(format: "(modifiedDate >= %@)", Date())
NSPredicate(format: "(path BEGINSWITH %@)", "folder/child folder")
```
- Note: Don't pass Spotlight predicates to this method directly, use `FileProvider.convertSpotlightPredicateTo()` method to get usable predicate.
- Parameters:
- path: location of directory to start search
- recursive: Searching subdirectories of path
- query: An `NSPredicate` object with keys like `FileObject` members, except `size` which becomes `filesize`.
- foundItemHandler: Closure which is called when a file is found
- completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
*/
func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void))
/**
Returns an independent url to access the file. Some providers like `Dropbox` due to their nature.
don't return an absolute url to be used to access file directly.
@@ -103,6 +138,39 @@ public protocol FileProviderBasic: class {
}
extension FileProviderBasic {
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
let predicate = NSPredicate(format: "name CONTAINS[c] %@", query)
self.searchFiles(path: path, recursive: recursive, query: predicate, foundItemHandler: foundItemHandler, completionHandler: completionHandler)
}
/// Converts Spotlight search predicate to `FileProvider.searchFiles()` method usable predicate.
public func convertSpotlightPredicateTo(_ query: NSPredicate) -> NSPredicate {
let mapDict: [String: URLResourceKey] = [NSMetadataItemURLKey: .fileURL, NSMetadataItemFSNameKey: .nameKey, NSMetadataItemPathKey: .pathKey,
NSMetadataItemFSSizeKey: .fileSizeKey, NSMetadataItemFSCreationDateKey: .creationDateKey,
NSMetadataItemFSContentChangeDateKey: .contentModificationDateKey, "kMDItemFSInvisible": .isHiddenKey, "kMDItemFSIsWriteable": .isWritableKey, "kMDItemKind": .mimeType]
if let cQuery = query as? NSCompoundPredicate {
let newSub = cQuery.subpredicates.map { convertSpotlightPredicateTo($0 as! NSPredicate) }
switch cQuery.compoundPredicateType {
case .and: return NSCompoundPredicate(andPredicateWithSubpredicates: newSub)
case .not: return NSCompoundPredicate(notPredicateWithSubpredicate: newSub[0])
case .or: return NSCompoundPredicate(orPredicateWithSubpredicates: newSub)
}
} else if let cQuery = query as? NSComparisonPredicate {
var newLeft = cQuery.leftExpression
var newRight = cQuery.rightExpression
if newLeft.expressionType == .keyPath, let newKey = mapDict[newLeft.keyPath] {
newLeft = NSExpression(forKeyPath: newKey.rawValue)
}
if newRight.expressionType == .keyPath, let newKey = mapDict[newRight.keyPath] {
newRight = NSExpression(forKeyPath: newKey.rawValue)
}
return NSComparisonPredicate(leftExpression: newLeft, rightExpression: newRight, modifier: cQuery.comparisonPredicateModifier, type: cQuery.predicateOperatorType, options: cQuery.options)
} else {
return query
}
}
/// The maximum number of queued operations that can execute at the same time.
///
/// The default value of this property is `OperationQueue.defaultMaxConcurrentOperationCount`.
@@ -114,6 +182,26 @@ extension FileProviderBasic {
operation_queue.maxConcurrentOperationCount = newValue
}
}
internal func findNameQuery(_ query: NSPredicate, key: String?) -> Any? {
if let cQuery = query as? NSCompoundPredicate {
let find = cQuery.subpredicates.flatMap { findNameQuery($0 as! NSPredicate, key: key) }
if find.count > 0 {
return find[0]
}
return nil
} else if let cQuery = query as? NSComparisonPredicate {
if cQuery.leftExpression.expressionType == .keyPath, key == nil || cQuery.leftExpression.keyPath == key! {
return cQuery.rightExpression.constantValue
}
if cQuery.rightExpression.expressionType == .keyPath, key == nil || cQuery.rightExpression.keyPath == key! {
return cQuery.leftExpression.constantValue
}
return nil
} else {
return nil
}
}
}
/// Checking equality of two file provider, regardless of current path queues and delegates.
@@ -133,10 +221,18 @@ public protocol FileProviderBasicRemote: FileProviderBasic {
/// Underlying URLSession instance used for HTTP/S requests
var session: URLSession { get }
/// A `URLCache` to cache downloaded files and contents.
///
/// If set to nil, `URLCache.shared` object will be used.
/// - Note: It has no effect unless setting `useCache` property to `true`.
/**
A `URLCache` to cache downloaded files and contents.
- Note: It has no effect unless setting `useCache` property to `true`.
- Warning: FileProvider doesn't manage/free `URLCache` object in a memory pressure scenario. It's upon you to clear
cache memory when receiving `didReceiveMemoryWarning` or via observing `.UIApplicationDidReceiveMemoryWarning` notification.
To clear memory usage use this code:
```
provider.cache?.removeAllCachedResponses()
```
*/
var cache: URLCache? { get }
/// Determine to use `cache` property to cache downloaded file objects. Doesn't have effect on query type methods.
@@ -224,7 +320,6 @@ public protocol FileProviderOperations: FileProviderBasic {
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
*/
@discardableResult
func create(file: String, at: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle?
/**
@@ -363,7 +458,7 @@ public protocol FileProviderReadWrite: FileProviderBasic {
- Parameters:
- path: Path of file.
- completionHandler: a block with result of file contents or error.
- completionHandler: a closure with result of file contents or error.
`contents`: contents of file in a `Data` object.
`error`: Error returned by system.
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
@@ -379,7 +474,7 @@ public protocol FileProviderReadWrite: FileProviderBasic {
- path: Path of file.
- offset: First byte index which should be read. **Starts from 0.**
- length: Bytes count of data. Pass `-1` to read until the end of file.
- completionHandler: a block with result of file contents or error.
- completionHandler: a closure with result of file contents or error.
`contents`: contents of file in a `Data` object.
`error`: Error returned by system.
- Returns: An `OperationHandle` to get progress or cancel progress. Doesn't work on `LocalFileProvider`.
@@ -442,20 +537,6 @@ public protocol FileProviderReadWrite: FileProviderBasic {
*/
@discardableResult
func writeContents(path: String, contents: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle?
/**
Search files inside directory using query asynchronously.
- Note: For now only it's limited to file names. `query` parameter may take `NSPredicate` format in near future.
- Parameters:
- path: location of directory to start search
- recursive: Searching subdirectories of path
- query: Simple string of file name to be search (for now).
- foundItemHandler: Block which is called when a file is found
- completionHandler: Block which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
*/
func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void))
}
extension FileProviderReadWrite {
@@ -496,7 +577,7 @@ public protocol FileProviderMonitor: FileProviderBasic {
- Parameters:
- path: path of directory.
- eventHandler: Block executed after change, on a secondary thread.
- eventHandler: Closure executed after change, on a secondary thread.
*/
func registerNotifcation(path: String, eventHandler: @escaping (() -> Void))
@@ -614,7 +695,7 @@ extension FileProviderBasic {
/// - Returns: A `String` contains relative path of url against base url.
public func relativePathOf(url: URL) -> String {
// check if url derieved from current base url
if url.relativeString.isEmpty, url.baseURL == self.baseURL {
if !url.relativePath.isEmpty, url.baseURL == self.baseURL {
return url.relativePath.removingPercentEncoding!
}
@@ -710,7 +791,7 @@ public protocol ExtendedFileProvider: FileProviderBasic {
- Parameters:
- path: path of file.
- completionHandler: a block with result of preview image or error.
- completionHandler: a closure with result of preview image or error.
`image`: `NSImage`/`UIImage` object contains preview.
`error`: Error returned by system.
*/
@@ -726,7 +807,7 @@ public protocol ExtendedFileProvider: FileProviderBasic {
- Parameters:
- path: path of file.
- dimension: width and height of result preview image.
- completionHandler: a block with result of preview image or error.
- completionHandler: a closure with result of preview image or error.
`image`: `NSImage`/`UIImage` object contains preview.
`error`: Error returned by system.
*/
@@ -741,7 +822,7 @@ public protocol ExtendedFileProvider: FileProviderBasic {
- Parameters:
- path: path of file.
- completionHandler: a block with result of preview image or error.
- completionHandler: a closure with result of preview image or error.
`propertiesDictionary`: A `Dictionary` of proprty keys and values.
`keys`: An `Array` contains ordering of keys.
`error`: Error returned by system.
+40 -41
View File
@@ -23,8 +23,10 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
open var operation_queue: OperationQueue
open weak var delegate: FileProviderDelegate?
open internal(set) var credential: URLCredential?
/// Underlying `FileManager` object for listing and metadata fetching.
open private(set) var fileManager = FileManager()
/// Underlying `FileManager` object for operationa like copying, moving, etc.
open private(set) var opFileManager = FileManager()
fileprivate var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
@@ -61,7 +63,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
default values are `directory: .documentDirectory`.
- Parameters:
- sharedContainerId: Same with `App Group` identifier defined in project settings.
- sharedContainerId: Same with `App Group` identifier defined in project settings.
- directory: The search path directory. The supported values are described in `FileManager.SearchPathDirectory`.
*/
public convenience init? (sharedContainerId: String, directory: FileManager.SearchPathDirectory = .documentDirectory) {
@@ -85,6 +87,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
}
self.init(baseURL: finalBaseURL)
self.isCoorinating = true
try? fileManager.createDirectory(at: finalBaseURL, withIntermediateDirectories: true)
}
@@ -132,6 +135,12 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
}
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
dispatch_queue.async {
completionHandler(LocalFileObject(fileWithPath: path, relativeTo: self.baseURL), nil)
}
}
open func storageProperties(completionHandler: (@escaping (_ total: Int64, _ used: Int64) -> Void)) {
let values = try? baseURL?.resourceValues(forKeys: [.volumeTotalCapacityKey, .volumeAvailableCapacityKey])
let totalSize = Int64(values??.volumeTotalCapacity ?? -1)
@@ -139,9 +148,21 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
completionHandler(totalSize, totalSize - freeSize)
}
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
dispatch_queue.async {
completionHandler(LocalFileObject(fileWithPath: path, relativeTo: self.baseURL), nil)
let iterator = self.fileManager.enumerator(at: self.url(of: path), includingPropertiesForKeys: nil, options: recursive ? [] : [.skipsSubdirectoryDescendants, .skipsPackageDescendants]) { (url, e) -> Bool in
completionHandler([], e)
return true
}
var result = [LocalFileObject]()
while let fileURL = iterator?.nextObject() as? URL {
let path = self.relativePathOf(url: fileURL)
if let fileObject = LocalFileObject(fileWithPath: path, relativeTo: self.baseURL), query.evaluate(with: fileObject.mapPredicate()) {
result.append(fileObject)
foundItemHandler?(fileObject)
}
}
completionHandler(result, nil)
}
}
@@ -227,26 +248,24 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
@discardableResult
fileprivate func doOperation(_ opType: FileOperationType, data: Data? = nil, atomically: Bool = false, forUploading: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
func urlofpath(path: String) -> URL {
if path.hasPrefix("file://") {
let removedSchemePath = path.replacingOccurrences(of: "file://", with: "", options: .anchored)
let pDecodedPath = removedSchemePath.removingPercentEncoding ?? removedSchemePath
return URL(fileURLWithPath: pDecodedPath)
} else {
return self.url(of: path)
}
}
guard let sourcePath = opType.source else { return nil }
let destPath = opType.destination
let source: URL
if sourcePath.hasPrefix("file://") {
let removedSchemePath = sourcePath.replacingOccurrences(of: "file://", with: "", options: .anchored)
let pDecodedPath = removedSchemePath.removingPercentEncoding ?? removedSchemePath
source = URL(fileURLWithPath: pDecodedPath)
} else {
source = self.url(of: sourcePath)
}
let source: URL = urlofpath(path: sourcePath)
let dest: URL?
if let destPath = destPath {
if destPath.hasPrefix("file://") {
let removedSchemePath = destPath.replacingOccurrences(of: "file://", with: "", options: .anchored)
let pDecodedPath = removedSchemePath.removingPercentEncoding ?? removedSchemePath
dest = URL(fileURLWithPath: pDecodedPath)
} else {
dest = self.url(of: destPath)
}
dest = urlofpath(path: destPath)
} else {
dest = nil
}
@@ -307,8 +326,8 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
}
if isCoorinating {
var intents = [NSFileAccessIntent]()
successfulSecurityScopedResourceAccess = source.startAccessingSecurityScopedResource()
var intents = [NSFileAccessIntent]()
switch opType {
case .create, .modify:
intents.append(NSFileAccessIntent.writingIntent(with: source, options: .forReplacing))
@@ -452,26 +471,6 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
return self.doOperation(opType, data: data, atomically: atomically, completionHandler: completionHandler)
}
open func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
dispatch_queue.async {
let iterator = self.fileManager.enumerator(at: self.url(of: path), includingPropertiesForKeys: nil, options: recursive ? [] : [.skipsSubdirectoryDescendants, .skipsPackageDescendants]) { (url, e) -> Bool in
completionHandler([], e)
return true
}
var result = [LocalFileObject]()
while let fileURL = iterator?.nextObject() as? URL {
if fileURL.lastPathComponent.lowercased().contains(query.lowercased()) {
let path = self.relativePathOf(url: fileURL)
if let fileObject = LocalFileObject(fileWithPath: path, relativeTo: self.baseURL) {
result.append(fileObject)
foundItemHandler?(fileObject)
}
}
}
completionHandler(result, nil)
}
}
fileprivate var monitors = [LocalFolderMonitor]()
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
@@ -500,7 +499,7 @@ open class LocalFileProvider: FileProvider, FileProviderMonitor, FileProvideUndo
}
open func isRegisteredForNotification(path: String) -> Bool {
return monitors.map( { self.relativePathOf(url: $0.url) } ).contains(path)
return monitors.map( { self.relativePathOf(url: $0.url) } ).contains(path.trimmingCharacters(in: CharacterSet(charactersIn: "/")))
}
open func copy(with zone: NSZone? = nil) -> Any {
+30 -25
View File
@@ -71,7 +71,7 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
- serverURL: server url, Set it if you are trying to connect OneDrive Business server, otherwise leave it
`nil` to connect to OneDrive Personal uses.
- drive: drive name for user on server, default value is `root`.
- cache: A URLCache to cache downloaded files and contents. If set to nil, URLCache.shared object will be used.
- cache: A URLCache to cache downloaded files and contents.
*/
public init(credential: URLCredential?, serverURL: URL? = nil, drive: String = "root", cache: URLCache? = nil) {
let baseURL = serverURL ?? URL(string: "https://api.onedrive.com/")!
@@ -139,6 +139,21 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
task.resume()
}
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
var foundFiles = [OneDriveFileObject]()
var queryStr: String?
queryStr = self.findNameQuery(query, key: "name") as? String ?? self.findNameQuery(query, key: nil) as? String
guard let finalQueryStr = queryStr else { return }
search(path, query: finalQueryStr, foundItem: { (file) in
if query.evaluate(with: file.mapPredicate()) {
foundFiles.append(file)
foundItemHandler?(file)
}
}, completionHandler: { (error) in
completionHandler(foundFiles, error)
})
}
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
let url = URL(string: "/drive/root", relativeTo: baseURL)!
var request = URLRequest(url: url)
@@ -157,25 +172,25 @@ open class OneDriveFileProvider: FileProviderBasicRemote {
extension OneDriveFileProvider: FileProviderOperations {
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open 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? {
open 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? {
open 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? {
open 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? {
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
return doOperation(.remove(path: path), completionHandler: completionHandler)
}
@@ -220,7 +235,7 @@ extension OneDriveFileProvider: FileProviderOperations {
return RemoteOperationHandle(operationType: operation, tasks: [task])
}
public func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open 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
@@ -228,7 +243,7 @@ extension OneDriveFileProvider: FileProviderOperations {
return upload_simple(toPath, localFile: localFile, overwrite: overwrite, operation: opType, completionHandler: completionHandler)
}
public func copyItem(path: String, toLocalURL destURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open 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
@@ -259,7 +274,7 @@ extension OneDriveFileProvider: FileProviderOperations {
}
extension OneDriveFileProvider: FileProviderReadWrite {
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
if length == 0 || offset < 0 {
dispatch_queue.async {
completionHandler(Data(), nil)
@@ -290,7 +305,7 @@ extension OneDriveFileProvider: FileProviderReadWrite {
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.modify(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
@@ -299,16 +314,6 @@ extension OneDriveFileProvider: FileProviderReadWrite {
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
@@ -327,7 +332,7 @@ extension OneDriveFileProvider: FileProviderReadWrite {
- Parameters:
- to: path of file, including file/directory name.
- completionHandler: a block with result of directory entries or error.
- completionHandler: a closure with result of directory entries or error.
`link`: a url returned by OneDrive to share.
`attribute`: `nil` for OneDrive.
`expiration`: `nil` for OneDrive, as it doesn't expires.
@@ -360,11 +365,11 @@ extension OneDriveFileProvider: FileProviderReadWrite {
extension OneDriveFileProvider: ExtendedFileProvider {
public func thumbnailOfFileSupported(path: String) -> Bool {
open func thumbnailOfFileSupported(path: String) -> Bool {
return true
}
public func propertiesOfFileSupported(path: String) -> Bool {
open func propertiesOfFileSupported(path: String) -> Bool {
let fileExt = (path as NSString).pathExtension.lowercased()
switch fileExt {
case "jpg", "jpeg", "bmp", "gif", "png", "tif", "tiff":
@@ -378,7 +383,7 @@ extension OneDriveFileProvider: ExtendedFileProvider {
}
}
public func thumbnailOfFile(path: String, dimension: CGSize?, completionHandler: @escaping ((_ image: ImageClass?, _ error: Error?) -> Void)) {
open 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)!
@@ -403,7 +408,7 @@ extension OneDriveFileProvider: ExtendedFileProvider {
task.resume()
}
public func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
open func propertiesOfFile(path: String, completionHandler: @escaping ((_ propertiesDictionary: [String : Any], _ keys: [String], _ error: Error?) -> Void)) {
let url = URL(string: escaped(path: path), relativeTo: driveURL)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
+3 -5
View File
@@ -8,7 +8,7 @@
import Foundation
/// Allows to get progress or cancel an in-progress operation, for remote -URLSession based- providers.
/// Allows to get progress or cancel an in-progress operation, for remote, `URLSession` based providers.
open class RemoteOperationHandle: OperationHandle {
internal var tasks: [Weak<URLSessionTask>]
@@ -43,7 +43,7 @@ open class RemoteOperationHandle: OperationHandle {
if let task = $1.value as? URLSessionUploadTask {
return $0 + task.countOfBytesExpectedToSend
} else {
return $0 + ($1.value?.countOfBytesExpectedToSend ?? 0)
return $0 + ($1.value?.countOfBytesExpectedToReceive ?? 0)
}
}
}
@@ -71,8 +71,6 @@ public protocol FileProviderHTTPError: Error, CustomStringConvertible {
var path: String { get }
/// Contents returned by server as error description
var errorDescription: String? { get }
var description: String { get }
}
extension FileProviderHTTPError {
@@ -309,7 +307,7 @@ public enum FileProviderHTTPErrorCode: Int, CustomStringConvertible {
case 300...399: return "Redirection"
case 400...499: return "Client Error"
case 500...599: return "Server Error"
default: return "Server Error"
default: return "Unknown Error"
}
}
}
+1 -1
View File
@@ -100,7 +100,7 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
return nil
}
open func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler:((FileObjectClass) -> Void)?, completionHandler: @escaping ((_ files: [FileObjectClass], _ error: Error?) -> Void)) {
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler:((FileObjectClass) -> Void)?, completionHandler: @escaping ((_ files: [FileObjectClass], _ error: Error?) -> Void)) {
NotImplemented()
}
+43 -43
View File
@@ -13,7 +13,7 @@ import Foundation
set `useCache` and `cache` properties to use Foundation `NSURLCache` system.
WebDAV system supported by many cloud services including [Box.net](https://www.box.com/home)
and [Yandex disk](https://disk.yandex.com).
and [Yandex disk](https://disk.yandex.com) and [ownCloud](https://owncloud.org).
- Important: Because this class uses `URLSession`, it's necessary to disable App Transport Security
in case of using this class with unencrypted HTTP connection.
@@ -59,7 +59,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
- Parameters:
- baseURL: Location of WebDAV server.
- credential: An `URLCredential` object with `user` and `password`.
- cache: A URLCache to cache downloaded files and contents. If set to nil, URLCache.shared object will be used.
- cache: A URLCache to cache downloaded files and contents.
*/
public init? (baseURL: URL, credential: URLCredential?, cache: URLCache? = nil) {
if !["http", "https"].contains(baseURL.uw_scheme.lowercased()) {
@@ -164,6 +164,38 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
})
}
open func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
let url = self.url(of: path)
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
//request.setValue("1", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
runDataTask(with: request, completionHandler: { (data, response, error) in
// FIXME: paginating results
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
}
if let data = data {
let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
var fileObjects = [WebDavFileObject]()
for attr in xresponse {
let fileObject = WebDavFileObject(attr)
if !query.evaluate(with: fileObject.mapPredicate()) {
continue
}
fileObjects.append(fileObject)
foundItemHandler?(fileObject)
}
completionHandler(fileObjects, responseError ?? error)
return
}
completionHandler([], responseError ?? error)
})
}
open func isReachable(completionHandler: @escaping (Bool) -> Void) {
var request = URLRequest(url: baseURL!)
request.httpMethod = "PROPFIND"
@@ -182,7 +214,7 @@ open class WebDAVFileProvider: FileProviderBasicRemote {
extension WebDAVFileProvider: FileProviderOperations {
@discardableResult
public func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.create(path: (atPath as NSString).appendingPathComponent(folderName) + "/")
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
@@ -204,7 +236,7 @@ extension WebDAVFileProvider: FileProviderOperations {
}
@discardableResult
public func create(file fileName: String, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open func create(file fileName: String, at path: String, contents data: Data?, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.create(path: (path as NSString).appendingPathComponent(fileName))
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
@@ -226,7 +258,7 @@ extension WebDAVFileProvider: FileProviderOperations {
}
@discardableResult
public func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.move(source: path, destination: toPath)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
@@ -235,7 +267,7 @@ extension WebDAVFileProvider: FileProviderOperations {
}
@discardableResult
public func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.copy(source: path, destination: toPath)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
@@ -244,7 +276,7 @@ extension WebDAVFileProvider: FileProviderOperations {
}
@discardableResult
public func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.remove(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
@@ -299,7 +331,7 @@ extension WebDAVFileProvider: FileProviderOperations {
}
@discardableResult
public func copyItem(localFile: URL, to toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open 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
@@ -324,7 +356,7 @@ extension WebDAVFileProvider: FileProviderOperations {
}
@discardableResult
public func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.copy(source: path, destination: toLocalURL.absoluteString)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
@@ -355,7 +387,7 @@ extension WebDAVFileProvider: FileProviderOperations {
extension WebDAVFileProvider: FileProviderReadWrite {
@discardableResult
public func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) -> OperationHandle? {
if length == 0 || offset < 0 {
dispatch_queue.async {
completionHandler(Data(), nil)
@@ -384,7 +416,7 @@ extension WebDAVFileProvider: FileProviderReadWrite {
}
@discardableResult
public func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
open func writeContents(path: String, contents data: Data, atomically: Bool, overwrite: Bool, completionHandler: SimpleCompletionHandler) -> OperationHandle? {
let opType = FileOperationType.modify(path: path)
guard fileOperationDelegate?.fileProvider(self, shouldDoOperation: opType) ?? true == true else {
return nil
@@ -417,38 +449,6 @@ extension WebDAVFileProvider: FileProviderReadWrite {
return RemoteOperationHandle(operationType: opType, tasks: [task])
}
public func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
let url = self.url(of: path)
var request = URLRequest(url: url)
request.httpMethod = "PROPFIND"
//request.setValue("1", forHTTPHeaderField: "Depth")
request.setValue("text/xml; charset=\"utf-8\"", forHTTPHeaderField: "Content-Type")
request.httpBody = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/></D:propfind>".data(using: .utf8)
runDataTask(with: request, completionHandler: { (data, response, error) in
// FIXME: paginating results
var responseError: FileProviderWebDavError?
if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
responseError = FileProviderWebDavError(code: rCode, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8), url: url)
}
if let data = data {
let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
var fileObjects = [WebDavFileObject]()
for attr in xresponse {
let path = attr.href.path
if !((path as NSString).lastPathComponent.contains(query)) {
continue
}
let fileObject = WebDavFileObject(attr)
fileObjects.append(fileObject)
foundItemHandler?(fileObject)
}
completionHandler(fileObjects, responseError ?? error)
return
}
completionHandler([], responseError ?? error)
})
}
/*
fileprivate func registerNotifcation(path: String, eventHandler: (() -> Void)) {
/* There is no unified api for monitoring WebDAV server content change/update