{"id":30792075,"url":"https://github.com/cleancocoa/asyncfilemonitor","last_synced_at":"2025-09-07T20:01:58.542Z","repository":{"id":312905171,"uuid":"1048247563","full_name":"CleanCocoa/AsyncFileMonitor","owner":"CleanCocoa","description":"Swift Concurrency wrapper for monitoring file system events using FSEvents","archived":false,"fork":false,"pushed_at":"2025-09-02T18:06:56.000Z","size":59,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-02T19:14:27.590Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CleanCocoa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-01T06:47:19.000Z","updated_at":"2025-09-02T18:59:00.000Z","dependencies_parsed_at":"2025-09-02T19:26:44.217Z","dependency_job_id":null,"html_url":"https://github.com/CleanCocoa/AsyncFileMonitor","commit_stats":null,"previous_names":["cleancocoa/asyncfilemonitor"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/CleanCocoa/AsyncFileMonitor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleanCocoa%2FAsyncFileMonitor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleanCocoa%2FAsyncFileMonitor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleanCocoa%2FAsyncFileMonitor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleanCocoa%2FAsyncFileMonitor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CleanCocoa","download_url":"https://codeload.github.com/CleanCocoa/AsyncFileMonitor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleanCocoa%2FAsyncFileMonitor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273778165,"owners_count":25166512,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-05T02:00:09.113Z","response_time":402,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-05T15:34:21.794Z","updated_at":"2025-09-07T20:01:58.530Z","avatar_url":"https://github.com/CleanCocoa.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AsyncFileMonitor\n\n[![Swift](https://img.shields.io/badge/Swift-6.0-orange.svg)](https://swift.org)\n[![Platform](https://img.shields.io/badge/Platforms-macOS%2014%2B-blue.svg)](https://github.com/apple/swift-package-manager)\n[![Swift Package Manager](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager)\n\nModern async/await Swift Package for monitoring file system events using CoreFoundation's FSEvents API.\n\nAsyncFileMonitor is the modernized successor to RxFileMonitor, providing the same powerful file monitoring capabilities with Swift 6 concurrency support and no external dependencies.\n\n## Features\n\n- **Modern Async/await**: Uses `AsyncStream` for natural async/await integration\n- **Swift 6 Ready**: Full concurrency support with `Sendable` conformance\n- **FSEvents Integration**: Efficient file system monitoring using Apple's native FSEvents API\n- **Flexible Monitoring**: Monitor single files, directories, or multiple paths\n- **Event Filtering**: Rich event information with detailed change flags\n\n## Usage\n\n### Basic Usage\n\n```swift\nimport AsyncFileMonitor\n\n// Monitor a directory\nlet eventStream = FolderContentMonitor.makeStream(url: URL(fileURLWithPath: \"/path/to/monitor/\"))\n\n// Use async/await to process events\nfor await event in eventStream {\n    print(\"File changed: \\(event.filename) at \\(event.eventPath)\")\n    print(\"Change type: \\(event.change)\")\n}\n```\n\n### Advanced Configuration\n\n```swift\nimport AsyncFileMonitor\n\n// Create a stream with custom configuration\nlet eventStream = FolderContentMonitor.makeStream(\n    url: URL(fileURLWithPath: \"/Users/you/Documents\"),\n    latency: 0.5  // Coalesce rapid changes\n)\n\n// Process file events\nfor await event in eventStream {\n    // Filter for file changes only\n    guard event.change.contains(.isFile) else { continue }\n    \n    // Skip system files\n    guard event.filename != \".DS_Store\" else { continue }\n    \n    print(\"Document changed: \\(event.filename)\")\n}\n```\n\n### Monitoring Multiple Paths\n\n```swift\nlet eventStream = FolderContentMonitor.makeStream(paths: [\n    \"/Users/you/Documents\", \n    \"/Users/you/Desktop\"\n])\n\nfor await event in eventStream {\n    print(\"Change in \\(event.eventPath): \\(event.change)\")\n}\n```\n\n### Task-based Processing\n\n```swift\nlet eventStream = FolderContentMonitor.makeStream(url: folderURL)\n\nlet monitorTask = Task {\n    for await event in eventStream {\n        // Process file events\n        await handleFileChange(event)\n    }\n}\n\n// Stop monitoring\nmonitorTask.cancel()\n```\n\n### Filtering Events\n\n```swift\nlet eventStream = FolderContentMonitor.makeStream(url: documentsURL)\n\nfor await event in eventStream\nwhere event.change.contains(.isFile) \u0026\u0026 event.change.contains(.modified) {\n    await processModifiedFile(event.url)\n}\n```\n\n### Multiple Concurrent Streams\n\nAsyncFileMonitor uses a multicast AsyncStream approach where multiple streams from the same monitor **share a single FSEventStream** and receive identical events in **registration order**:\n\n```swift\n// Create multiple independent streams monitoring the same directory\nlet uiUpdateStream = FolderContentMonitor.makeStream(url: documentsURL)\nlet backupStream = FolderContentMonitor.makeStream(url: documentsURL)\nlet logStream = FolderContentMonitor.makeStream(url: documentsURL)\n\n// Process events differently in each stream\nTask {\n    for await event in uiUpdateStream {\n        await updateUI(for: event)\n    }\n}\n\nTask {\n    for await event in backupStream {\n        guard event.change.contains(.modified) else { continue }\n        await backupFile(event.url)\n    }\n}\n\nTask {\n    for await event in logStream {\n        logger.info(\"File changed: \\(event.filename)\")\n    }\n}\n```\n\n**Ordering Guarantee**: Events are delivered to subscribers in registration order. In the example above, for each file system event:\n\n1. `uiUpdateStream` receives the event first\n2. `backupStream` receives the event second\n3. `logStream` receives the event third\n\nYou should probably not rely on the kind of things like subscription order, but I figured it's better you know just in case that you run into concurrency-related issues in your app, than having to guess.\n\n## Event Types\n\nThe `Change` struct provides detailed information about what changed:\n\n### File Type Flags\n- `.isFile` - The item is a regular file\n- `.isDirectory` - The item is a directory\n- `.isSymlink` - The item is a symbolic link\n- `.isHardlink` - The item is a hard link\n\n### Change Type Flags\n- `.created` - Item was created\n- `.modified` - Item was modified\n- `.removed` - Item was removed\n- `.renamed` - Item was renamed/moved\n\n### Metadata Changes\n- `.changeOwner` - Ownership changed\n- `.finderInfoModified` - Finder info changed\n- `.inodeMetaModified` - Inode metadata changed\n- `.xattrsModified` - Extended attributes changed\n\n## Latency Configuration\n\nControl event coalescing with the `latency` parameter:\n\n```swift\n// No latency - all events reported immediately (can be noisy)\nlet eventStream = FolderContentMonitor.makeStream(url: url, latency: 0.0)\n\n// 1-second latency - coalesces rapid changes\nlet eventStream = FolderContentMonitor.makeStream(url: url, latency: 1.0)\n```\n\nA latency of 0.0 can produce too much noise when applications make multiple rapid changes to files. Experiment with slightly higher values (e.g., 0.1-1.0 seconds) to reduce noise.\n\n## Understanding File Events\n\nDifferent applications can generate different event patterns:\n\n### TextEdit (atomic saves):\n```\ntexteditfile.txt changed (isFile, renamed, xattrsModified)\ntexteditfile.txt changed (isFile, renamed, finderInfoModified, xattrsModified)\ntexteditfile.txt.sb-56afa5c6-DmdqsL changed (isFile, renamed)\ntexteditfile.txt changed (isFile, renamed, finderInfoModified, inodeMetaModified, xattrsModified)\ntexteditfile.txt.sb-56afa5c6-DmdqsL changed (isFile, modified, removed, renamed, changeOwner)\n```\n\n### Simple editors (direct writes):\n```\nfile.txt changed (isFile, modified, xattrsModified)\n```\n\n## Installation\n\n### Swift Package Manager\n\nAdd AsyncFileMonitor to your `Package.swift`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/yourusername/AsyncFileMonitor.git\", from: \"1.0.0\")\n]\n```\n\nOr add it through Xcode:\n1. File \u003e Add Package Dependencies\n2. Enter the repository URL\n3. Select your target\n\n## Requirements\n\n- macOS 14.0+\n- Swift 6.0+\n- Xcode 16.0+\n\n## Migration from RxFileMonitor\n\nAsyncFileMonitor provides the same core functionality as [RxFileMonitor](https://github.com/RxSwiftCommunity/RxFileMonitor/) but with modern Swift concurrency:\n\n### Before (RxFileMonitor)\n```swift\nimport RxFileMonitor\nimport RxSwift\n\nlet monitor = FolderContentMonitor(url: folderUrl)\nlet disposeBag = DisposeBag()\n\nmonitor.rx.folderContentChange\n    .subscribe(onNext: { event in\n        print(\"File changed: \\(event.filename)\")\n    })\n    .disposed(by: disposeBag)\n```\n\n### After (AsyncFileMonitor)\n```swift\nimport AsyncFileMonitor\n\nlet eventStream = FolderContentMonitor.makeStream(url: folderUrl)\n\nfor await event in eventStream {\n    print(\"File changed: \\(event.filename)\")\n}\n```\n\n## Demo -- Command Line Tool\n\nAsyncFileMonitor includes a built-in CLI tool for monitoring file changes to demo the capabilities:\n\n```bash\n# Monitor a single directory\nswift run watch /Users/username/Documents\n\n# Monitor multiple directories\nswift run watch /path/to/folder1 /path/to/folder2\n\n# Show usage help\nswift run watch\n```\n\nExample Output:\n\n```\n🎯 Starting AsyncFileMonitor CLI\n📁 Monitoring paths:\n   • /Users/username/Documents\n📡 Press Ctrl+C to stop monitoring\n\n[14:23:15.123] 📄 /Users/username/Documents/test.txt\n                🔄 isFile, modified\n                🆔 Event ID: 12345678\n\n[14:23:15.456] 📄 /Users/username/Documents/newfile.txt\n                🔄 isFile, created\n                🆔 Event ID: 12345679\n```\n\n## Building and Testing\n\n```bash\n# Build\nmake build\n\n# Build and run CLI tool\nswift run watch /path/to/monitor\n\n# Generate documentation\nmake docs\n\n# Preview documentation in browser\nmake docs-preview\n\n# Generate static documentation website\nmake docs-static\n\n# Run tests  \nmake test\n\n# Format code\nmake format\n\n# Clean\nmake clean\n```\n\n## License\n\nCopyright (c) 2016 Christian Tietze, RxSwiftCommunity (original RxFileMonitor)  \nCopyright (c) 2025 Christian Tietze (AsyncFileMonitor modernization)\n\nDistributed under The MIT License. See LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcleancocoa%2Fasyncfilemonitor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcleancocoa%2Fasyncfilemonitor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcleancocoa%2Fasyncfilemonitor/lists"}