sync app filled in
This commit is contained in:
103
apps/macos-sync/Sources/CairnquireSync/Sync/FolderWatcher.swift
Normal file
103
apps/macos-sync/Sources/CairnquireSync/Sync/FolderWatcher.swift
Normal file
@@ -0,0 +1,103 @@
|
||||
import Foundation
|
||||
import CoreServices
|
||||
|
||||
public protocol FolderWatcherDelegate: AnyObject {
|
||||
func folderWatcher(_ watcher: FolderWatcher, didDetectChanges paths: [String])
|
||||
}
|
||||
|
||||
public class FolderWatcher {
|
||||
public weak var delegate: FolderWatcherDelegate?
|
||||
|
||||
private var stream: FSEventStreamRef?
|
||||
private let folderPath: String
|
||||
private var debounceTimer: Timer?
|
||||
private var pendingPaths: Set<String> = []
|
||||
private let queue = DispatchQueue(label: "com.cairnquire.folderwatcher")
|
||||
|
||||
public init(folderPath: String) {
|
||||
self.folderPath = folderPath
|
||||
}
|
||||
|
||||
public func start() {
|
||||
stop()
|
||||
|
||||
let pathsToWatch = [folderPath as CFString]
|
||||
var context = FSEventStreamContext(
|
||||
version: 0,
|
||||
info: Unmanaged.passUnretained(self).toOpaque(),
|
||||
retain: nil,
|
||||
release: nil,
|
||||
copyDescription: nil
|
||||
)
|
||||
|
||||
let callback: FSEventStreamCallback = { (streamRef, clientCallBackInfo, numEvents, eventPaths, eventFlags, eventIds) in
|
||||
let watcher = Unmanaged<FolderWatcher>.fromOpaque(clientCallBackInfo!).takeUnretainedValue()
|
||||
watcher.handleEvents(numEvents: numEvents, eventPaths: eventPaths, eventFlags: eventFlags)
|
||||
}
|
||||
|
||||
stream = FSEventStreamCreate(
|
||||
kCFAllocatorDefault,
|
||||
callback,
|
||||
&context,
|
||||
pathsToWatch as CFArray,
|
||||
FSEventStreamEventId(kFSEventStreamEventIdSinceNow),
|
||||
0.5, // latency in seconds
|
||||
FSEventStreamCreateFlags(kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagUseCFTypes)
|
||||
)
|
||||
|
||||
if let stream = stream {
|
||||
FSEventStreamSetDispatchQueue(stream, queue)
|
||||
FSEventStreamStart(stream)
|
||||
}
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
if let stream = stream {
|
||||
FSEventStreamStop(stream)
|
||||
FSEventStreamInvalidate(stream)
|
||||
FSEventStreamRelease(stream)
|
||||
self.stream = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func handleEvents(numEvents: Int, eventPaths: UnsafeMutableRawPointer, eventFlags: UnsafePointer<FSEventStreamEventFlags>) {
|
||||
guard let paths = Unmanaged<CFArray>.fromOpaque(eventPaths).takeUnretainedValue() as? [String] else { return }
|
||||
|
||||
var changedPaths: [String] = []
|
||||
for i in 0..<numEvents {
|
||||
let path = paths[i]
|
||||
let flags = eventFlags[i]
|
||||
|
||||
// Filter for relevant file events
|
||||
if flags & UInt32(kFSEventStreamEventFlagItemCreated) != 0 ||
|
||||
flags & UInt32(kFSEventStreamEventFlagItemModified) != 0 ||
|
||||
flags & UInt32(kFSEventStreamEventFlagItemRemoved) != 0 ||
|
||||
flags & UInt32(kFSEventStreamEventFlagItemRenamed) != 0 {
|
||||
|
||||
// Only track markdown files
|
||||
if path.hasSuffix(".md") {
|
||||
changedPaths.append(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard !changedPaths.isEmpty else { return }
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.pendingPaths.formUnion(changedPaths)
|
||||
self.debounceTimer?.invalidate()
|
||||
self.debounceTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
let paths = Array(self.pendingPaths)
|
||||
self.pendingPaths.removeAll()
|
||||
self.delegate?.folderWatcher(self, didDetectChanges: paths)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user