Bug fixes for path handling and LocalFolderMonitor

- Bugs in fileByUniqueName(), relativePath() and absoluteURL fixed
- Introduced delay in LocalFolderMonitor to prevent excess handler calling
This commit is contained in:
Amir Abbas Mousavian
2016-07-13 13:30:32 +04:30
parent f0cd7846d8
commit 3301d2c004
2 changed files with 73 additions and 37 deletions
+31 -10
View File
@@ -95,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
}
}
@@ -168,38 +176,51 @@ 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.absoluteString.stringByReplacingOccurrencesOfString(baseURL.absoluteString, withString: "/").stringByRemovingPercentEncoding!
return url.URLByStandardizingPath!.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
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 i = Int(fileName.componentsSeparatedByString(" ").filter {
var bareFileName = fileName
var number = Int(fileName.componentsSeparatedByString(" ").filter {
!$0.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).isEmpty
}.last ?? "noname") ?? 2
}.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(fileName) && $0.hasSuffix("." + (filePath as NSString).pathExtension)
$0.hasPrefix(result) && (!fileExt.isEmpty && $0.hasSuffix("." + fileExt))
}
while similiar.contains(result) {
result = ((((fileName as NSString).stringByDeletingPathExtension + " \(i)") as NSString).pathExtension as NSString).stringByAppendingPathExtension((filePath as NSString).pathExtension)!
while similiar.contains(result + (!fileExt.isEmpty ? "." + fileExt : "")) {
result = "\(bareFileName) \(i)"
i += 1
}
dispatch_group_leave(group)
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
return (dirPath as NSString).stringByAppendingPathComponent(result)
let finalFile = result + (!fileExt.isEmpty ? "." + fileExt : "")
return (dirPath as NSString).stringByAppendingPathComponent(finalFile)
}
internal func throwError(path: String, code: FoundationErrorEnum) -> NSError {
+42 -27
View File
@@ -23,23 +23,27 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
public var baseURL: NSURL? = LocalFileProvider.defaultBaseURL()
public var currentPath: String = ""
public var dispatch_queue: dispatch_queue_t
public var operation_queue: dispatch_queue_t
public weak var delegate: FileProviderDelegate?
public let credential: NSURLCredential? = nil
public let fileManager = NSFileManager()
public let opFileManager = NSFileManager()
private var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
init () {
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)
fileManager.delegate = fileProviderManagerDelegate
opFileManager.delegate = fileProviderManagerDelegate
}
init (baseURL: NSURL) {
self.baseURL = baseURL
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)
fileManager.delegate = fileProviderManagerDelegate
opFileManager.delegate = fileProviderManagerDelegate
}
private static func defaultBaseURL() -> NSURL {
@@ -61,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)
@@ -90,9 +94,9 @@ 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) + "/"))
@@ -107,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 {
@@ -119,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)
@@ -139,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))
@@ -161,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))
@@ -182,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))
@@ -199,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))
@@ -216,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))
@@ -244,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) {
@@ -269,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))
@@ -316,12 +320,13 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
}
public func unregisterNotifcation(path: String) {
var removedMonitor: LocalFolderMonitor?
for (i, monitor) in monitors.enumerate() {
if self.relativePathOf(url: monitor.url) == path {
monitor.stop()
monitors.removeAtIndex(i)
removedMonitor = monitors.removeAtIndex(i)
}
}
removedMonitor?.stop()
}
public func isRegisteredForNotification(path: String) -> Bool {
@@ -331,9 +336,9 @@ public class LocalFileProvider: FileProvider, FileProviderMonitor {
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))
@@ -431,11 +436,12 @@ internal class LocalFolderMonitor {
private let descriptor: CInt
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(
@@ -444,13 +450,23 @@ internal class LocalFolderMonitor {
DISPATCH_VNODE_WRITE,
qq
)
// 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 = {
dispatch_async(dispatch_get_main_queue(), {
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)
self.url = url
dispatch_source_set_cancel_handler(source) {
close(self.descriptor)
}
start()
}
@@ -471,7 +487,6 @@ internal class LocalFolderMonitor {
}
deinit {
close(descriptor)
dispatch_source_cancel(source)
}
}