Last active
March 10, 2023 15:50
-
-
Save DivineDominion/56e56f3db43216d9eaab300d3b9f049a to your computer and use it in GitHub Desktop.
Wrapper for using C FSEvents with Swift 4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Based on: https://blog.beecomedigital.com/2015/06/27/developing-a-filesystemwatcher-for-os-x-by-using-fsevents-with-swift-2/ | |
// | |
import Foundation | |
public struct Event: CustomStringConvertible { | |
public let eventId: FSEventStreamEventId | |
public let eventPath: String | |
public let eventFlags: FSEventStreamEventFlags | |
public var description: String { | |
return "\(eventId) - \(eventFlags) - \(eventPath)" | |
} | |
} | |
public class FolderContentMonitor { | |
let callback: (Event) -> Void | |
public init(pathsToWatch: [String], sinceWhen: FSEventStreamEventId = FSEventStreamEventId(kFSEventStreamEventIdSinceNow), callback: @escaping (Event) -> Void) { | |
self.lastEventId = sinceWhen | |
self.pathsToWatch = pathsToWatch | |
self.callback = callback | |
} | |
deinit { | |
stop() | |
} | |
// MARK: - Private Properties | |
private let eventCallback: FSEventStreamCallback = { | |
(stream: ConstFSEventStreamRef, | |
contextInfo: UnsafeMutableRawPointer?, | |
numEvents: Int, | |
eventPaths: UnsafeMutableRawPointer, | |
eventFlags: UnsafePointer<FSEventStreamEventFlags>, | |
eventIds: UnsafePointer<FSEventStreamEventId>) in | |
let fileSystemWatcher: FolderContentMonitor = unsafeBitCast(contextInfo, to: FolderContentMonitor.self) | |
guard let paths = unsafeBitCast(eventPaths, to: NSArray.self) as? [String] else { return } | |
for index in 0..<numEvents { | |
fileSystemWatcher.processEvent(eventId: eventIds[index], eventPath: paths[index], eventFlags: eventFlags[index]) | |
} | |
fileSystemWatcher.lastEventId = eventIds[numEvents - 1] | |
} | |
private let pathsToWatch: [String] | |
private var started = false | |
private var streamRef: FSEventStreamRef! | |
private func processEvent(eventId: FSEventStreamEventId, eventPath: String, eventFlags: FSEventStreamEventFlags) { | |
let event = Event(eventId: eventId, eventPath: eventPath, eventFlags: eventFlags) | |
callback(event) | |
} | |
public private(set) var lastEventId: FSEventStreamEventId | |
public func start() { | |
guard started == false else { return } | |
var context = FSEventStreamContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) | |
context.info = Unmanaged.passUnretained(self).toOpaque() | |
let flags = UInt32(kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents) | |
streamRef = FSEventStreamCreate(kCFAllocatorDefault, eventCallback, &context, pathsToWatch as CFArray, lastEventId, 0, flags) | |
FSEventStreamScheduleWithRunLoop(streamRef, CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue) | |
FSEventStreamStart(streamRef) | |
started = true | |
} | |
public func stop() { | |
guard started == true else { return } | |
FSEventStreamStop(streamRef) | |
FSEventStreamInvalidate(streamRef) | |
FSEventStreamRelease(streamRef) | |
streamRef = nil | |
started = false | |
} | |
} |
Thank you for this gist
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I wrote an example app to show how to use this; it's RxSwift-based, though. The Rx adapter stuff is pretty thin, and you can rip out the core Monitor object and the event enum: https://github.com/RxSwiftCommunity/RxFileMonitor