Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save adam-zethraeus/1c34a8319c94578909905ddcbe2ae73d to your computer and use it in GitHub Desktop.

Select an option

Save adam-zethraeus/1c34a8319c94578909905ddcbe2ae73d to your computer and use it in GitHub Desktop.
DirectoryChangesAsyncSequence
import Foundation
import Dispatch
import CoreServices
import Foundation
import os
/// An AsyncSequence which emits when a file or folder is changed.
public final class DirectoryChanges: AsyncSequence {
public enum Failure: Error {
case InvalidFileURL
case DirectoryMissing
case NotDirectory
case FailedToOpen
}
public func makeAsyncIterator() -> AsyncThrowingStream<Element, any Error>.Iterator {
builder().makeAsyncIterator()
}
public typealias AsyncIterator = AsyncThrowingStream<Element, any Error>.Iterator
public typealias Element = ()
private let builder: () -> AsyncThrowingStream<Element, any Error>
public init(_ url: URL) {
builder = {
let (stream, cont) = AsyncThrowingStream<Element, any Error>.makeStream()
let fileURL = url.standardizedFileURL
var objCBool: ObjCBool = false
guard url.isFileURL else {
cont.finish(throwing: Failure.InvalidFileURL)
return stream
}
guard FileManager.default.fileExists(atPath: fileURL.path(), isDirectory: &objCBool) else {
cont.finish(throwing: Failure.DirectoryMissing)
return stream
}
guard objCBool.boolValue == true else {
cont.finish(throwing: Failure.NotDirectory)
return stream
}
let it = DirectoryWatcher()
cont.onTermination = { _ in
it.stop()
}
let didWatch = it.watch(path: url.path) {
cont.yield()
}
guard didWatch else {
cont.finish(throwing: Failure.FailedToOpen)
return stream
}
return stream
}
}
}
import Foundation
public final class DirectoryWatcher {
public init(){}
deinit {
stop()
}
public typealias Callback = () -> Void
private var dirFD : Int32 = -1 {
didSet {
if oldValue != -1 {
close(oldValue)
}
}
}
private var dispatchSource : DispatchSourceFileSystemObject?
public func watch(path: String, callback: @escaping Callback) -> Bool {
dirFD = open(path, O_EVTONLY)
if dirFD < 0 {
return false
}
let dispatchSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: dirFD, eventMask: .write, queue: DispatchQueue.main)
dispatchSource.setEventHandler {
callback()
}
dispatchSource.setCancelHandler { [weak self] in
self?.dirFD = -1
}
self.dispatchSource = dispatchSource
dispatchSource.resume()
return true
}
public func stop() {
guard let dispatchSource = dispatchSource else {
return
}
dispatchSource.setEventHandler(handler: nil)
dispatchSource.cancel()
self.dispatchSource = nil
}
}
for try await _ in DirectoryChanges(URL(filePath: "/Users/adamz/Developer/ambience/mobile-ios")) {
print("change")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment