Implement watching a path
Each inotify instance produces events for paths in its watch list. Each item in the watch list is identified by its watch descriptor. Different paths can be watched for different events.
This commit is contained in:
@@ -2,6 +2,7 @@ import CInotify
|
|||||||
|
|
||||||
public actor Inotify {
|
public actor Inotify {
|
||||||
private let fd: Int32
|
private let fd: Int32
|
||||||
|
private var watches: [Int32: String] = [:]
|
||||||
|
|
||||||
public init() throws {
|
public init() throws {
|
||||||
self.fd = inotify_init1(Int32(IN_NONBLOCK | IN_CLOEXEC))
|
self.fd = inotify_init1(Int32(IN_NONBLOCK | IN_CLOEXEC))
|
||||||
@@ -10,6 +11,16 @@ public actor Inotify {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public func addWatch(path: String, mask: InotifyEventMask) throws -> Int32 {
|
||||||
|
let wd = inotify_add_watch(self.fd, path, mask.rawValue)
|
||||||
|
guard wd >= 0 else {
|
||||||
|
throw InotifyError.addWatchFailed(path: path, errno: cinotify_get_errno())
|
||||||
|
}
|
||||||
|
watches[wd] = path
|
||||||
|
return wd
|
||||||
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
cinotify_deinit(self.fd)
|
cinotify_deinit(self.fd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ import CInotify
|
|||||||
|
|
||||||
public enum InotifyError: Error, Sendable, CustomStringConvertible {
|
public enum InotifyError: Error, Sendable, CustomStringConvertible {
|
||||||
case initFailed(errno: Int32)
|
case initFailed(errno: Int32)
|
||||||
|
case addWatchFailed(path: String, errno: Int32)
|
||||||
|
|
||||||
public var description: String {
|
public var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .initFailed(let code):
|
case .initFailed(let code):
|
||||||
"inotify_init1 failed: \(readableErrno(code))"
|
"inotify_init1 failed: \(readableErrno(code))"
|
||||||
|
case .addWatchFailed(let path, let code):
|
||||||
|
"inotify_add_watch failed for '\(path)': \(readableErrno(code))"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
47
Sources/Inotify/InotifyEventMask.swift
Normal file
47
Sources/Inotify/InotifyEventMask.swift
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import CInotify
|
||||||
|
|
||||||
|
public struct InotifyEventMask: OptionSet, Sendable, Hashable {
|
||||||
|
public let rawValue: UInt32
|
||||||
|
|
||||||
|
public init(rawValue: UInt32) {
|
||||||
|
self.rawValue = rawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Watchable Events
|
||||||
|
|
||||||
|
public static let access = InotifyEventMask(rawValue: UInt32(IN_ACCESS))
|
||||||
|
public static let attrib = InotifyEventMask(rawValue: UInt32(IN_ATTRIB))
|
||||||
|
public static let closeWrite = InotifyEventMask(rawValue: UInt32(IN_CLOSE_WRITE))
|
||||||
|
public static let closeNoWrite = InotifyEventMask(rawValue: UInt32(IN_CLOSE_NOWRITE))
|
||||||
|
public static let create = InotifyEventMask(rawValue: UInt32(IN_CREATE))
|
||||||
|
public static let delete = InotifyEventMask(rawValue: UInt32(IN_DELETE))
|
||||||
|
public static let deleteSelf = InotifyEventMask(rawValue: UInt32(IN_DELETE_SELF))
|
||||||
|
public static let modify = InotifyEventMask(rawValue: UInt32(IN_MODIFY))
|
||||||
|
public static let moveSelf = InotifyEventMask(rawValue: UInt32(IN_MOVE_SELF))
|
||||||
|
public static let movedFrom = InotifyEventMask(rawValue: UInt32(IN_MOVED_FROM))
|
||||||
|
public static let movedTo = InotifyEventMask(rawValue: UInt32(IN_MOVED_TO))
|
||||||
|
public static let open = InotifyEventMask(rawValue: UInt32(IN_OPEN))
|
||||||
|
|
||||||
|
// MARK: - Combinations
|
||||||
|
|
||||||
|
public static let move: InotifyEventMask = [.movedFrom, .movedTo]
|
||||||
|
public static let close: InotifyEventMask = [.closeWrite, .closeNoWrite]
|
||||||
|
public static let allEvents: InotifyEventMask = [
|
||||||
|
.access, .attrib, .closeWrite, .closeNoWrite,
|
||||||
|
.create, .delete, .deleteSelf, .modify,
|
||||||
|
.moveSelf, .movedFrom, .movedTo, .open
|
||||||
|
]
|
||||||
|
|
||||||
|
// MARK: - Watch Flags
|
||||||
|
|
||||||
|
public static let dontFollow = InotifyEventMask(rawValue: UInt32(IN_DONT_FOLLOW))
|
||||||
|
public static let onlyDir = InotifyEventMask(rawValue: UInt32(IN_ONLYDIR))
|
||||||
|
public static let oneShot = InotifyEventMask(rawValue: UInt32(IN_ONESHOT))
|
||||||
|
|
||||||
|
// MARK: - Kernel-Only Flags
|
||||||
|
|
||||||
|
public static let isDir = InotifyEventMask(rawValue: UInt32(IN_ISDIR))
|
||||||
|
public static let ignored = InotifyEventMask(rawValue: UInt32(IN_IGNORED))
|
||||||
|
public static let queueOverflow = InotifyEventMask(rawValue: UInt32(IN_Q_OVERFLOW))
|
||||||
|
public static let unmount = InotifyEventMask(rawValue: UInt32(IN_UNMOUNT))
|
||||||
|
}
|
||||||
10
Tests/InotifyIntegrationTests/Utilities.swift
Normal file
10
Tests/InotifyIntegrationTests/Utilities.swift
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
func withTempDir(_ body: (String) async throws -> Void) async throws {
|
||||||
|
let dir = FileManager.default.temporaryDirectory
|
||||||
|
.appendingPathComponent("InotifyIntegrationTests-\(UUID().uuidString)")
|
||||||
|
.path
|
||||||
|
try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true)
|
||||||
|
defer { try? FileManager.default.removeItem(atPath: dir) }
|
||||||
|
try await body(dir)
|
||||||
|
}
|
||||||
21
Tests/InotifyIntegrationTests/WatchTests.swift
Normal file
21
Tests/InotifyIntegrationTests/WatchTests.swift
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import Testing
|
||||||
|
import Foundation
|
||||||
|
@testable import Inotify
|
||||||
|
|
||||||
|
@Suite("Watch Management")
|
||||||
|
struct WatchTests {
|
||||||
|
@Test func addWatchReturnsValidDescriptor() async throws {
|
||||||
|
try await withTempDir { dir in
|
||||||
|
let watcher = try Inotify()
|
||||||
|
let wd = try await watcher.addWatch(path: dir, mask: .allEvents)
|
||||||
|
#expect(wd >= 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func addWatchOnInvalidPathThrows() async throws {
|
||||||
|
let watcher = try Inotify()
|
||||||
|
await #expect(throws: InotifyError.self) {
|
||||||
|
try await watcher.addWatch(path: "/nonexistent-\(UUID())", mask: .allEvents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user