Implement recursive watching of a directory

This commit is contained in:
T. R. Bernstein
2026-03-12 01:00:39 +01:00
parent d57f998fd4
commit b41b82bd0f
7 changed files with 122 additions and 3 deletions

View File

@@ -0,0 +1,44 @@
import _NIOFileSystem
public struct DirectoryResolver {
static let fileManager = FileSystem.shared
public static func resolve(_ paths: String...) async throws -> [FilePath] {
try await Self.resolve(paths)
}
static func resolve(_ paths: [String]) async throws -> [FilePath] {
var resolved: [FilePath] = []
for path in paths {
let itemPath = FilePath(path)
try await Self.ensure(itemPath, is: .directory)
let allDirectoriesIncludingSelf = try await getAllSubdirectoriesAndSelf(at: itemPath)
resolved.append(contentsOf: allDirectoriesIncludingSelf)
}
return resolved
}
private static func ensure(_ path: FilePath, is fileType: FileType) async throws {
guard let fileInfo = try await fileManager.info(forFileAt: path) else {
throw DirectoryResolverError.pathNotFound(path)
}
guard fileInfo.type == fileType else {
throw DirectoryResolverError.pathIsNoDirectory(path)
}
}
private static func getAllSubdirectoriesAndSelf(at path: FilePath) async throws -> [FilePath] {
var result: [FilePath] = []
let directoryHandle = try await fileManager.openDirectory(atPath: path)
for try await childContent in directoryHandle.listContents(recursive: true) {
guard childContent.type == .directory else { continue }
result.append(childContent.path)
}
try await directoryHandle.close()
return result
}
}

View File

@@ -0,0 +1,16 @@
import Foundation
import SystemPackage
public enum DirectoryResolverError: LocalizedError, Equatable {
case pathNotFound(FilePath)
case pathIsNoDirectory(FilePath)
var errorDescription: String {
switch self {
case .pathNotFound(let path):
return "Path not found: \(path)"
case .pathIsNoDirectory(let path):
return "Path is not a directory: \(path)"
}
}
}

View File

@@ -28,6 +28,17 @@ public actor Inotify {
return wd
}
@discardableResult
public func addRecursiveWatch(forDirectory path: String, mask: InotifyEventMask) async throws -> [CInt] {
let directoryPaths = try await DirectoryResolver.resolve(path)
var result: [CInt] = []
for path in directoryPaths {
let wd = try self.addWatch(path: path.string, mask: mask)
result.append(wd)
}
return result
}
public func removeWatch(_ wd: CInt) throws {
guard inotify_rm_watch(self.fd, wd) == 0 else {
throw InotifyError.removeWatchFailed(watchDescriptor: wd, errno: cinotify_get_errno())