Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0004bd4c90 | |||
| bf4337bcb3 | |||
| 3301d2c004 | |||
| f0cd7846d8 | |||
| 8fd7da669d | |||
| 13a68c84f8 |
@@ -16,8 +16,8 @@ Pod::Spec.new do |s|
|
||||
#
|
||||
|
||||
s.name = "FileProvider"
|
||||
s.version = "0.1.0"
|
||||
s.summary = "Extended Local/WebDAV/SMB/CIFS/etc. File Manager for Swift on iOS and MacOS."
|
||||
s.version = "0.2.0"
|
||||
s.summary = "NSFileManager replacement for Local and Remote (WebDAV/Dropbox/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?
|
||||
@@ -67,7 +67,7 @@ Pod::Spec.new do |s|
|
||||
#
|
||||
|
||||
# s.platform = :ios
|
||||
# s.platform = :ios, "7.0"
|
||||
# s.platform = :ios, "8.0"
|
||||
|
||||
# When using multiple platforms
|
||||
s.ios.deployment_target = "8.0"
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
# FileProvider (experimental)
|
||||
|
||||
>This Swift library provide a swifty way to deal with local and remote files and directories in same way.
|
||||
>This Swift library provide a swifty way to deal with local and remote files and directories in a unified way.
|
||||
|
||||
[![Swift Version][swift-image]][swift-url]
|
||||
[![License][license-image]][license-url]
|
||||
[]()
|
||||
[](https://codebeat.co/projects/github-com-amosavian-fileprovider)
|
||||
[](https://img.shields.io/cocoapods/v/FileProvider.svg)
|
||||
[![codebeat badge][codebeat-image]][codebeat-url]
|
||||
|
||||
<!---
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](https://img.shields.io/cocoapods/v/LFAlertController.svg)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
--->
|
||||
|
||||
|
||||
|
||||
This library provides implementaion of WebDav and SMB/CIFS (incomplete) and local files.
|
||||
|
||||
All functions are async calls and it wont block your main thread.
|
||||
|
||||
Local and WebDAV providers are fully tested and can be used in production environment.
|
||||
|
||||
## Features
|
||||
|
||||
- [x] **LocalFileProvider** a wrapper for `NSFileManager` with some additions like searching and reading a portion of file
|
||||
- [x] **WebDAVFileProvider** WebDAV protocol is usual file transmission system on Macs
|
||||
- [ ] **SMBFileProvider** SMB/CIFS and SMB2/3 are file and printer sharing protocol which is originated from IBM & Microsoft and SMB2/3 is now replacing AFP protocol on MacOS. I Implemented data types and some basic functions but *main interface is not implemented yet!*
|
||||
- [x] **LocalFileProvider** a wrapper around `NSFileManager` with some additions like searching and reading a portion of file.
|
||||
- [x] **WebDAVFileProvider** WebDAV protocol is usual file transmission system on Macs.
|
||||
- [ ] **SMBFileProvider** SMB/CIFS and SMB2/3 are file and printer sharing protocol which is originated from IBM & Microsoft and SMB2/3 is now replacing AFP protocol on MacOS. I implemented data types and some basic functions but *main interface is not implemented yet!*
|
||||
- [ ] **DropboxFileProvider**
|
||||
- [ ] **FTPFileProvider**
|
||||
- [ ] **AmazonS3FileProvider**
|
||||
@@ -31,16 +31,20 @@ All functions are async calls and it wont block your main thread.
|
||||
## Requirements
|
||||
|
||||
- **Swift 2.2**
|
||||
- iOS 7.0 , OSX 10.9
|
||||
- iOS 8.0 , OSX 10.10
|
||||
- XCode 7.3
|
||||
|
||||
## Installation
|
||||
|
||||
### Cocoapods / Carthage / Swift Package Manager
|
||||
|
||||
I will add when project is completed is ready to use in production envioronment
|
||||
FileProvider supports both CocoaPods.
|
||||
|
||||
### Git clone
|
||||
Add this line to your pods file:
|
||||
|
||||
pod "FileProvider"
|
||||
|
||||
### Git
|
||||
To have latest updates with ease, use this command on terminal to get a clone:
|
||||
|
||||
git clone https://github.com/amosavian/FileProvider FileProvider
|
||||
@@ -49,13 +53,12 @@ You can update your library using this command in FileProvider folder:
|
||||
|
||||
git pull
|
||||
|
||||
### Submodule into your project
|
||||
if you have a git based project, use this command in your projects directory:
|
||||
if you have a git based project, use this command in your projects directory to add this project as a submodule to your project:
|
||||
|
||||
git submodule add https://github.com/amosavian/FileProvider FileProvider
|
||||
|
||||
### Manually
|
||||
Copy Source folder to your project and voila!
|
||||
Copy Source folder to your project and Voila!
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -65,13 +68,13 @@ Each provider has a specific class which conforms to FileProvider protocol and s
|
||||
|
||||
For LocalFileProvider if you want to deal with `Documents` folder
|
||||
|
||||
let documentsFileProvider = LocalFileProvider()
|
||||
let documentsProvider = LocalFileProvider()
|
||||
|
||||
is equal to:
|
||||
|
||||
let documentPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true);
|
||||
let documentsURL = NSURL(fileURLWithPath: documentPath);
|
||||
let documentsFileProvider = LocalFileProvider(baseURL: documentsURL)
|
||||
let documentsProvider = LocalFileProvider(baseURL: documentsURL)
|
||||
|
||||
You can't change the base url later. and all paths are related to this base url by default.
|
||||
|
||||
@@ -82,7 +85,9 @@ For remote file providers authentication may be necessary:
|
||||
|
||||
For interaction with UI, set delegate variable of `FileProvider` object
|
||||
|
||||
### Delegate
|
||||
You can use `absoluteURL()` method if provider to get direct access url (local or remote files) for some file systems which allows to do so (Dropbox doesn't support)
|
||||
|
||||
### Delegates
|
||||
|
||||
For updating User interface please consider using delegate method instead of completion handlers. Delegate methods are guaranteed to run in main thread to avoid bugs.
|
||||
|
||||
@@ -91,7 +96,7 @@ It's simply tree method which indicated whether the operation failed, succeed an
|
||||
Your class should conforms `FileProviderDelegate` class:
|
||||
|
||||
override func viewDidLoad() {
|
||||
documentsFileProvider.delegate = self
|
||||
documentsProvider.delegate = self
|
||||
}
|
||||
|
||||
func fileproviderSucceed(fileProvider: FileProvider, operation: FileOperation) {
|
||||
@@ -125,14 +130,27 @@ Your class should conforms `FileProviderDelegate` class:
|
||||
}
|
||||
}
|
||||
|
||||
**Note:** `fileproviderProgress()` delegate method is not called by `LocalFileProvider`.
|
||||
|
||||
Use completion handlers for error handling or result processing as far as possible.
|
||||
It's recommended to use completion handlers for error handling or result processing.
|
||||
|
||||
#### Controlling file operations
|
||||
|
||||
You can also implement `FileOperationDelegate` protocol to control behaviour of file operation (copy, move/rename, remove and linking), and decide which files should be removed for example and which won't.
|
||||
|
||||
`fileProvider:shouldDoOperation:` method is called before doing a operation. You sould return `true` if you want to do operation or `false` if you want to stop that operation.
|
||||
|
||||
`fileProvider:shouldProceedAfterError:operation:` will be called if an error occured during file operations. Return `true` if you want to continue operation on next files or `false` if you want stop operation further. Default value is false if you don't implement delegate.
|
||||
|
||||
**Note: these methods will be called for files in a directory and its subfolders recursively.**
|
||||
|
||||
### Directory contents and file attributes
|
||||
|
||||
There is a `FileObject` class which holds file attributes like size and creation date. You can retrieve information of files inside a directory or get information of a file directly
|
||||
There is a `FileObject` class which holds file attributes like size and creation date. You can retrieve information of files inside a directory or get information of a file directly.
|
||||
|
||||
documentsFileProvider.attributesOfItemAtPath(path: "/file.txt", completionHandler: {
|
||||
For a single file:
|
||||
|
||||
documentsProvider.attributesOfItemAtPath(path: "/file.txt", completionHandler: {
|
||||
(attributes: LocalFileObject?, error: ErrorType?) -> Void} in
|
||||
if let attributes = attributes {
|
||||
print("File Size: \(attributes.size)")
|
||||
@@ -142,7 +160,9 @@ There is a `FileObject` class which holds file attributes like size and creation
|
||||
}
|
||||
)
|
||||
|
||||
documentsFileProvider.contentsOfDirectoryAtPath(path: "/", completionHandler: {
|
||||
To get list of files in a directory:
|
||||
|
||||
documentsProvider.contentsOfDirectoryAtPath(path: "/", completionHandler: {
|
||||
(contents: [LocalFileObject], error: ErrorType?) -> Void} in
|
||||
for file in contents {
|
||||
print("Name: \(attributes.name)")
|
||||
@@ -154,41 +174,44 @@ There is a `FileObject` class which holds file attributes like size and creation
|
||||
|
||||
### Change current directory
|
||||
|
||||
documentsFileProvider.currentPath = "/New Folder"
|
||||
documentsProvider.currentPath = "/New Folder"
|
||||
// now path is ~/Documents/New Folder
|
||||
|
||||
You can then pass "" (empty string) to contentsOfDirectoryAtPath method to list files in current directory.
|
||||
|
||||
### Creating File and Folders
|
||||
|
||||
Creating new directory:
|
||||
|
||||
documentsFileProvider.createFolder(folderName: "new folder", atPath: "/", completionHandler: nil)
|
||||
documentsProvider.createFolder(folderName: "new folder", atPath: "/", completionHandler: nil)
|
||||
|
||||
Creating new file from data stream:
|
||||
|
||||
let data = "hello world!".dataUsingEncoding(NSUTF8StringEncoding)
|
||||
let file = FileObject(name: "old.txt", createdDate: NSDate(), modifiedDate: NSDate(), isHidden: false, isReadOnly: true)
|
||||
documentsFileProvider.createFile(fileAttribs: file, atPath: "/", contents: data, completionHandler: nil)
|
||||
documentsProvider.createFile(fileAttribs: file, atPath: "/", contents: data, completionHandler: nil)
|
||||
|
||||
### Copy and Move/Rename Files
|
||||
|
||||
// Copy file old.txt to new.txt in current path
|
||||
documentsFileProvider.copyItemAtPath(path: "new folder/old.txt", toPath: "new.txt", overwrite: false, completionHandler: nil)
|
||||
Copy file old.txt to new.txt in current path:
|
||||
|
||||
// Move file old.txt to new.txt in current path
|
||||
documentsFileProvider.moveItemAtPath(path: "new folder/old.txt", toPath: "new.txt", overwrite: false, completionHandler: nil)
|
||||
documentsProvider.copyItemAtPath(path: "new folder/old.txt", toPath: "new.txt", overwrite: false, completionHandler: nil)
|
||||
|
||||
Move file old.txt to new.txt in current path:
|
||||
|
||||
documentsProvider.moveItemAtPath(path: "new folder/old.txt", toPath: "new.txt", overwrite: false, completionHandler: nil)
|
||||
|
||||
### Delete Files
|
||||
|
||||
documentsFileProvider.removeItemAtPath(path: "new.txt", completionHandler: nil)
|
||||
|
||||
***Caution:*** This method will not delete directories with content.
|
||||
documentsProvider.removeItemAtPath(path: "new.txt", completionHandler: nil)
|
||||
|
||||
***Caution:*** This method will delete directories with all it's content recursively.
|
||||
|
||||
### Retrieve Content of File
|
||||
|
||||
THere is two method for this purpose, one of them loads entire file into NSData and another can load a portion of file.
|
||||
|
||||
documentsFileProvider.contentsAtPath(path: "old.txt:, completionHandler: {
|
||||
documentsProvider.contentsAtPath(path: "old.txt", completionHandler: {
|
||||
(contents: NSData?, error: ErrorType?) -> Void
|
||||
if let contents = contents {
|
||||
print(String(data: contents, encoding: NSUTF8StringEncoding)) // "hello world!"
|
||||
@@ -197,7 +220,7 @@ THere is two method for this purpose, one of them loads entire file into NSData
|
||||
|
||||
If you want to retrieve a portion of file you should can `contentsAtPath` method with offset and length arguments. Please note first byte of file has offset: 0.
|
||||
|
||||
documentsFileProvider.contentsAtPath(path: "old.txt", offset: 2, length: 5, completionHandler: {
|
||||
documentsProvider.contentsAtPath(path: "old.txt", offset: 2, length: 5, completionHandler: {
|
||||
(contents: NSData?, error: ErrorType?) -> Void
|
||||
if let contents = contents {
|
||||
print(String(data: contents, encoding: NSUTF8StringEncoding)) // "llo w"
|
||||
@@ -207,31 +230,43 @@ If you want to retrieve a portion of file you should can `contentsAtPath` method
|
||||
### Write Data To Files
|
||||
|
||||
let data = "What's up Newyork!".dataUsingEncoding(NSUTF8StringEncoding)
|
||||
documentsFileProvider.writeContentsAtPath(path: "old.txt", contents data: data, atomically: true, completionHandler: nil)
|
||||
documentsProvider.writeContentsAtPath(path: "old.txt", contents data: data, atomically: true, completionHandler: nil)
|
||||
|
||||
### Monitoring FIle Changes
|
||||
|
||||
You can monitor updates in some file system (Local and SMB2), there is three methods in supporting provider you can use to register a handler, to unregister and to check whether it's being monitored or not. It's useful to find out when new files added or removed from directory and update user interface. The handler will be dispatched to main threads to avoid UI bugs with a 0.25 sec delay.
|
||||
|
||||
documentsProvider.registerNotifcation(provider.currentPath)
|
||||
{
|
||||
// calling functions to update UI
|
||||
}
|
||||
|
||||
// To discontinue monitoring folders:
|
||||
documentsProvider.unregisterNotifcation(provider.currentPath)
|
||||
|
||||
* **Please note** in LocalFileProvider it will also monitor changes in subfolders. This behaviour can varies according to file system specification.
|
||||
|
||||
## Contribute
|
||||
|
||||
We would love for you to contribute to **FileProvider**, check the `LICENSE` file for more info.
|
||||
|
||||
## Meta
|
||||
|
||||
Amir-Abbas Mousavia – [@amosavian](https://twitter.com/amosavian)
|
||||
Amir-Abbas Mousavian – [@amosavian](https://twitter.com/amosavian)
|
||||
|
||||
Distributed under the MIT license. See `LICENSE` for more information.
|
||||
|
||||
[https://github.com/yourname/github-link](https://github.com/dbader/)
|
||||
|
||||
[swift-image]:https://img.shields.io/badge/swift-2.3-green.svg
|
||||
[swift-image]:https://img.shields.io/badge/swift-2.2-green.svg
|
||||
[swift-url]: https://swift.org/
|
||||
[license-image]: https://img.shields.io/badge/License-MIT-blue.svg
|
||||
[license-url]: LICENSE
|
||||
[codebeat-image]: https://codebeat.co/badges/7b359f48-78eb-4647-ab22-56262a827517
|
||||
[codebeat-url]: https://codebeat.co/projects/github-com-amosavian-fileprovider
|
||||
|
||||
|
||||
<!---
|
||||
[travis-image]: https://img.shields.io/travis/dbader/node-datadog-metrics/master.svg?style=flat-square
|
||||
[travis-url]: https://travis-ci.org/dbader/node-datadog-metrics
|
||||
--->
|
||||
|
||||
[codebeat-image]: https://codebeat.co/badges/c19b47ea-2f9d-45df-8458-b2d952fe9dad
|
||||
[codebeat-url]: https://codebeat.co/projects/github-com-vsouza-awesomeios-com
|
||||
--->
|
||||
+103
-19
@@ -7,11 +7,12 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
#if (iOS)
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
#if (OSX)
|
||||
import AppKit
|
||||
public typealias ImageClass = UIImage
|
||||
#elseif os(OSX)
|
||||
import Cocoa
|
||||
public typealias ImageClass = NSImage
|
||||
#endif
|
||||
|
||||
public enum FileType: String {
|
||||
@@ -63,6 +64,7 @@ extension NSCocoaError: FoundationErrorEnum {}
|
||||
public class FileObject {
|
||||
let absoluteURL: NSURL?
|
||||
let name: String
|
||||
let path: String
|
||||
let size: Int64
|
||||
let createdDate: NSDate?
|
||||
let modifiedDate: NSDate?
|
||||
@@ -70,9 +72,10 @@ public class FileObject {
|
||||
let isHidden: Bool
|
||||
let isReadOnly: Bool
|
||||
|
||||
init(absoluteURL: NSURL, name: String, size: Int64, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool) {
|
||||
init(absoluteURL: NSURL?, name: String, path: String, size: Int64, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool) {
|
||||
self.absoluteURL = absoluteURL
|
||||
self.name = name
|
||||
self.path = path
|
||||
self.size = size
|
||||
self.createdDate = createdDate
|
||||
self.modifiedDate = modifiedDate
|
||||
@@ -81,9 +84,10 @@ public class FileObject {
|
||||
self.isReadOnly = isReadOnly
|
||||
}
|
||||
|
||||
init(name: String, createdDate: NSDate?, modifiedDate: NSDate?, isHidden: Bool, isReadOnly: Bool) {
|
||||
self.absoluteURL = NSURL()
|
||||
init(name: String, path: String, createdDate: NSDate?, modifiedDate: NSDate?, isHidden: Bool, isReadOnly: Bool) {
|
||||
self.absoluteURL = nil
|
||||
self.name = name
|
||||
self.path = path
|
||||
self.size = -1
|
||||
self.createdDate = createdDate
|
||||
self.modifiedDate = modifiedDate
|
||||
@@ -91,6 +95,14 @@ public class FileObject {
|
||||
self.isHidden = isHidden
|
||||
self.isReadOnly = isReadOnly
|
||||
}
|
||||
|
||||
var isDirectory: Bool {
|
||||
return self.fileType == .Directory
|
||||
}
|
||||
|
||||
var isSymLink: Bool {
|
||||
return self.fileType == .SymbolicLink
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +125,8 @@ public protocol FileProviderBasic: class {
|
||||
}
|
||||
|
||||
public protocol FileProviderOperations: FileProviderBasic {
|
||||
var fileOperationDelegate : FileOperationDelegate? { get set }
|
||||
|
||||
func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler)
|
||||
func createFile(fileAttribs: FileObject, atPath: String, contents data: NSData?, completionHandler: SimpleCompletionHandler)
|
||||
func moveItemAtPath(path: String, toPath: String, overwrite: Bool, completionHandler: SimpleCompletionHandler)
|
||||
@@ -134,6 +148,7 @@ public protocol FileProviderReadWrite: FileProviderBasic {
|
||||
public protocol FileProviderMonitor: FileProviderBasic {
|
||||
func registerNotifcation(path: String, eventHandler: (() -> Void))
|
||||
func unregisterNotifcation(path: String)
|
||||
func isRegisteredForNotification(path: String) -> Bool
|
||||
}
|
||||
|
||||
public protocol FileProvider: FileProviderBasic, FileProviderOperations, FileProviderReadWrite {
|
||||
@@ -145,7 +160,7 @@ extension FileProviderBasic {
|
||||
return currentPath.stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: ". /"))
|
||||
}
|
||||
|
||||
func absoluteURL(path: String? = nil) -> NSURL {
|
||||
public func absoluteURL(path: String? = nil) -> NSURL {
|
||||
let rpath: String
|
||||
if let path = path {
|
||||
rpath = path
|
||||
@@ -161,10 +176,53 @@ extension FileProviderBasic {
|
||||
return baseURL.URLByAppendingPathComponent(rpath)
|
||||
}
|
||||
} else {
|
||||
return NSURL(fileURLWithPath: rpath)
|
||||
return NSURL(fileURLWithPath: rpath).URLByStandardizingPath!
|
||||
}
|
||||
}
|
||||
|
||||
public func relativePathOf(url url: NSURL) -> String {
|
||||
guard let baseURL = self.baseURL else { return url.absoluteString }
|
||||
return url.URLByStandardizingPath!.absoluteString.stringByReplacingOccurrencesOfString(baseURL.absoluteString, withString: "/").stringByRemovingPercentEncoding!
|
||||
}
|
||||
|
||||
public func fileByUniqueName(filePath: String) -> String {
|
||||
let fileUrl = NSURL(fileURLWithPath: filePath)
|
||||
let dirPath = fileUrl.URLByDeletingLastPathComponent?.path ?? ""
|
||||
guard let fileName = fileUrl.URLByDeletingPathExtension?.lastPathComponent else {
|
||||
return filePath
|
||||
}
|
||||
let fileExt = fileUrl.pathExtension ?? ""
|
||||
var result = fileName
|
||||
let group = dispatch_group_create()
|
||||
dispatch_group_enter(group)
|
||||
self.contentsOfDirectoryAtPath(dirPath) { (contents, error) in
|
||||
var bareFileName = fileName
|
||||
let number = Int(fileName.componentsSeparatedByString(" ").filter {
|
||||
!$0.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).isEmpty
|
||||
}.last ?? "noname")
|
||||
if let _ = number {
|
||||
result = fileName.componentsSeparatedByString(" ").filter {
|
||||
!$0.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).isEmpty
|
||||
}.dropLast().joinWithSeparator(" ")
|
||||
bareFileName = result
|
||||
}
|
||||
var i = number ?? 2
|
||||
let similiar = contents.map {
|
||||
$0.absoluteURL?.lastPathComponent ?? $0.name
|
||||
}.filter {
|
||||
$0.hasPrefix(result) && (!fileExt.isEmpty && $0.hasSuffix("." + fileExt))
|
||||
}
|
||||
while similiar.contains(result + (!fileExt.isEmpty ? "." + fileExt : "")) {
|
||||
result = "\(bareFileName) \(i)"
|
||||
i += 1
|
||||
}
|
||||
dispatch_group_leave(group)
|
||||
}
|
||||
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
|
||||
let finalFile = result + (!fileExt.isEmpty ? "." + fileExt : "")
|
||||
return (dirPath as NSString).stringByAppendingPathComponent(finalFile)
|
||||
}
|
||||
|
||||
internal func throwError(path: String, code: FoundationErrorEnum) -> NSError {
|
||||
let fileURL = self.absoluteURL(path)
|
||||
let domain: String
|
||||
@@ -218,17 +276,13 @@ extension FileProviderBasic {
|
||||
}
|
||||
}
|
||||
|
||||
#if (iOS)
|
||||
|
||||
public protocol ExtendedFileProvider: FileProvider {
|
||||
func thumbnailOfFileAtPath(path: String, dimension: CGSize, completionHandler: ((image: UIImage?, error: ErrorType?) -> Void))
|
||||
func thumbnailOfFileSupported(path: String) -> Bool
|
||||
func propertiesOfFileSupported(path: String) -> Bool
|
||||
func thumbnailOfFileAtPath(path: String, dimension: CGSize, completionHandler: ((image: ImageClass?, error: ErrorType?) -> Void))
|
||||
func propertiesOfFileAtPath(path: String, completionHandler: ((propertiesDictionary: [String: AnyObject], keys: [String], error: ErrorType?) -> Void))
|
||||
}
|
||||
#elseif (OSX)
|
||||
public protocol ExtendedFileProvider: FileProvider {
|
||||
func thumbnailOfFileAtPath(path: String, dimension: CGSize, completionHandler: ((image: NSImage?, error: ErrorType?) -> Void))
|
||||
func propertiesOfFileAtPath(path: String, completionHandler: ((propertiesDictionary: [String: AnyObject], keys: [String], error: ErrorType?) -> Void))
|
||||
}
|
||||
#endif
|
||||
|
||||
public enum FileOperation {
|
||||
case Create (path: String)
|
||||
@@ -237,12 +291,42 @@ public enum FileOperation {
|
||||
case Modify (path: String)
|
||||
case Remove (path: String)
|
||||
case Link (link: String, target: String)
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .Create(path: _): return "Create"
|
||||
case .Copy(source: _, destination: _): return "Copy"
|
||||
case .Move(source: _, destination: _): return "Move"
|
||||
case .Modify(path: _): return "Modify"
|
||||
case .Remove(path: _): return "Remove"
|
||||
case .Link(link: _, target: _): return "Link"
|
||||
}
|
||||
}
|
||||
|
||||
var actionDescription: String {
|
||||
switch self {
|
||||
case .Create(path: _): return "Creating"
|
||||
case .Copy(source: _, destination: _): return "Copying"
|
||||
case .Move(source: _, destination: _): return "Moving"
|
||||
case .Modify(path: _): return "Modifying"
|
||||
case .Remove(path: _): return "Removing"
|
||||
case .Link(link: _, target: _): return "Linking"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public protocol FileProviderDelegate {
|
||||
public protocol FileProviderDelegate: class {
|
||||
func fileproviderSucceed(fileProvider: FileProviderOperations, operation: FileOperation)
|
||||
func fileproviderFailed(fileProvider: FileProviderOperations, operation: FileOperation)
|
||||
func fileproviderProgress(fileProvider: FileProviderOperations, operation: FileOperation, progress: Float)
|
||||
}
|
||||
|
||||
|
||||
public protocol FileOperationDelegate: class {
|
||||
|
||||
/// fileProvider(_:shouldOperate:) gives the delegate an opportunity to filter the file operation. Returning true from this method will allow the copy to happen. Returning false from this method causes the item in question to be skipped. If the item skipped was a directory, no children of that directory will be subject of the operation, nor will the delegate be notified of those children.
|
||||
func fileProvider(fileProvider: FileProviderOperations, shouldDoOperation operation: FileOperation) -> Bool
|
||||
|
||||
/// fileProvider:shouldProceedAfterError:copyingItemAtPath:toPath: gives the delegate an opportunity to recover from or continue copying after an error. If an error occurs, the error object will contain an ErrorType indicating the problem. The source path and destination paths are also provided. If this method returns true, the FileProvider instance will continue as if the error had not occurred. If this method returns false, the NSFileManager instance will stop copying, return false from copyItemAtPath:toPath:error: and the error will be provied there.
|
||||
func fileProvider(fileProvider: FileProviderOperations, shouldProceedAfterError error: ErrorType, operation: FileOperation) -> Bool
|
||||
}
|
||||
+159
-40
@@ -11,30 +11,39 @@ import Foundation
|
||||
public final class LocalFileObject: FileObject {
|
||||
let allocatedSize: Int64
|
||||
|
||||
init(absoluteURL: NSURL, name: String, size: Int64, allocatedSize: Int64, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool) {
|
||||
init(absoluteURL: NSURL, name: String, path: String, size: Int64, allocatedSize: Int64, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool) {
|
||||
self.allocatedSize = allocatedSize
|
||||
super.init(absoluteURL: absoluteURL, name: name, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
|
||||
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
public let type = "NSFileManager"
|
||||
public let type = "Local"
|
||||
public var isPathRelative: Bool = true
|
||||
public var baseURL: NSURL? = LocalFileProvider.defaultBaseURL()
|
||||
public var currentPath: String = ""
|
||||
public var dispatch_queue: dispatch_queue_t
|
||||
public var delegate: FileProviderDelegate?
|
||||
public var operation_queue: dispatch_queue_t
|
||||
public weak var delegate: FileProviderDelegate?
|
||||
public let credential: NSURLCredential? = nil
|
||||
|
||||
let fileManager = NSFileManager()
|
||||
public let fileManager = NSFileManager()
|
||||
public let opFileManager = NSFileManager()
|
||||
private var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
|
||||
|
||||
init () {
|
||||
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_SERIAL)
|
||||
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
|
||||
operation_queue = dispatch_queue_create("FileProvider.\(type).Operation", DISPATCH_QUEUE_SERIAL)
|
||||
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
|
||||
opFileManager.delegate = fileProviderManagerDelegate
|
||||
}
|
||||
|
||||
init (baseURL: NSURL) {
|
||||
self.baseURL = baseURL
|
||||
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_SERIAL)
|
||||
dispatch_queue = dispatch_queue_create("FileProvider.\(type)", DISPATCH_QUEUE_CONCURRENT)
|
||||
operation_queue = dispatch_queue_create("FileProvider.\(type).Operation", DISPATCH_QUEUE_SERIAL)
|
||||
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
|
||||
opFileManager.delegate = fileProviderManagerDelegate
|
||||
}
|
||||
|
||||
private static func defaultBaseURL() -> NSURL {
|
||||
@@ -56,7 +65,7 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
private func attributesOfItemAtURL(fileURL: NSURL) -> LocalFileObject {
|
||||
internal func attributesOfItemAtURL(fileURL: NSURL) -> LocalFileObject {
|
||||
var namev, sizev, allocated, filetypev, creationDatev, modifiedDatev, hiddenv, readonlyv: AnyObject?
|
||||
_ = try? fileURL.getResourceValue(&namev, forKey: NSURLNameKey)
|
||||
_ = try? fileURL.getResourceValue(&sizev, forKey: NSURLFileSizeKey)
|
||||
@@ -66,7 +75,13 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
_ = try? fileURL.getResourceValue(&filetypev, forKey: NSURLFileResourceTypeKey)
|
||||
_ = try? fileURL.getResourceValue(&hiddenv, forKey: NSURLIsHiddenKey)
|
||||
_ = try? fileURL.getResourceValue(&readonlyv, forKey: NSURLVolumeIsReadOnlyKey)
|
||||
let fileAttr = LocalFileObject(absoluteURL: fileURL, name: namev as! String, size: sizev?.longLongValue ?? -1, allocatedSize: allocated?.longLongValue ?? -1, createdDate: creationDatev as? NSDate, modifiedDate: modifiedDatev as? NSDate, fileType: FileType(urlResourceTypeValue: filetypev as? String ?? ""), isHidden: hiddenv?.boolValue ?? false, isReadOnly: readonlyv?.boolValue ?? false)
|
||||
let path: String
|
||||
if isPathRelative {
|
||||
path = self.relativePathOf(url: fileURL)
|
||||
} else {
|
||||
path = fileURL.path!
|
||||
}
|
||||
let fileAttr = LocalFileObject(absoluteURL: fileURL, name: namev as! String, path: path, size: sizev?.longLongValue ?? -1, allocatedSize: allocated?.longLongValue ?? -1, createdDate: creationDatev as? NSDate, modifiedDate: modifiedDatev as? NSDate, fileType: FileType(urlResourceTypeValue: filetypev as? String ?? ""), isHidden: hiddenv?.boolValue ?? false, isReadOnly: readonlyv?.boolValue ?? false)
|
||||
return fileAttr
|
||||
}
|
||||
|
||||
@@ -76,10 +91,12 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
public weak var fileOperationDelegate : FileOperationDelegate?
|
||||
|
||||
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
dispatch_async(dispatch_queue) {
|
||||
dispatch_async(operation_queue) {
|
||||
do {
|
||||
try self.fileManager.createDirectoryAtURL(self.absoluteURL(atPath).URLByAppendingPathComponent(folderName), withIntermediateDirectories: true, attributes: [:])
|
||||
try self.opFileManager.createDirectoryAtURL(self.absoluteURL(atPath).URLByAppendingPathComponent(folderName), withIntermediateDirectories: true, attributes: [:])
|
||||
completionHandler?(error: nil)
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .Create(path: (atPath as NSString).stringByAppendingPathComponent(folderName) + "/"))
|
||||
@@ -94,7 +111,7 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
}
|
||||
|
||||
public func createFile(fileAttribs: FileObject, atPath: String, contents data: NSData?, completionHandler: SimpleCompletionHandler) {
|
||||
dispatch_async(dispatch_queue) {
|
||||
dispatch_async(operation_queue) {
|
||||
let fileURL = self.absoluteURL(atPath).URLByAppendingPathComponent(fileAttribs.name)
|
||||
var attributes = [String : AnyObject]()
|
||||
if let createdDate = fileAttribs.createdDate {
|
||||
@@ -106,7 +123,7 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
if fileAttribs.isReadOnly {
|
||||
attributes[NSFilePosixPermissions] = NSNumber(short: 365 /*555 o*/)
|
||||
}
|
||||
let success = self.fileManager.createFileAtPath(fileURL.path!, contents: data, attributes: attributes)
|
||||
let success = self.opFileManager.createFileAtPath(fileURL.path!, contents: data, attributes: attributes)
|
||||
if success {
|
||||
do {
|
||||
try fileURL.setResourceValue(fileAttribs.isHidden, forKey: NSURLIsHiddenKey)
|
||||
@@ -126,13 +143,13 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
|
||||
public func moveItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
|
||||
// FIXME: progress
|
||||
dispatch_async(dispatch_queue) {
|
||||
dispatch_async(operation_queue) {
|
||||
if !overwrite && self.fileManager.fileExistsAtPath(self.absoluteURL(toPath).path ?? "") {
|
||||
completionHandler?(error: self.throwError(toPath, code: NSURLError.CannotMoveFile))
|
||||
return
|
||||
}
|
||||
do {
|
||||
try self.fileManager.moveItemAtURL(self.absoluteURL(path), toURL: self.absoluteURL(toPath))
|
||||
try self.opFileManager.moveItemAtURL(self.absoluteURL(path), toURL: self.absoluteURL(toPath))
|
||||
completionHandler?(error: nil)
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .Move(source: path, destination: toPath))
|
||||
@@ -148,13 +165,13 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
|
||||
public func copyItemAtPath(path: String, toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
|
||||
// FIXME: progress, for files > 100mb, monitor file by another thread, for dirs check copied items count
|
||||
dispatch_async(dispatch_queue) {
|
||||
dispatch_async(operation_queue) {
|
||||
if !overwrite && self.fileManager.fileExistsAtPath(self.absoluteURL(toPath).path ?? "") {
|
||||
completionHandler?(error: self.throwError(toPath, code: NSURLError.CannotWriteToFile))
|
||||
return
|
||||
}
|
||||
do {
|
||||
try self.fileManager.copyItemAtURL(self.absoluteURL(path), toURL: self.absoluteURL(toPath))
|
||||
try self.opFileManager.copyItemAtURL(self.absoluteURL(path), toURL: self.absoluteURL(toPath))
|
||||
completionHandler?(error: nil)
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .Copy(source: path, destination: toPath))
|
||||
@@ -169,9 +186,9 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
}
|
||||
|
||||
public func removeItemAtPath(path: String, completionHandler: SimpleCompletionHandler) {
|
||||
dispatch_async(dispatch_queue) {
|
||||
dispatch_async(operation_queue) {
|
||||
do {
|
||||
try self.fileManager.removeItemAtURL(self.absoluteURL(path))
|
||||
try self.opFileManager.removeItemAtURL(self.absoluteURL(path))
|
||||
completionHandler?(error: nil)
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .Remove(path: path))
|
||||
@@ -186,9 +203,9 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
}
|
||||
|
||||
public func copyLocalFileToPath(localFile: NSURL, toPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
dispatch_async(dispatch_queue) {
|
||||
dispatch_async(operation_queue) {
|
||||
do {
|
||||
try self.fileManager.copyItemAtURL(localFile, toURL: self.absoluteURL(toPath))
|
||||
try self.opFileManager.copyItemAtURL(localFile, toURL: self.absoluteURL(toPath))
|
||||
completionHandler?(error: nil)
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .Copy(source: localFile.absoluteString, destination: toPath))
|
||||
@@ -203,9 +220,9 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
}
|
||||
|
||||
public func copyPathToLocalFile(path: String, toLocalURL: NSURL, completionHandler: SimpleCompletionHandler) {
|
||||
dispatch_async(dispatch_queue) {
|
||||
dispatch_async(operation_queue) {
|
||||
do {
|
||||
try self.fileManager.copyItemAtURL(self.absoluteURL(path), toURL: toLocalURL)
|
||||
try self.opFileManager.copyItemAtURL(self.absoluteURL(path), toURL: toLocalURL)
|
||||
completionHandler?(error: nil)
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .Copy(source: path, destination: toLocalURL.absoluteString))
|
||||
@@ -231,7 +248,7 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
// So we have to fallback to POSIX provided methods
|
||||
dispatch_async(dispatch_queue) {
|
||||
let aPath = self.absoluteURL(path).path!
|
||||
if self.attributesOfItemAtURL(self.absoluteURL(path)).fileType == .Directory {
|
||||
if self.attributesOfItemAtURL(self.absoluteURL(path)).isDirectory {
|
||||
self.throwError(path, code: NSURLError.FileIsDirectory)
|
||||
}
|
||||
if !self.fileManager.fileExistsAtPath(aPath) {
|
||||
@@ -256,7 +273,7 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
}
|
||||
|
||||
public func writeContentsAtPath(path: String, contents data: NSData, atomically: Bool, completionHandler: SimpleCompletionHandler) {
|
||||
dispatch_async(dispatch_queue) {
|
||||
dispatch_async(operation_queue) {
|
||||
data.writeToURL(self.absoluteURL(path), atomically: atomically)
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .Modify(path: path))
|
||||
@@ -282,38 +299,46 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
private var monitorDictionary = [String : LocalFolderMonitor]()
|
||||
private var monitors = [LocalFolderMonitor]()
|
||||
|
||||
public func registerNotifcation(path: String, eventHandler: (() -> Void)) {
|
||||
self.unregisterNotifcation(path)
|
||||
let absurl = self.absoluteURL(path)
|
||||
var isdirv: AnyObject?
|
||||
do {
|
||||
try absoluteURL(path).getResourceValue(&isdirv, forKey: NSURLIsDirectoryKey)
|
||||
try absurl.getResourceValue(&isdirv, forKey: NSURLIsDirectoryKey)
|
||||
} catch _ {
|
||||
}
|
||||
if !(isdirv?.boolValue ?? false) {
|
||||
return
|
||||
}
|
||||
let monitor = LocalFolderMonitor(url: absoluteURL(path)) {
|
||||
let monitor = LocalFolderMonitor(url: absurl) {
|
||||
eventHandler()
|
||||
}
|
||||
monitor.start()
|
||||
monitorDictionary[path] = monitor
|
||||
monitors.append(monitor)
|
||||
}
|
||||
|
||||
public func unregisterNotifcation(path: String) {
|
||||
if let prevMonitor = monitorDictionary[path] {
|
||||
prevMonitor.stop()
|
||||
monitorDictionary.removeValueForKey(path)
|
||||
var removedMonitor: LocalFolderMonitor?
|
||||
for (i, monitor) in monitors.enumerate() {
|
||||
if self.relativePathOf(url: monitor.url) == path {
|
||||
removedMonitor = monitors.removeAtIndex(i)
|
||||
}
|
||||
}
|
||||
removedMonitor?.stop()
|
||||
}
|
||||
|
||||
public func isRegisteredForNotification(path: String) -> Bool {
|
||||
return monitors.map( { self.relativePathOf(url: $0.url) } ).contains(path)
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalFileProvider {
|
||||
public func createSymbolicLinkAtPath(path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
dispatch_async(dispatch_queue) {
|
||||
dispatch_async(operation_queue) {
|
||||
do {
|
||||
try self.fileManager.createSymbolicLinkAtURL(self.absoluteURL(path), withDestinationURL: self.absoluteURL(destPath))
|
||||
try self.opFileManager.createSymbolicLinkAtURL(self.absoluteURL(path), withDestinationURL: self.absoluteURL(destPath))
|
||||
completionHandler?(error: nil)
|
||||
dispatch_async(dispatch_get_main_queue(), {
|
||||
self.delegate?.fileproviderSucceed(self, operation: .Link(link: path, target: destPath))
|
||||
@@ -328,15 +353,95 @@ extension LocalFileProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private class LocalFolderMonitor {
|
||||
class LocalFileProviderManagerDelegate: NSObject, NSFileManagerDelegate {
|
||||
weak var provider: LocalFileProvider?
|
||||
|
||||
init(provider: LocalFileProvider) {
|
||||
self.provider = provider
|
||||
}
|
||||
|
||||
func fileManager(fileManager: NSFileManager, shouldCopyItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
|
||||
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .Copy(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(fileManager: NSFileManager, shouldMoveItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
|
||||
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .Move(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(fileManager: NSFileManager, shouldRemoveItemAtURL URL: NSURL) -> Bool {
|
||||
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let path = provider.relativePathOf(url: URL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .Remove(path: path))
|
||||
}
|
||||
|
||||
func fileManager(fileManager: NSFileManager, shouldLinkItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
|
||||
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
|
||||
return true
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldDoOperation: .Link(link: srcPath, target: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(fileManager: NSFileManager, shouldProceedAfterError error: NSError, copyingItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
|
||||
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .Copy(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(fileManager: NSFileManager, shouldProceedAfterError error: NSError, movingItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
|
||||
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .Move(source: srcPath, destination: dstPath))
|
||||
}
|
||||
|
||||
func fileManager(fileManager: NSFileManager, shouldProceedAfterError error: NSError, removingItemAtURL URL: NSURL) -> Bool {
|
||||
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let path = provider.relativePathOf(url: URL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .Remove(path: path))
|
||||
}
|
||||
|
||||
func fileManager(fileManager: NSFileManager, shouldProceedAfterError error: NSError, linkingItemAtURL srcURL: NSURL, toURL dstURL: NSURL) -> Bool {
|
||||
guard let provider = self.provider, delegate = provider.fileOperationDelegate else {
|
||||
return false
|
||||
}
|
||||
let srcPath = provider.relativePathOf(url: srcURL)
|
||||
let dstPath = provider.relativePathOf(url: dstURL)
|
||||
return delegate.fileProvider(provider, shouldProceedAfterError: error, operation: .Link(link: srcPath, target: dstPath))
|
||||
}
|
||||
}
|
||||
|
||||
internal class LocalFolderMonitor {
|
||||
private let source: dispatch_source_t
|
||||
private let descriptor: CInt
|
||||
private let qq: dispatch_queue_t = dispatch_get_main_queue()
|
||||
private let qq: dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||
private var state: Bool = false
|
||||
private var monitoredTime: NSTimeInterval = NSDate().timeIntervalSinceReferenceDate
|
||||
var url: NSURL
|
||||
|
||||
/// Creates a folder monitor object with monitoring enabled.
|
||||
init(url: NSURL, handler: ()->Void) {
|
||||
|
||||
self.url = url
|
||||
descriptor = open(url.fileSystemRepresentation, O_EVTONLY)
|
||||
|
||||
source = dispatch_source_create(
|
||||
@@ -345,8 +450,23 @@ private class LocalFolderMonitor {
|
||||
DISPATCH_VNODE_WRITE,
|
||||
qq
|
||||
)
|
||||
|
||||
dispatch_source_set_event_handler(source, handler)
|
||||
// Folder monitoring is recursive and deep. Monitoring a root folder may be very costly
|
||||
// We have a 0.2 second delay to ensure we wont call handler 1000s times when there is
|
||||
// a huge file operation. This ensures app will work smoothly while this 250 milisec won't
|
||||
// affect user experince much
|
||||
let main_handler: ()->Void = {
|
||||
if NSDate().timeIntervalSinceReferenceDate < self.monitoredTime + 0.2 {
|
||||
return
|
||||
}
|
||||
self.monitoredTime = NSDate().timeIntervalSinceReferenceDate
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC) / 4), dispatch_get_main_queue(), {
|
||||
handler()
|
||||
})
|
||||
}
|
||||
dispatch_source_set_event_handler(source, main_handler)
|
||||
dispatch_source_set_cancel_handler(source) {
|
||||
close(self.descriptor)
|
||||
}
|
||||
start()
|
||||
}
|
||||
|
||||
@@ -367,7 +487,6 @@ private class LocalFolderMonitor {
|
||||
}
|
||||
|
||||
deinit {
|
||||
close(descriptor)
|
||||
dispatch_source_cancel(source)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
|
||||
var baseURL: NSURL?
|
||||
var currentPath: String = ""
|
||||
var dispatch_queue: dispatch_queue_t
|
||||
var delegate: FileProviderDelegate?
|
||||
weak var delegate: FileProviderDelegate?
|
||||
let credential: NSURLCredential?
|
||||
|
||||
typealias FileObjectClass = FileObject
|
||||
@@ -40,6 +40,8 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
weak var fileOperationDelegate: FileOperationDelegate?
|
||||
|
||||
func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
|
||||
NotImplemented()
|
||||
}
|
||||
@@ -91,7 +93,10 @@ class SMBFileProvider: FileProvider, FileProviderMonitor {
|
||||
func unregisterNotifcation(path: String) {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
|
||||
func isRegisteredForNotification(path: String) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: basic CIFS interactivity
|
||||
|
||||
@@ -51,10 +51,10 @@ public final class WebDavFileObject: FileObject {
|
||||
let contentType: String
|
||||
let entryTag: String?
|
||||
|
||||
init(absoluteURL: NSURL, name: String, size: Int64, contentType: String, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool, entryTag: String?) {
|
||||
init(absoluteURL: NSURL, name: String, path: String, size: Int64, contentType: String, createdDate: NSDate?, modifiedDate: NSDate?, fileType: FileType, isHidden: Bool, isReadOnly: Bool, entryTag: String?) {
|
||||
self.contentType = contentType
|
||||
self.entryTag = entryTag
|
||||
super.init(absoluteURL: absoluteURL, name: name, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
|
||||
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,14 +71,14 @@ public class WebDAVFileProvider: NSObject, FileProviderBasic {
|
||||
assert(_session == nil, "It's not effective to change dispatch_queue property after session is initialized.")
|
||||
}
|
||||
}
|
||||
public var delegate: FileProviderDelegate?
|
||||
public weak var delegate: FileProviderDelegate?
|
||||
public let credential: NSURLCredential?
|
||||
|
||||
private var _session: NSURLSession?
|
||||
private var session: NSURLSession {
|
||||
if _session == nil {
|
||||
let queue = NSOperationQueue()
|
||||
queue.underlyingQueue = dispatch_queue
|
||||
//queue.underlyingQueue = dispatch_queue
|
||||
_session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: queue)
|
||||
}
|
||||
return _session!
|
||||
@@ -145,6 +145,8 @@ public class WebDAVFileProvider: NSObject, FileProviderBasic {
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
public weak var fileOperationDelegate: FileOperationDelegate?
|
||||
}
|
||||
|
||||
extension WebDAVFileProvider: FileProviderOperations {
|
||||
@@ -479,7 +481,7 @@ internal extension WebDAVFileProvider {
|
||||
let contentType = davResponse.prop["getcontenttype"] ?? "octet/stream"
|
||||
let isDirectory = contentType == "httpd/unix-directory"
|
||||
let entryTag = davResponse.prop["getetag"]
|
||||
return WebDavFileObject(absoluteURL: href, name: name, size: size, contentType: contentType, createdDate: createdDate, modifiedDate: modifiedDate, fileType: isDirectory ? .Directory : .Regular, isHidden: false, isReadOnly: false, entryTag: entryTag)
|
||||
return WebDavFileObject(absoluteURL: href, name: name, path: href.path ?? name, size: size, contentType: contentType, createdDate: createdDate, modifiedDate: modifiedDate, fileType: isDirectory ? .Directory : .Regular, isHidden: false, isReadOnly: false, entryTag: entryTag)
|
||||
}
|
||||
|
||||
private func delegateNotify(operation: FileOperation, error: ErrorType?) {
|
||||
|
||||
Reference in New Issue
Block a user