497 lines
24 KiB
Swift
497 lines
24 KiB
Swift
//
|
|
// LocalFileProvider.swift
|
|
// FileProvider
|
|
//
|
|
// Created by Amir Abbas Mousavian.
|
|
// Copyright © 2016 Mousavian. Distributed under MIT license.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
public final class LocalFileObject: FileObject {
|
|
public let allocatedSize: Int64
|
|
// codebeat:disable[ARITY]
|
|
public init(absoluteURL: URL, name: String, path: String, size: Int64 = -1, allocatedSize: Int64 = 0, createdDate: Date? = nil, modifiedDate: Date? = nil, fileType: FileType = .regular, isHidden: Bool = false, isReadOnly: Bool = false) {
|
|
self.allocatedSize = allocatedSize
|
|
super.init(absoluteURL: absoluteURL, name: name, path: path, size: size, createdDate: createdDate, modifiedDate: modifiedDate, fileType: fileType, isHidden: isHidden, isReadOnly: isReadOnly)
|
|
}
|
|
// codebeat:enable[ARITY]
|
|
}
|
|
|
|
open class LocalFileProvider: FileProvider, FileProviderMonitor {
|
|
open let type = "Local"
|
|
open var isPathRelative: Bool = true
|
|
open var baseURL: URL? = LocalFileProvider.defaultBaseURL()
|
|
open var currentPath: String = ""
|
|
open var dispatch_queue: DispatchQueue
|
|
open var operation_queue: DispatchQueue
|
|
open weak var delegate: FileProviderDelegate?
|
|
open let credential: URLCredential? = nil
|
|
|
|
open let fileManager = FileManager()
|
|
open let opFileManager = FileManager()
|
|
fileprivate var fileProviderManagerDelegate: LocalFileProviderManagerDelegate? = nil
|
|
|
|
public init () {
|
|
dispatch_queue = DispatchQueue(label: "FileProvider.\(type)", attributes: DispatchQueue.Attributes.concurrent)
|
|
operation_queue = DispatchQueue(label: "FileProvider.\(type).Operation", attributes: [])
|
|
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
|
|
opFileManager.delegate = fileProviderManagerDelegate
|
|
}
|
|
|
|
public init (baseURL: URL) {
|
|
self.baseURL = baseURL
|
|
dispatch_queue = DispatchQueue(label: "FileProvider.\(type)", attributes: DispatchQueue.Attributes.concurrent)
|
|
operation_queue = DispatchQueue(label: "FileProvider.\(type).Operation", attributes: [])
|
|
fileProviderManagerDelegate = LocalFileProviderManagerDelegate(provider: self)
|
|
opFileManager.delegate = fileProviderManagerDelegate
|
|
}
|
|
|
|
fileprivate static func defaultBaseURL() -> URL {
|
|
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true);
|
|
return URL(fileURLWithPath: paths[0])
|
|
}
|
|
|
|
open func contentsOfDirectory(path: String, completionHandler: @escaping ((_ contents: [FileObject], _ error: Error?) -> Void)) {
|
|
dispatch_queue.async {
|
|
do {
|
|
let contents = try self.fileManager.contentsOfDirectory(at: self.absoluteURL(path), includingPropertiesForKeys: [URLResourceKey.nameKey, URLResourceKey.fileSizeKey, URLResourceKey.fileAllocatedSizeKey, URLResourceKey.creationDateKey, URLResourceKey.contentModificationDateKey, URLResourceKey.isHiddenKey, URLResourceKey.volumeIsReadOnlyKey], options: FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants)
|
|
let filesAttributes = contents.map({ (fileURL) -> LocalFileObject in
|
|
return self.attributesOfItem(url: fileURL)
|
|
})
|
|
completionHandler(filesAttributes, nil)
|
|
} catch let e as NSError {
|
|
completionHandler([], e)
|
|
}
|
|
}
|
|
}
|
|
|
|
internal func attributesOfItem(url fileURL: URL) -> LocalFileObject {
|
|
var namev, sizev, allocated, filetypev, creationDatev, modifiedDatev, hiddenv, readonlyv: AnyObject?
|
|
_ = try? (fileURL as NSURL).getResourceValue(&namev, forKey: URLResourceKey.nameKey)
|
|
_ = try? (fileURL as NSURL).getResourceValue(&sizev, forKey: URLResourceKey.fileSizeKey)
|
|
_ = try? (fileURL as NSURL).getResourceValue(&allocated, forKey: URLResourceKey.fileAllocatedSizeKey)
|
|
_ = try? (fileURL as NSURL).getResourceValue(&creationDatev, forKey: URLResourceKey.creationDateKey)
|
|
_ = try? (fileURL as NSURL).getResourceValue(&modifiedDatev, forKey: URLResourceKey.contentModificationDateKey)
|
|
_ = try? (fileURL as NSURL).getResourceValue(&filetypev, forKey: URLResourceKey.fileResourceTypeKey)
|
|
_ = try? (fileURL as NSURL).getResourceValue(&hiddenv, forKey: URLResourceKey.isHiddenKey)
|
|
_ = try? (fileURL as NSURL).getResourceValue(&readonlyv, forKey: URLResourceKey.volumeIsReadOnlyKey)
|
|
let path: String
|
|
if isPathRelative {
|
|
path = self.relativePathOf(url: fileURL)
|
|
} else {
|
|
path = fileURL.path
|
|
}
|
|
let filetype = URLFileResourceType(rawValue: filetypev as? String ?? "")
|
|
let fileAttr = LocalFileObject(absoluteURL: fileURL, name: namev as! String, path: path, size: sizev?.int64Value ?? -1, allocatedSize: allocated?.int64Value ?? -1, createdDate: creationDatev as? Date, modifiedDate: modifiedDatev as? Date, fileType: FileType(urlResourceTypeValue: filetype), isHidden: hiddenv?.boolValue ?? false, isReadOnly: readonlyv?.boolValue ?? false)
|
|
return fileAttr
|
|
}
|
|
|
|
open func storageProperties(completionHandler: (@escaping (_ total: Int64, _ used: Int64) -> Void)) {
|
|
let dict = (try? FileManager.default.attributesOfFileSystem(forPath: baseURL?.path ?? "/"))
|
|
let totalSize = (dict?[FileAttributeKey.systemSize] as? NSNumber)?.int64Value ?? -1;
|
|
let freeSize = (dict?[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0;
|
|
completionHandler(totalSize, totalSize - freeSize)
|
|
}
|
|
|
|
open func attributesOfItem(path: String, completionHandler: @escaping ((_ attributes: FileObject?, _ error: Error?) -> Void)) {
|
|
dispatch_queue.async {
|
|
completionHandler(self.attributesOfItem(url: self.absoluteURL(path)), nil)
|
|
}
|
|
}
|
|
|
|
open weak var fileOperationDelegate : FileOperationDelegate?
|
|
|
|
@objc(createWithFolder:at:completionHandler:) open func create(folder folderName: String, at atPath: String, completionHandler: SimpleCompletionHandler) {
|
|
operation_queue.async {
|
|
do {
|
|
try self.opFileManager.createDirectory(at: self.absoluteURL(atPath).uw_URLByAppendingPathComponent(folderName), withIntermediateDirectories: true, attributes: [:])
|
|
completionHandler?(nil)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderSucceed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(folderName) + "/"))
|
|
})
|
|
} catch let e as NSError {
|
|
completionHandler?(e)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderFailed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(folderName) + "/"))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
open func create(file fileAttribs: FileObject, at atPath: String, contents data: Data?, completionHandler: SimpleCompletionHandler) {
|
|
operation_queue.async {
|
|
let fileURL = self.absoluteURL(atPath).uw_URLByAppendingPathComponent(fileAttribs.name)
|
|
var attributes = [String : Any]()
|
|
if let createdDate = fileAttribs.createdDate {
|
|
attributes[FileAttributeKey.creationDate.rawValue] = createdDate as NSDate
|
|
}
|
|
if let modDate = fileAttribs.modifiedDate {
|
|
attributes[FileAttributeKey.modificationDate.rawValue] = modDate as NSDate
|
|
}
|
|
if fileAttribs.isReadOnly {
|
|
attributes[FileAttributeKey.posixPermissions.rawValue] = NSNumber(value: 365 as Int16)
|
|
}
|
|
let success = self.opFileManager.createFile(atPath: fileURL.path, contents: data, attributes: attributes)
|
|
if success {
|
|
do {
|
|
try (fileURL as NSURL).setResourceValue(fileAttribs.isHidden, forKey: URLResourceKey.isHiddenKey)
|
|
} catch _ {}
|
|
completionHandler?(nil)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderSucceed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(fileAttribs.name)))
|
|
})
|
|
} else {
|
|
completionHandler?(self.throwError(atPath, code: URLError.cannotCreateFile as FoundationErrorEnum))
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderFailed(self, operation: .create(path: (atPath as NSString).appendingPathComponent(fileAttribs.name)))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
open func moveItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
|
|
// FIXME: progress
|
|
operation_queue.async {
|
|
if !overwrite && self.fileManager.fileExists(atPath: self.absoluteURL(toPath).path) {
|
|
completionHandler?(self.throwError(toPath, code: URLError.cannotMoveFile as FoundationErrorEnum))
|
|
return
|
|
}
|
|
do {
|
|
try self.opFileManager.moveItem(at: self.absoluteURL(path), to: self.absoluteURL(toPath))
|
|
completionHandler?(nil)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderSucceed(self, operation: .move(source: path, destination: toPath))
|
|
})
|
|
} catch let e as NSError {
|
|
completionHandler?(e)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderFailed(self, operation: .move(source: path, destination: toPath))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
open func copyItem(path: String, to toPath: String, overwrite: Bool = false, completionHandler: SimpleCompletionHandler) {
|
|
// FIXME: progress, for files > 100mb, monitor file by another thread, for dirs check copied items count
|
|
operation_queue.async {
|
|
if !overwrite && self.fileManager.fileExists(atPath: self.absoluteURL(toPath).path) {
|
|
completionHandler?(self.throwError(toPath, code: URLError.cannotWriteToFile as FoundationErrorEnum))
|
|
return
|
|
}
|
|
do {
|
|
try self.opFileManager.copyItem(at: self.absoluteURL(path), to: self.absoluteURL(toPath))
|
|
completionHandler?(nil)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderSucceed(self, operation: .copy(source: path, destination: toPath))
|
|
})
|
|
} catch let e as NSError {
|
|
completionHandler?(e)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderFailed(self, operation: .copy(source: path, destination: toPath))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
open func removeItem(path: String, completionHandler: SimpleCompletionHandler) {
|
|
operation_queue.async {
|
|
do {
|
|
try self.opFileManager.removeItem(at: self.absoluteURL(path))
|
|
completionHandler?(nil)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderSucceed(self, operation: .remove(path: path))
|
|
})
|
|
} catch let e as NSError {
|
|
completionHandler?(e)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderFailed(self, operation: .remove(path: path))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
open func copyItem(localFile: URL, to toPath: String, completionHandler: SimpleCompletionHandler) {
|
|
operation_queue.async {
|
|
do {
|
|
try self.opFileManager.copyItem(at: localFile, to: self.absoluteURL(toPath))
|
|
completionHandler?(nil)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderSucceed(self, operation: .copy(source: localFile.uw_absoluteString, destination: toPath))
|
|
})
|
|
} catch let e as NSError {
|
|
completionHandler?(e)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderFailed(self, operation: .copy(source: localFile.uw_absoluteString, destination: toPath))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
open func copyItem(path: String, toLocalURL: URL, completionHandler: SimpleCompletionHandler) {
|
|
operation_queue.async {
|
|
do {
|
|
try self.opFileManager.copyItem(at: self.absoluteURL(path), to: toLocalURL)
|
|
completionHandler?(nil)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderSucceed(self, operation: .copy(source: path, destination: toLocalURL.uw_absoluteString))
|
|
})
|
|
} catch let e as NSError {
|
|
completionHandler?(e)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderFailed(self, operation: .copy(source: path, destination: toLocalURL.uw_absoluteString))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
open func contents(path: String, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
|
|
dispatch_queue.async {
|
|
let data = self.fileManager.contents(atPath: self.absoluteURL(path).path)
|
|
completionHandler(data, nil)
|
|
}
|
|
}
|
|
|
|
open func contents(path: String, offset: Int64, length: Int, completionHandler: @escaping ((_ contents: Data?, _ error: Error?) -> Void)) {
|
|
// Unfortunatlely there is no method provided in NSFileManager to read a segment of file.
|
|
// So we have to fallback to POSIX provided methods
|
|
dispatch_queue.async {
|
|
let aPath = self.absoluteURL(path).path
|
|
guard !self.attributesOfItem(url: self.absoluteURL(path)).isDirectory && self.fileManager.fileExists(atPath: aPath) else {
|
|
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
|
|
return
|
|
}
|
|
let fd_from = open(aPath, O_RDONLY)
|
|
if fd_from < 0 {
|
|
completionHandler(nil, self.throwError(path, code: URLError.cannotOpenFile as FoundationErrorEnum))
|
|
return
|
|
}
|
|
defer { precondition(close(fd_from) >= 0) }
|
|
lseek(fd_from, offset, SEEK_SET)
|
|
var buf = [UInt8](repeating: 0, count: length)
|
|
let nread = read(fd_from, &buf, buf.count)
|
|
if nread < 0 {
|
|
completionHandler(nil, self.throwError(path, code: URLError.noPermissionsToReadFile as FoundationErrorEnum))
|
|
} else if nread == 0 {
|
|
completionHandler(nil, nil)
|
|
} else {
|
|
let data = Data(bytesNoCopy: UnsafeMutablePointer<UInt8>(&buf), count: nread, deallocator: .free)
|
|
completionHandler(data, nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
open func writeContents(path: String, contents data: Data, atomically: Bool, completionHandler: SimpleCompletionHandler) {
|
|
operation_queue.async {
|
|
try? data.write(to: self.absoluteURL(path), options: atomically ? [.atomic] : [])
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderSucceed(self, operation: .modify(path: path))
|
|
})
|
|
}
|
|
}
|
|
|
|
open func searchFiles(path: String, recursive: Bool, query: String, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ((_ files: [FileObject], _ error: Error?) -> Void)) {
|
|
dispatch_queue.async {
|
|
let iterator = self.fileManager.enumerator(at: self.absoluteURL(path), includingPropertiesForKeys: nil, options: recursive ? FileManager.DirectoryEnumerationOptions() : .skipsSubdirectoryDescendants) { (url, e) -> Bool in
|
|
completionHandler([], e)
|
|
return true
|
|
}
|
|
var result = [LocalFileObject]()
|
|
while let fileURL = iterator?.nextObject() as? URL {
|
|
if fileURL.lastPathComponent.lowercased().contains(query.lowercased()) {
|
|
let fileObject = self.attributesOfItem(url: fileURL)
|
|
result.append(self.attributesOfItem(url: fileURL))
|
|
foundItemHandler?(fileObject)
|
|
}
|
|
}
|
|
completionHandler(result, nil)
|
|
}
|
|
}
|
|
|
|
fileprivate var monitors = [LocalFolderMonitor]()
|
|
|
|
open func registerNotifcation(path: String, eventHandler: @escaping (() -> Void)) {
|
|
self.unregisterNotifcation(path: path)
|
|
let absurl = self.absoluteURL(path)
|
|
var isdirv: AnyObject?
|
|
do {
|
|
try (absurl as NSURL).getResourceValue(&isdirv, forKey: URLResourceKey.isDirectoryKey)
|
|
} catch _ {
|
|
}
|
|
if !(isdirv?.boolValue ?? false) {
|
|
return
|
|
}
|
|
let monitor = LocalFolderMonitor(url: absurl) {
|
|
eventHandler()
|
|
}
|
|
monitor.start()
|
|
monitors.append(monitor)
|
|
}
|
|
|
|
open func unregisterNotifcation(path: String) {
|
|
var removedMonitor: LocalFolderMonitor?
|
|
for (i, monitor) in monitors.enumerated() {
|
|
if self.relativePathOf(url: monitor.url) == path {
|
|
removedMonitor = monitors.remove(at: i)
|
|
break
|
|
}
|
|
}
|
|
removedMonitor?.stop()
|
|
}
|
|
|
|
open func isRegisteredForNotification(path: String) -> Bool {
|
|
return monitors.map( { self.relativePathOf(url: $0.url) } ).contains(path)
|
|
}
|
|
}
|
|
|
|
public extension LocalFileProvider {
|
|
public func create(symbolicLink path: String, withDestinationPath destPath: String, completionHandler: SimpleCompletionHandler) {
|
|
operation_queue.async {
|
|
do {
|
|
try self.opFileManager.createSymbolicLink(at: self.absoluteURL(path), withDestinationURL: self.absoluteURL(destPath))
|
|
completionHandler?(nil)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderSucceed(self, operation: .link(link: path, target: destPath))
|
|
})
|
|
} catch let e as NSError {
|
|
completionHandler?(e)
|
|
DispatchQueue.main.async(execute: {
|
|
self.delegate?.fileproviderFailed(self, operation: .link(link: path, target: destPath))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class LocalFileProviderManagerDelegate: NSObject, FileManagerDelegate {
|
|
weak var provider: LocalFileProvider?
|
|
|
|
init(provider: LocalFileProvider) {
|
|
self.provider = provider
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldCopyItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
|
guard let provider = self.provider, let 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: FileManager, shouldMoveItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
|
guard let provider = self.provider, let 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: FileManager, shouldRemoveItemAt URL: URL) -> Bool {
|
|
guard let provider = self.provider, let delegate = provider.fileOperationDelegate else {
|
|
return true
|
|
}
|
|
let path = provider.relativePathOf(url: URL)
|
|
return delegate.fileProvider(provider, shouldDoOperation: .remove(path: path))
|
|
}
|
|
|
|
func fileManager(_ fileManager: FileManager, shouldLinkItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
|
guard let provider = self.provider, let 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: FileManager, shouldProceedAfterError error: Error, copyingItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
|
guard let provider = self.provider, let 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: FileManager, shouldProceedAfterError error: Error, movingItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
|
guard let provider = self.provider, let 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: FileManager, shouldProceedAfterError error: Error, removingItemAt URL: URL) -> Bool {
|
|
guard let provider = self.provider, let 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: FileManager, shouldProceedAfterError error: Error, linkingItemAt srcURL: URL, to dstURL: URL) -> Bool {
|
|
guard let provider = self.provider, let 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 {
|
|
fileprivate let source: DispatchSourceFileSystemObject
|
|
fileprivate let descriptor: CInt
|
|
fileprivate let qq: DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
|
|
fileprivate var state: Bool = false
|
|
fileprivate var monitoredTime: TimeInterval = Date().timeIntervalSinceReferenceDate
|
|
var url: URL
|
|
|
|
/// Creates a folder monitor object with monitoring enabled.
|
|
init(url: URL, handler: @escaping ()->Void) {
|
|
self.url = url
|
|
descriptor = open((url as NSURL).fileSystemRepresentation, O_EVTONLY)
|
|
source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: DispatchSource.FileSystemEvent.write, queue: 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 = {
|
|
if Date().timeIntervalSinceReferenceDate < self.monitoredTime + 0.2 {
|
|
return
|
|
}
|
|
self.monitoredTime = Date().timeIntervalSinceReferenceDate
|
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.25, execute: {
|
|
handler()
|
|
})
|
|
}
|
|
source.setEventHandler(handler: main_handler)
|
|
source.setCancelHandler {
|
|
close(self.descriptor)
|
|
}
|
|
start()
|
|
}
|
|
|
|
/// Starts sending notifications if currently stopped
|
|
func start() {
|
|
if !state {
|
|
state = true
|
|
source.resume()
|
|
}
|
|
}
|
|
|
|
/// Stops sending notifications if currently enabled
|
|
func stop() {
|
|
if state {
|
|
state = false
|
|
source.suspend()
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
source.cancel()
|
|
}
|
|
}
|