Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link
Owner Author

commented Mar 21, 2018

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

This comment has been minimized.

Copy link

commented Oct 4, 2018

Thank you for this gist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.