{"id":13513553,"url":"https://github.com/e-dant/watcher","last_synced_at":"2025-03-31T02:32:31.192Z","repository":{"id":61617016,"uuid":"532930182","full_name":"e-dant/watcher","owner":"e-dant","description":"Filesystem watcher. Works anywhere. Simple, efficient and friendly.","archived":false,"fork":false,"pushed_at":"2025-02-17T23:16:43.000Z","size":2717,"stargazers_count":726,"open_issues_count":8,"forks_count":40,"subscribers_count":7,"default_branch":"release","last_synced_at":"2025-03-29T00:42:22.670Z","etag":null,"topics":["c","cpp","efficient","fast","filesystem","friendly","header-only","javascript","nodejs","python","safe","simple","single-header","thread-safe","watcher"],"latest_commit_sha":null,"homepage":"","language":"C++","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/e-dant.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":"contributing.md","funding":null,"license":"license","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"security.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-09-05T14:12:55.000Z","updated_at":"2025-03-12T21:43:14.000Z","dependencies_parsed_at":"2023-10-21T22:31:53.456Z","dependency_job_id":"74c5667c-37c4-4e7d-91bf-0407a6daaf6f","html_url":"https://github.com/e-dant/watcher","commit_stats":null,"previous_names":[],"tags_count":58,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/e-dant%2Fwatcher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/e-dant%2Fwatcher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/e-dant%2Fwatcher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/e-dant%2Fwatcher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/e-dant","download_url":"https://codeload.github.com/e-dant/watcher/tar.gz/refs/heads/release","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246407179,"owners_count":20772079,"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","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":["c","cpp","efficient","fast","filesystem","friendly","header-only","javascript","nodejs","python","safe","simple","single-header","thread-safe","watcher"],"created_at":"2024-08-01T05:00:30.958Z","updated_at":"2025-03-31T02:32:31.184Z","avatar_url":"https://github.com/e-dant.png","language":"C++","readme":"# Watcher\n\n[![Conan Center](https://img.shields.io/conan/v/watcher)](https://conan.io/center/recipes/watcher)\n[![Rust/Cargo Crate](https://img.shields.io/crates/v/wtr-watcher.svg)](https://crates.io/crates/wtr-watcher)\n[![PyPI/Pip Package](https://badge.fury.io/py/wtr-watcher.svg)](https://badge.fury.io/py/wtr-watcher)\n[![Builds and Publishing for Distribution](https://github.com/e-dant/watcher/actions/workflows/dist.yml/badge.svg)](https://github.com/e-dant/watcher/actions/workflows/dist.yml)\n[![CodeQL Tests](https://github.com/e-dant/watcher/actions/workflows/codeql.yml/badge.svg)](https://github.com/e-dant/watcher/actions/workflows/codeql.yml)\n[![Linux Tests](https://github.com/e-dant/watcher/actions/workflows/ubuntu.yml/badge.svg)](https://github.com/e-dant/watcher/actions/workflows/ubuntu.yml)\n[![macOS Tests](https://github.com/e-dant/watcher/actions/workflows/macos.yml/badge.svg)](https://github.com/e-dant/watcher/actions/workflows/macos.yml)\n[![Android Compilation Tests](https://github.com/e-dant/watcher/actions/workflows/android.yml/badge.svg)](https://github.com/e-dant/watcher/actions/workflows/android.yml)\n[![Windows Tests](https://github.com/e-dant/watcher/actions/workflows/windows.yml/badge.svg)](https://github.com/e-dant/watcher/actions/workflows/windows.yml)\n\n## Quick Start\n\n\u003cdetails\u003e\n\u003csummary\u003eC++\u003c/summary\u003e\n\n```cpp\n#include \"wtr/watcher.hpp\"\n#include \u003ciostream\u003e\n#include \u003cstring\u003e\n\nusing namespace std;\nusing namespace wtr;\n\n// The event type, and every field within it, has\n// string conversions and stream operators. All\n// kinds of strings -- Narrow, wide and weird ones.\n// If we don't want particular formatting, we can\n// json-serialize and show the event like this:\n//   some_stream \u003c\u003c event\n// Here, we'll apply our own formatting.\nauto show(event e) {\n  cout \u003c\u003c to\u003cstring\u003e(e.effect_type) + ' '\n        + to\u003cstring\u003e(e.path_type)   + ' '\n        + to\u003cstring\u003e(e.path_name)\n        + (e.associated ? \" -\u003e \" + to\u003cstring\u003e(e.associated-\u003epath_name) : \"\")\n       \u003c\u003c endl;\n}\n\nauto main() -\u003e int {\n  // Watch the current directory asynchronously,\n  // calling the provided function on each event.\n  auto watcher = watch(\".\", show);\n\n  // Do some work. (We'll just wait for a newline.)\n  getchar();\n\n  // The watcher would close itself around here,\n  // though we can check and close it ourselves.\n  return watcher.close() ? 0 : 1;\n}\n```\n\n```sh\n# Sigh\nPLATFORM_EXTRAS=$(test \"$(uname)\" = Darwin \u0026\u0026 echo '-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -framework CoreFoundation -framework CoreServices')\n# Build\neval c++ -std=c++17 -Iinclude src/wtr/tiny_watcher/main.cpp -o watcher $PLATFORM_EXTRAS\n# Run\n./watcher\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eC\u003c/summary\u003e\n\n```c\n#include \"wtr/watcher-c.h\"\n#include \u003cstdio.h\u003e\n\nvoid callback(struct wtr_watcher_event event, void* _ctx) {\n    printf(\n        \"path name: %s, effect type: %d path type: %d, effect time: %lld, associated path name: %s\\n\",\n        event.path_name,\n        event.effect_type,\n        event.path_type,\n        event.effect_time,\n        event.associated_path_name ? event.associated_path_name : \"\"\n    );\n}\n\nint main() {\n    void* watcher = wtr_watcher_open(\".\", callback, NULL);\n    getchar();\n    return ! wtr_watcher_close(watcher);\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003ePython\u003c/summary\u003e\n\n```sh\npip install wtr-watcher\n```\n\n```python\nfrom watcher import Watch\n\nwith Watch(\".\", print):\n    input()\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eRust\u003c/summary\u003e\n\n```sh\ncargo add wtr-watcher tokio futures\n```\n\n```rust\nuse futures::StreamExt;\nuse wtr_watcher::Watch;\n\n#[tokio::main(flavor = \"current_thread\")]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    let show = |e| async move { println!(\"{e:?}\") };\n    let events = Watch::try_new(\".\")?;\n    events.for_each(show).await;\n    Ok(())\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eNode.js\u003c/summary\u003e\n\n```javascript\nimport * as watcher from 'watcher';\n\nvar w = watcher.watch('.', (event) =\u003e {\n  console.log(event);\n});\n\nprocess.stdin.on('data', () =\u003e {\n  w.close();\n  process.exit();\n});\n```\n\u003c/details\u003e\n\nThe output of each above will be something like this, depending on the example:\n\n```\nmodify file /home/e-dant/dev/watcher/.git/refs/heads/next.lock\nrename file /home/e-dant/dev/watcher/.git/refs/heads/next.lock -\u003e /home/e-dant/dev/watcher/.git/refs/heads/next\ncreate file /home/e-dant/dev/watcher/.git/HEAD.lock\n```\n\nEnjoy!\n\n---\n\n## Tell Me More\n\nA filesystem event watcher which is\n\n1. Friendly\n\u003e I try to keep the [1579](https://github.com/e-dant/watcher/blob/release/tool/sl)\nlines that make up the runtime of *Watcher* [relatively simple](https://github.com/e-dant/watcher/blob/release/include/wtr/watcher.hpp)\nand the API practical:\n```cpp\nauto w = watch(path, [](event ev) { cout \u003c\u003c ev; });\n```\n```sh\nwtr.watcher ~\n```\n\n2. Modular\n\u003e *Watcher* may be used as **a library, a program, or both**.\nIf you aren't looking to create something with the library, no worries.\nJust use ours and you've got yourself a filesystem watcher which prints\nfilesystem events as JSON. Neat. Here's how:\n```bash\n# The main branch is the (latest) release branch.\ngit clone https://github.com/e-dant/watcher.git \u0026\u0026 cd watcher\n# Via Nix\nnix run | grep -oE 'cmake-is-tough'\n# With the build script\ntool/build --no-build-test --no-run \u0026\u0026 cd out/this/Release # Build the release version for the host platform.\n./wtr.watcher | grep -oE 'needle-in-a-haystack/.+\"' # Use it, pipe it, whatever. (This is an .exe on Windows.)\n```\n\n3. Efficient\n\u003e You can watch an *entire filesystem* with this project.\nIn [almost all cases](https://github.com/e-dant/watcher/tree/release#exceptions-to-efficient-scanning),\nwe use a near-zero amount of resources and make\n[efficient use of the cache](https://github.com/e-dant/watcher/tree/release#cache-efficiency).\nWe regularly test that the overhead of detecting and sending an event to the user is\nan order of magnitude less than the filesystem operations being measured.\n\n4. Well Tested\n\u003e We run this project through\n[unit tests against all available sanitiziers](https://github.com/e-dant/watcher/actions).\nThis code tries hard to be thread, memory, bounds, type and resource-safe. What we lack\nfrom the language, we try to make up for with testing. For some practical definition of safety,\nthis project probably fits.\n\n5. Dependency Minimal\n\u003e *Watcher* depends on the C++ Standard Library. For efficiency,\nwe [leverage the OS](https://github.com/e-dant/watcher/tree/release#os-apis-used)\nwhen possible on Linux, Darwin and Windows. For testing and\ndebugging, we use [Snitch](https://github.com/cschreib/snitch) and\n[Sanitizers](https://clang.llvm.org/docs/index.html).\n\n6. Portable\n\u003e *Watcher* is runnable almost anywhere. The only requirement\nis a filesystem.\n\n---\n\n## Usage\n\n### Project Content\n\nThe important pieces are the (header-only) library and the (optional) CLI program.\n\n- C++ Header-Only Library: `include/wtr/watcher.hpp`.\n  Include this to use *Watcher* in your C++ project. Copying this into your project, and\n  including it as `#include \"wtr/watcher.hpp\"` (or similar) is sufficient to get up and\n  running in this language. Some extra documentation and high-level library internals can\n  be found in the [event](https://github.com/e-dant/watcher/blob/release/devel/include/wtr/watcher-/event.hpp)\n  and [watch](https://github.com/e-dant/watcher/blob/release/devel/include/wtr/watcher-/watch.hpp) headers.\n- C Library: `watcher-c`.\n  Build this to use *Watcher* from C or through an FFI in other languages.\n- Full CLI Program: `src/wtr/watcher/main.cpp`.\n  Build this to use *Watcher* from the command line. The output is an exhaustive JSON stream.\n- Minimal CLI Program: `src/wtr/tiny_watcher/main.cpp`.\n  A very minimal, more human-readable, CLI program. The source for this is almost identical\n  to the example usage for C++.\n\nA directory tree is [in the notes below](https://github.com/e-dant/watcher/tree/release#namespaces-and-the-directory-tree).\n\n### The Library\n\nThe two fundamental building blocks here are:\n  - The `watch` function or class (depending on the language)\n  - The `event` object (or similarly named, again depending on the language)\n\n`watch` takes a path, which is a string-like thing, and a\ncallback, with is a function-like thing. For example, passing\n`watch` a character array and a closure would work well in C++.\n\nExamples for a variety of languages can be found in the [Quick Start](https://github.com/e-dant/watcher/tree/release#quick-start).\nThe API is relatively consistent across languages.\n\nThe watcher will happily continue watching until you stop\nit or it hits an unrecoverable error.\n\nThe `event` object is used to pass information about\nfilesystem events to the callback given (by you) to `watch`.\n\nThe `event` object will contain:\n  - `path_name`, which is an absolute path to the event.\n  - `path_type`, the type of path. One of:\n    - `dir`\n    - `file`\n    - `hard_link`\n    - `sym_link`\n    - `watcher`\n    - `other`\n  - `effect_type`, \"what happened\". One of:\n    - `rename`\n    - `modify`\n    - `create`\n    - `destroy`\n    - `owner`\n    - `other`\n  - `effect_time`, the time of the event in nanoseconds since epoch.\n  - `associated` (an event, C++) or `associated_path_name` (all other implementations, a single path name):\n    - For the C++ implementation, this is a recursive structure. Another event, associated with \"this\" one, is stored here.\n      The only events stored here, currently, are the renamed-to part of rename events.\n    - For all other implementations, this field represents the path name of an associated event.\n    - The implementation in C++, a recursive structure, was chosen to future-proof the library in the event that\n      we need to support other associated events.\n\n(Note that, for JavaScript, we use the camel-case, to be consistent with that language's ecosystem.)\n\n#### State Changes and Special Events\n\nThe `watcher` type is special.\n\nEvents with this type will include messages from\nthe watcher. You may recieve error messages or\nimportant status updates.\n\nThis format was chosen to support asynchronous messages\nfrom the watcher in a generic, portable format.\n\nTwo of the most important \"watcher\" events are the\ninitial \"live\" event and the final \"die\" event.\n\nThe message appears prepended to the watched base path.\n\nFor example, after opening a watcher at `/a/path`, you may receive these\nmessages from the watcher:\n- `s/self/live@/a/path`\n- `e/self/die@/a/path`\n\nThe messages always begin with either an `s`, indicating a\nsuccessful operation, a `w`, indicating a non-fatal warning,\nor an `e`, indicating a fatal error.\n\nImportantly, closing the watcher will always produce an error if\n- The `self/live` message has not yet been sent; or, in other words,\n  if the watcher has not fully started. In this case, the watcher\n  will immediately close after fully opening and report an error\n  in all calls to close.\n- Any repeated calls to close the watcher. In other words, it is\n  considered an error to close a watcher which has already been\n  closed, or which does not exist. For the C API, this is also true\n  for passing a null object to close.\n\nThe last event will always be a `destroy` event from the watcher.\nYou can parse it like this, for some event `ev`:\n\n```cpp\nev.path_type == path_type::watcher \u0026\u0026 ev.effect_type == effect_type::destroy;\n```\n\nHappy hacking.\n\n### Your Project\n\nThis project tries to make it easy for you to work with\nfilesystem events. I think good tools are easy to use. If\nthis project is not ergonomic, file an issue.\n\nHere is a snapshot of the output taken while preparing this\ncommit, right before writing this paragraph.\n\n```json\n{\n  \"1666393024210001000\": {\n    \"path_name\": \"/home/edant/dev/watcher/.git/logs/HEAD\",\n    \"effect_type\": \"modify\",\n    \"path_type\": \"file\"\n  },\n  \"1666393024210026000\": {\n    \"path_name\": \"/home/edant/dev/watcher/.git/logs/refs/heads/next\",\n    \"effect_type\": \"modify\",\n    \"path_type\": \"file\"\n  },\n  \"1666393024210032000\": {\n    \"path_name\": \"/home/edant/dev/watcher/.git/refs/heads/next.lock\",\n    \"effect_type\": \"create\",\n    \"path_type\": \"other\"\n  }\n}\n```\n\nWhich is pretty cool.\n\nA capable program is [here](https://github.com/e-dant/watcher/blob/release/src/wtr/watcher/main.cpp).\n\n## Consume\n\nThis project is accessible through:\n- Conan: Includes the C++ header/library\n- Nix: Provides isolation, determinism, includes header, cli, test and benchmark targets\n- Bazel: Provides isolation, includes the C++ header/library and cli targets\n- `tool/build`: Includes the C++ header/library, cli, test and benchmark targets\n- `tool/cross`: Includes the `watcher-c` shared library and header, cross-compiled for many platforms\n- CMake: Includes the single-header C++ library, the `watcher-c` library (static and shared), cli, test and benchmark targets\n- Just copying the header file\n\n\u003cdetails\u003e\n\u003csummary\u003eConan\u003c/summary\u003e\n\nSee the [package here](https://conan.io/center/recipes/watcher).\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eNix\u003c/summary\u003e\n\n```sh\nnix build # To just build\nnix run # Build the default target, then run without arguments\nnix run . -- / | jq # Build and run, watch the root directory, pipe it to jq\nnix develop # Enter an isolated development shell with everything needed to explore this project\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eBazel\u003c/summary\u003e\n\n```sh\nbazel build cli # Build, but don't run, the cli\nbazel build hdr # Ditto, for the single-header\nbazel run cli # Run the cli program without arguments\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e`tool/build`\u003c/summary\u003e\n\n```sh\ntool/build\ncd out/this/Release\n\n# watches the current directory forever\n./wtr.watcher\n# watches some path for 10 seconds\n./wtr.watcher 'your/favorite/path' -s 10\n```\n\nThis will take care of some platform-specifics, building the\nrelease, debug, and sanitizer variants, and running some tests.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eCMake\u003c/summary\u003e\n\n```sh\ncmake -S . -B out\ncmake --build out --config Release\ncd out\n\n# watches the current directory forever\n./wtr.watcher\n# watches some path for 10 seconds\n./wtr.watcher 'your/favorite/path' -s 10\n```\n\u003c/details\u003e\n\n## Bugs \u0026 Limitations\n\n\u003cdetails\u003e\n\u003csummary\u003e\"Access\" events are ignored\u003c/summary\u003e\n\nWatchers on all platforms intentionally ignore\nmodification events which only change the acess\ntime on a file or directory.\n\nThe utility of those events was questionable.\n\nIt seemed more harmful than good. Other watchers,\nlike Microsoft's C# watcher, ignore them by default.\nSome user applications rely on modification events\nto know when themselves to reload a file.\n\nBetter, more complete solutions exist, and these\ndefaults might again change.\n\nProviding a way to ignore events from a process-id,\na shorthand from \"this\" process, and a way to specify\nwhich kinds of event sources we are interested in\nare good candidates for more complete solutions.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eSafety and C++\u003c/summary\u003e\n\nI was comfortable with C++ when I first wrote\nthis. I later rewrote this project in Rust as\nan experiment. There are benefits and drawbacks\nto Rust. Some things were a bit safer to express,\nother things were definitely not. The necessity\nof doing pointer math on some variably-sized\nopaque types from the kernel, for example, is not\nsafer to express in Rust. Other things are safer,\nbut this project doesn't benefit much from them.\n\nRust really shines in usability and expression.\nThat might be enough of a reason to use it.\nAmong other things, we could work with async\ntraits and algebraic types for great good.\n\nI'm not sure if there is a language that can\n\"just\" make the majority of the code in this\nproject safe by definition.\n\nThe guts of this project, the adapters, talk\nto the kernel. They are bound to use unsafe,\nill-typed, caveat-rich system-level interfaces.\n\nThe public API is just around 100 lines, is\nwell-typed, well-tested, and human-verifiable.\nNot much happens there.\n\nCreating an FFI by exposing the adapters with\na C ABI might be worthwhile. Most languages\nshould be able to hook into that.\n\nThe safety of the platform adapters necessarily\ndepends on each platform's documentation for\ntheir interfaces. Like with all system-level\ninterfaces, as long as we ensure the correct\npre-and-post-conditions, and those conditions\nare well-defined, we should be fine.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003ePlatform-specific adapter selection\u003c/summary\u003e\n\nAmong the platform-specific [implementations](https://github.com/e-dant/watcher/tree/release/devel/include/detail/wtr/watcher/adapter),\nthe `FSEvents` API is used on Darwin and the\n`ReadDirectoryChanges` API is used on Windows.\nThere is some extra work we do to select the best\nadapter on Linux. The `fanotify` adapter is used\nwhen the kernel version is greater than 5.9, the\ncontaining process has root priveleges, and the\nnecessary system calls are otherwise allowed.\nThe system calls associated with `fanotify` may\nbe disallowed when inside a container or cgroup,\ndespite the necessary priviledges and kernel\nversion. The `inotify` adapter is used otherwise.\nYou can find the selection code for Linux [here](https://github.com/e-dant/watcher/blob/next/devel/include/detail/wtr/watcher/adapter/linux/watch.hpp).\n\nThe namespaces for our [adapters](https://github.com/e-dant/watcher/tree/release/devel/include/detail/wtr/watcher/adapter)\nare inline. When the (internal) `detail::...::watch()`\nfunction is [invoked](https://github.com/e-dant/watcher/blob/release/devel/include/wtr/watcher-/watch.hpp#L65),\nit resolves to one (and only one) platform-specifc\nimplementation of the `watch()` function. One symbol,\nmany platforms, where the platforms are inline\nnamespaces.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eExceptions to Efficient Scanning\u003c/summary\u003e\n\nEfficiency takes a hit when we bring out the `warthog`,\nour platform-independent adapter. This adapter is used\non platforms that lack better alternatives, such as (not\nDarwin) BSD and Solaris (because `warthog` beats `kqueue`).\n\n*Watcher* is still relatively efficient when it has no\nalternative better than `warthog`. As a thumb-rule,\nscanning more than one-hundred-thousand paths with `warthog`\nmight stutter.\n\nI'll keep my eyes open for better kernel APIs on BSD.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eReady State\u003c/summary\u003e\n\nThere is no reliable way to communicate when a\nwatcher is ready to send events to the callback.\n\nFor a few thousand paths, this may take a few\nmilliseconds. For tens-of-thousands of paths,\nconsider waiting a few seconds.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eUnsupported events\u003c/summary\u003e\n\nNone of the platform-specific implementations provide\ninformation on what attributes were changed from.\nThis makes supporting those events dependant on storing\nthis information ourselves. Storing maps of paths to\n`stat` structures, diffing them on attribute changes,\nis a non-insignificant memory commitment.\n\nThe owner and attribute events are unsupported because\nI'm not sure how to support those events efficienty.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eUnsupported filesystems\u003c/summary\u003e\n\nSpecial filesystems, including `/proc` and `/sys`,\ncannot be watched with `inotify`, `fanotify` or the\n`warthog`. Future work may involve dispatching ebpf\nprograms for the kernel to use. This would allow us\nto monitor for `modify` events on some of those\nspecial filesystem.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eResource limitations\u003c/summary\u003e\n\nThe number of watched files is limited when `inotify`\nis used.\n\u003c/details\u003e\n\n## Relevant OS APIs Used\n\n\u003cdetails\u003e\n\u003csummary\u003eLinux\u003c/summary\u003e\n- `inotify`\n- `fanotify`\n- `epoll`\n- `eventfd`\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eDarwin\u003c/summary\u003e\n- `FSEvents`\n- `dispatch`\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eWindows\u003c/summary\u003e\n- `ReadDirectoryChangesW`\n- `IoCompletionPort`\n\u003c/details\u003e\n\n## Minimum C++ Version\n\nFor the header-only library and the tiny-watcher,\nC++17 and up should be fine.\n\nWe might use C++20 coroutines someday.\n\n## Cache Efficiency\n\n```sh\n$ tool/gen-event/dir \u0026\n$ tool/gen-event/file \u0026\n$ valgrind --tool=cachegrind wtr.watcher ~ -s 30\n```\n\n```txt\nI   refs:      797,368,564\nI1  misses:          6,807\nLLi misses:          2,799\nI1  miss rate:        0.00%\nLLi miss rate:        0.00%\n\nD   refs:      338,544,669  (224,680,988 rd   + 113,863,681 wr)\nD1  misses:         35,331  (     24,823 rd   +      10,508 wr)\nLLd misses:         11,884  (      8,121 rd   +       3,763 wr)\nD1  miss rate:         0.0% (        0.0%     +         0.0%  )\n\nLLd miss rate:         0.0% (        0.0%     +         0.0%  )\nLL refs:            42,138  (     31,630 rd   +      10,508 wr)\nLL misses:          14,683  (     10,920 rd   +       3,763 wr)\nLL miss rate:          0.0% (        0.0%     +         0.0%  )\n```\n\n## Namespaces and the Directory Tree\n\nNamespaces and symbols closely follow the directories in the `devel/include` folder.\nInline namespaces are in directories with the `-` affix.\n\nFor example, `wtr::watch` is inside the file `devel/include/wtr/watcher-/watch.hpp`.\nThe namespace `watcher` in `wtr::watcher::watch` is anonymous by this convention.\n\nMore in depth: the function `::detail::wtr::watcher::adapter::watch()` is defined inside\none (and only one!) of the files `devel/include/detail/wtr/watcher/adapter/*/watch.hpp`,\nwhere `*` is decided at compile-time (depending on the host's operating system).\n\nAll of the headers in `devel/include` are amalgamated into `include/wtr/watcher.hpp`\nand an include guard is added to the top. The include guard doesn't change with the\nrelease version. In the future, it might.\n\n\n```\nwatcher\n├── src\n│  └── wtr\n│     ├── watcher\n│     │  └── main.cpp\n│     └── tiny_watcher\n│        └── main.cpp\n├── out\n├── include\n│  └── wtr\n│     └── watcher.hpp\n└── devel\n   ├── src\n   │  └── wtr\n   └── include\n      ├── wtr\n      │  ├── watcher.hpp\n      │  └── watcher-\n      │     ├── watch.hpp\n      │     └── event.hpp\n      └── detail\n         └── wtr\n            └── watcher\n               ├── semabin.hpp\n               └── adapter\n                  ├── windows\n                  │  └── watch.hpp\n                  ├── warthog\n                  │  └── watch.hpp\n                  ├── linux\n                  │  ├── watch.hpp\n                  │  ├── sysres.hpp\n                  │  ├── inotify\n                  │  │  └── watch.hpp\n                  │  └── fanotify\n                  │     └── watch.hpp\n                  └── darwin\n                     └── watch.hpp\n```\n\n\u003e You can run [`tool/tree`](https://github.com/e-dant/watcher/blob/release/tool/tree) to view this tree locally.\n\n\u003cdetails\u003e\n\u003csummary\u003eComparison with Similar Projects\u003c/summary\u003e\n\n```yml\nhttps://github.com/notify-rs/notify:\n  lines of code: 2799\n  lines of tests: 475\n  lines of docs: 1071\n  implementation languages: rust\n  interface languages: rust\n  supported platforms: linux, windows, darwin, bsd\n  kernel apis: inotify, readdirectorychanges, fsevents, kqueue\n  non-blocking: yes\n  dependencies: none\n  tests: yes\n  static analysis: yes (borrow checked, memory and concurrency safe language)\n\nhttps://github.com/e-dant/watcher:\n  lines of code: 1579\n  lines of tests: 881\n  lines of docs: 1977\n  implementation languages: cpp\n  interface languages: cpp, shells\n  supported platforms: linux, darwin, windows, bsd\n  kernel apis: inotify, fanotify, fsevents, readdirectorychanges\n  non-blocking: yes\n  dependencies: none\n  tests: yes\n  static analysis: yes\n\nhttps://github.com/facebook/watchman.git:\n  lines of code: 37435\n  lines of tests: unknown\n  lines of docs: unknown\n  implementation languages: cpp, c\n  interface languages: cpp, js, java, python, ruby, rust, shells\n  supported platforms: linux, darwin, windows, maybe bsd\n  kernel apis: inotify, fsevents, readdirectorychanges\n  non-blocking: yes\n  dependencies: none\n  tests: yes (many)\n  static analysis: yes (all available)\n\nhttps://github.com/p-ranav/fswatch:\n  lines of code: 245\n  lines of tests: 19\n  lines of docs: 114\n  implementation languages: cpp\n  interface languages: cpp, shells\n  supported platforms: linux, darwin, windows, bsd\n  kernel apis: inotify\n  non-blocking: maybe\n  dependencies: none\n  tests: some\n  static analysis: none\n\nhttps://github.com/tywkeene/go-fsevents:\n  lines of code: 413\n  lines of tests: 401\n  lines of docs: 384\n  implementation languages: go\n  interface languages: go\n  supported platforms: linux\n  kernel apis: inotify\n  non-blocking: yes\n  dependencies: yes\n  tests: yes\n  static analysis: none (gc language)\n\nhttps://github.com/radovskyb/watcher:\n  lines of code: 552\n  lines of tests: 767\n  lines of docs: 399\n  implementation languages: go\n  interface languages: go\n  supported platforms: linux, darwin, windows\n  kernel apis: none\n  non-blocking: no\n  dependencies: none\n  tests: yes\n  static analysis: none\n\nhttps://github.com/parcel-bundler/watcher:\n  lines of code: 2862\n  lines of tests: 474\n  lines of docs: 791\n  implementation languages: cpp\n  interface languages: js\n  supported platforms: linux, darwin, windows, maybe bsd\n  kernel apis: fsevents, inotify, readdirectorychanges\n  non-blocking: yes\n  dependencies: none\n  tests: some (js bindings)\n  static analysis: none (interpreted language)\n\nhttps://github.com/atom/watcher:\n  lines of code: 7789\n  lines of tests: 1864\n  lines of docs: 1334\n  implementation languages: cpp\n  interface languages: js\n  supported platforms: linux, darwin, windows, maybe bsd\n  kernel apis: inotify, fsevents, readdirectorychanges\n  non-blocking: yes\n  dependencies: none\n  tests: some (js bindings)\n  static analysis: none\n\nhttps://github.com/paulmillr/chokidar:\n  lines of code: 1544\n  lines of tests: 1823\n  lines of docs: 1377\n  implementation languages: js\n  interface languages: js\n  supported platforms: linux, darwin, windows, bsd\n  kernel apis: fsevents\n  non-blocking: maybe\n  dependencies: yes\n  tests: yes (many)\n  static analysis: none (interpreted language)\n\nhttps://github.com/Axosoft/nsfw:\n  lines of code: 2536\n  lines of tests: 1085\n  lines of docs: 148\n  implementation languages: cpp\n  interface languages: js\n  supported platforms: linux, darwin, windows, maybe bsd\n  kernel apis: fsevents\n  non-blocking: maybe\n  dependencies: yes (many)\n  tests: yes (js bindings)\n  static analysis: none\n\nhttps://github.com/canton7/SyncTrayzor:\n  lines of code: 17102\n  lines of tests: 0\n  lines of docs: 2303\n  implementation languages: c#\n  interface languages: c#\n  supported platforms: windows\n  kernel apis: unknown\n  non-blocking: yes\n  dependencies: unknown\n  tests: none\n  static analysis: none (managed language)\n\nhttps://github.com/g0t4/Rx-FileSystemWatcher:\n  lines of code: 360\n  lines of tests: 0\n  lines of docs: 46\n  implementation languages: c#\n  interface languages: c#\n  supported platforms: windows\n  kernel apis: unknown\n  non-blocking: yes\n  dependencies: unknown\n  tests: yes\n  static analysis: none (managed language)\n\n```\n\u003c/details\u003e\n","funding_links":[],"categories":["C++"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fe-dant%2Fwatcher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fe-dant%2Fwatcher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fe-dant%2Fwatcher/lists"}