Skip to content

Instantly share code, notes, and snippets.

@DivineDominion
Last active March 10, 2023 15:50
Show Gist options
  • Save DivineDominion/56e56f3db43216d9eaab300d3b9f049a to your computer and use it in GitHub Desktop.
Save DivineDominion/56e56f3db43216d9eaab300d3b9f049a to your computer and use it in GitHub Desktop.
Wrapper for using C FSEvents with Swift 4
//
// 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
}
}
@DivineDominion
Copy link
Author

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

@CanYumusak
Copy link

Thank you for this gist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment