FileOpreationDelegate, delegate strong reference bug, fileByUniqueName method

This commit is contained in:
Amir Abbas Mousavian
2016-07-11 13:31:22 +04:30
parent 8fd7da669d
commit f0cd7846d8
4 changed files with 218 additions and 44 deletions
+81 -18
View File
@@ -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
@@ -113,6 +117,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 +140,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 +152,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
@@ -165,6 +172,36 @@ extension FileProviderBasic {
}
}
public func relativePathOf(url url: NSURL) -> String {
guard let baseURL = self.baseURL else { return url.absoluteString }
return url.absoluteString.stringByReplacingOccurrencesOfString(baseURL.absoluteString, withString: "/").stringByRemovingPercentEncoding!
}
public func fileByUniqueName(filePath: String) -> String {
let dirPath = (filePath as NSString).stringByDeletingLastPathComponent
let fileName = ((filePath as NSString).lastPathComponent as NSString).stringByDeletingPathExtension
var result = fileName
let group = dispatch_group_create()
dispatch_group_enter(group)
self.contentsOfDirectoryAtPath(dirPath) { (contents, error) in
var i = Int(fileName.componentsSeparatedByString(" ").filter {
!$0.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).isEmpty
}.last ?? "noname") ?? 2
let similiar = contents.map {
$0.absoluteURL?.lastPathComponent ?? $0.name
}.filter {
$0.hasPrefix(fileName) && $0.hasSuffix("." + (filePath as NSString).pathExtension)
}
while similiar.contains(result) {
result = ((((fileName as NSString).stringByDeletingPathExtension + " \(i)") as NSString).pathExtension as NSString).stringByAppendingPathExtension((filePath as NSString).pathExtension)!
i += 1
}
dispatch_group_leave(group)
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
return (dirPath as NSString).stringByAppendingPathComponent(result)
}
internal func throwError(path: String, code: FoundationErrorEnum) -> NSError {
let fileURL = self.absoluteURL(path)
let domain: String
@@ -218,17 +255,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 +270,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
}
+123 -19
View File
@@ -11,30 +11,35 @@ 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 weak var delegate: FileProviderDelegate?
public let credential: NSURLCredential? = nil
let fileManager = NSFileManager()
public let fileManager = 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)
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
fileManager.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)
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
fileManager.delegate = fileProviderManagerDelegate
}
private static func defaultBaseURL() -> NSURL {
@@ -66,7 +71,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,6 +87,8 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
}
}
public weak var fileOperationDelegate : FileOperationDelegate?
public func createFolder(folderName: String, atPath: String, completionHandler: SimpleCompletionHandler) {
dispatch_async(dispatch_queue) {
do {
@@ -282,31 +295,38 @@ 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)
for (i, monitor) in monitors.enumerate() {
if self.relativePathOf(url: monitor.url) == path {
monitor.stop()
monitors.removeAtIndex(i)
}
}
}
public func isRegisteredForNotification(path: String) -> Bool {
return monitors.map( { self.relativePathOf(url: $0.url) } ).contains(path)
}
}
extension LocalFileProvider {
@@ -328,11 +348,90 @@ 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
var url: NSURL
/// Creates a folder monitor object with monitoring enabled.
init(url: NSURL, handler: ()->Void) {
@@ -345,8 +444,13 @@ private class LocalFolderMonitor {
DISPATCH_VNODE_WRITE,
qq
)
dispatch_source_set_event_handler(source, handler)
let main_handler: ()->Void = {
dispatch_async(dispatch_get_main_queue(), {
handler()
})
}
dispatch_source_set_event_handler(source, main_handler)
self.url = url
start()
}
+7 -2
View File
@@ -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
+7 -5
View File
@@ -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?) {