From a30a212373693bff3f863e7b91bb90860a1bc918 Mon Sep 17 00:00:00 2001 From: Christian Tietze Date: Tue, 13 Dec 2016 17:38:03 +0100 Subject: [PATCH] change FolderContentMonitor observable initialization See discussion here for details: https://github.com/RxSwiftCommunity/contributors/issues/16 --- Example/AppDelegate.swift | 8 +++- README.md | 11 ++++-- RxFileMonitor.xcodeproj/project.pbxproj | 8 ++-- RxFileMonitor/FileMonitor.swift | 2 +- RxFileMonitor/FolderContentMonitor.swift | 47 +++++++++++++++++++----- RxFileMonitor/Monitoring.swift | 29 --------------- RxFileMonitor/RxFileMonitor.swift | 35 ++++++++++++++++++ 7 files changed, 92 insertions(+), 48 deletions(-) delete mode 100644 RxFileMonitor/Monitoring.swift create mode 100644 RxFileMonitor/RxFileMonitor.swift diff --git a/Example/AppDelegate.swift b/Example/AppDelegate.swift index 8203715..abc9a08 100644 --- a/Example/AppDelegate.swift +++ b/Example/AppDelegate.swift @@ -32,7 +32,13 @@ class AppDelegate: NSObject, NSApplicationDelegate { report(url) - Monitoring.folderMonitor(url: url) + FolderContentMonitor(url: url) + .asObservable() + + // Ignore Finder folder settings + .filter { $0.filename != ".DS_Store" } + + // Report changes into app's main log .subscribeOn(MainScheduler.asyncInstance) .subscribe(onNext: { event in self.report("\(event.filename) changed (\(event.change))") diff --git a/README.md b/README.md index fbef35f..f6fcaf4 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ import RxFileMonitor let disposeBag = DisposeBag() let folderUrl = URL(fileURLWithPath: "/path/to/monitor/") -Monitoring.folderMonitor(url: folderUrl) +FolderContentMonitor(url: folderUrl) + .asObservable() .subscribe(onNext: { event in print("Folder contents changed at \(event.url) (\(event.change))") }) @@ -37,10 +38,11 @@ Monitoring.folderMonitor(url: folderUrl) Say you want to update a cache of a folder's notes' contents, you'll be interested in files only: ```swift -let changedFile = Monitoring.folderMonitor(url: folderUrl) +let changedFile = FolderContentMonitor(url: folderUrl) + .asObservable() // Files only ... .filter { $0.change.contains(.isFile) } - // ... except the Spotlight cache. + // ... except the user's folder settings. .filter { $0.filename != ".DS_Store" } .map { $0.filename } .observeOn(MainScheduler.instance) @@ -55,7 +57,8 @@ changedFile.subscribe(onNext: cache.updateFile) Or if you simply rebuild the whole cache when anything changed, you can stop after filtering for accepted events: ```swift -let changedFile = Monitoring.folderMonitor(url: folderUrl) +let changedFile = FolderContentMonitor(url: folderUrl) + .asObservable() .filter { $0.change.contains(.isFile) } .filter { $0.filename != ".DS_Store" } .observeOn(MainScheduler.instance) diff --git a/RxFileMonitor.xcodeproj/project.pbxproj b/RxFileMonitor.xcodeproj/project.pbxproj index d55c8dd..c32bedd 100644 --- a/RxFileMonitor.xcodeproj/project.pbxproj +++ b/RxFileMonitor.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ 5096B2B81DD2216B00076058 /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5096B2B61DD2216B00076058 /* RxSwift.framework */; }; 5096B2B91DD2217900076058 /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5096B2B61DD2216B00076058 /* RxSwift.framework */; }; 5096B2BA1DD2217900076058 /* RxSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5096B2B61DD2216B00076058 /* RxSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 5096B2BC1DD221D700076058 /* Monitoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5096B2BB1DD221D700076058 /* Monitoring.swift */; }; + 5096B2BC1DD221D700076058 /* RxFileMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5096B2BB1DD221D700076058 /* RxFileMonitor.swift */; }; 5096B2C11DD22D6000076058 /* FolderContentChangeEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5096B2C01DD22D6000076058 /* FolderContentChangeEvent.swift */; }; 50E8354D1DD1B26900783B62 /* RxFileMonitor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50E835431DD1B26900783B62 /* RxFileMonitor.framework */; }; 50E835521DD1B26900783B62 /* RxFileMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E835511DD1B26900783B62 /* RxFileMonitorTests.swift */; }; @@ -61,7 +61,7 @@ /* Begin PBXFileReference section */ 5096B2B41DD21D0B00076058 /* Change.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Change.swift; sourceTree = ""; }; 5096B2B61DD2216B00076058 /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = Carthage/Build/Mac/RxSwift.framework; sourceTree = ""; }; - 5096B2BB1DD221D700076058 /* Monitoring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Monitoring.swift; sourceTree = ""; }; + 5096B2BB1DD221D700076058 /* RxFileMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxFileMonitor.swift; sourceTree = ""; }; 5096B2C01DD22D6000076058 /* FolderContentChangeEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FolderContentChangeEvent.swift; sourceTree = ""; }; 5096B2C21DD22FC200076058 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 5096B2C41DD2333E00076058 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -148,7 +148,7 @@ isa = PBXGroup; children = ( 50E835461DD1B26900783B62 /* RxFileMonitor.h */, - 5096B2BB1DD221D700076058 /* Monitoring.swift */, + 5096B2BB1DD221D700076058 /* RxFileMonitor.swift */, 5096B2BD1DD222D000076058 /* Monitors */, 50E835471DD1B26900783B62 /* Info.plist */, ); @@ -325,7 +325,7 @@ files = ( 50E835761DD1D1C200783B62 /* FolderContentMonitor.swift in Sources */, 5096B2C11DD22D6000076058 /* FolderContentChangeEvent.swift in Sources */, - 5096B2BC1DD221D700076058 /* Monitoring.swift in Sources */, + 5096B2BC1DD221D700076058 /* RxFileMonitor.swift in Sources */, 50E8356F1DD1BAEE00783B62 /* FileMonitor.swift in Sources */, 5096B2B51DD21D0B00076058 /* Change.swift in Sources */, ); diff --git a/RxFileMonitor/FileMonitor.swift b/RxFileMonitor/FileMonitor.swift index 0211fb2..05aeddf 100644 --- a/RxFileMonitor/FileMonitor.swift +++ b/RxFileMonitor/FileMonitor.swift @@ -22,7 +22,7 @@ public class FileMonitor { public let url: URL } - public static let monitorQueue = DispatchQueue(label: "com.cleancocoa.rxfilemonitor.monitorqueue", qos: .background, attributes: [.concurrent]) + public static let monitorQueue = DispatchQueue(label: "org.rxswift.rxfilemonitor.monitorqueue", qos: .background, attributes: [.concurrent]) var monitoredFileDescriptor: Int32? var monitorSource: DispatchSourceFileSystemObject? diff --git a/RxFileMonitor/FolderContentMonitor.swift b/RxFileMonitor/FolderContentMonitor.swift index 3becbe0..2b88334 100644 --- a/RxFileMonitor/FolderContentMonitor.swift +++ b/RxFileMonitor/FolderContentMonitor.swift @@ -10,7 +10,7 @@ import Foundation public class FolderContentMonitor { - let callback: (FolderContentChangeEvent) -> Void + var callback: ((FolderContentChangeEvent) -> Void)? public let pathsToWatch: [String] public private(set) var hasStarted = false @@ -18,7 +18,28 @@ public class FolderContentMonitor { public private(set) var lastEventId: FSEventStreamEventId - public init(pathsToWatch: [String], sinceWhen: FSEventStreamEventId = FSEventStreamEventId(kFSEventStreamEventIdSinceNow), callback: @escaping (FolderContentChangeEvent) -> Void) { + /// - parameter url: Folder to monitor. + /// - parameter sinceWhen: Reference event for the subscription. Default + /// is `kFSEventStreamEventIdSinceNow`. + /// - parameter callback: Callback for incoming file system events. Can be ignored + /// when you use the monitor `asObservable` + public convenience init( + url: URL, + sinceWhen: FSEventStreamEventId = FSEventStreamEventId(kFSEventStreamEventIdSinceNow), + callback: ((FolderContentChangeEvent) -> Void)? = nil) { + + self.init(pathsToWatch: [url.path], sinceWhen: sinceWhen, callback: callback) + } + + /// - parameter pathsToWatch: Collection of file or folder paths. + /// - parameter sinceWhen: Reference event for the subscription. Default + /// is `kFSEventStreamEventIdSinceNow`. + /// - parameter callback: Callback for incoming file system events. Can be ignored + /// when you use the monitor `asObservable` + public init( + pathsToWatch: [String], + sinceWhen: FSEventStreamEventId = FSEventStreamEventId(kFSEventStreamEventIdSinceNow), + callback: ((FolderContentChangeEvent) -> Void)? = nil) { self.lastEventId = sinceWhen self.pathsToWatch = pathsToWatch @@ -31,7 +52,7 @@ public class FolderContentMonitor { public func start() { - guard hasStarted == false else { assertionFailure("Start must not be called twice. (Ignoring)"); return } + guard !hasStarted else { assertionFailure("Start must not be called twice. (Ignoring)"); return } var context = FSEventStreamContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) context.info = Unmanaged.passUnretained(self).toOpaque() @@ -44,26 +65,34 @@ public class FolderContentMonitor { hasStarted = true } - private let eventCallback: FSEventStreamCallback = { (stream: ConstFSEventStreamRef, contextInfo: UnsafeMutableRawPointer?, numEvents: Int, eventPaths: UnsafeMutableRawPointer, eventFlags: UnsafePointer?, eventIds: UnsafePointer?) in + private let eventCallback: FSEventStreamCallback = { + (stream: ConstFSEventStreamRef, + contextInfo: UnsafeMutableRawPointer?, + numEvents: Int, + eventPaths: UnsafeMutableRawPointer, + eventFlags: UnsafePointer?, + eventIds: UnsafePointer?) in + + let fileSystemWatcher: FolderContentMonitor = unsafeBitCast(contextInfo, to: FolderContentMonitor.self) - guard let eventIds = eventIds, + guard let callback = fileSystemWatcher.callback, + let eventIds = eventIds, let eventFlags = eventFlags, let paths = unsafeBitCast(eventPaths, to: NSArray.self) as? [String] else { return } - let fileSystemWatcher: FolderContentMonitor = unsafeBitCast(contextInfo, to: FolderContentMonitor.self) - (0.. FolderContentChangeEvent in let change = Change(eventFlags: eventFlags[index]) return FolderContentChangeEvent(eventId: eventIds[index], eventPath: paths[index], change: change) - }.forEach(fileSystemWatcher.callback) + }.forEach(callback) fileSystemWatcher.lastEventId = eventIds[numEvents - 1] } public func stop() { - guard hasStarted == true else { return } + + guard hasStarted else { return } FSEventStreamStop(streamRef) FSEventStreamInvalidate(streamRef) diff --git a/RxFileMonitor/Monitoring.swift b/RxFileMonitor/Monitoring.swift deleted file mode 100644 index 857c3ce..0000000 --- a/RxFileMonitor/Monitoring.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// Monitoring.swift -// RxFileMonitor -// -// Created by Christian Tietze on 08/11/16. -// Copyright © 2016 RxSwiftCommunity https://github.com/RxSwiftCommunity -// - -import Foundation -import RxSwift - -public enum Monitoring { - - public static func folderMonitor(url: URL) -> Observable { - - return Observable.create { observer in - - let monitor = FolderContentMonitor(pathsToWatch: [url.path]) { event in - observer.on(.next(event)) - } - - monitor.start() - - return Disposables.create { - monitor.stop() - } - } - } -} diff --git a/RxFileMonitor/RxFileMonitor.swift b/RxFileMonitor/RxFileMonitor.swift new file mode 100644 index 0000000..efbf1f5 --- /dev/null +++ b/RxFileMonitor/RxFileMonitor.swift @@ -0,0 +1,35 @@ +// +// Monitoring.swift +// RxFileMonitor +// +// Created by Christian Tietze on 08/11/16. +// Copyright © 2016 RxSwiftCommunity https://github.com/RxSwiftCommunity +// + +import Foundation +import RxSwift + +extension FolderContentMonitor: ObservableConvertibleType { + + public func asObservable() -> Observable { + + return Observable.create { observer in + + // Wrap existing callback + let oldCallback = self.callback + + self.callback = { event in + oldCallback?(event) + observer.on(.next(event)) + } + + if !self.hasStarted { + self.start() + } + + return Disposables.create { + self.stop() + } + } + } +}