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:
+31
-10
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user