Watch directories for file-system events using the Linux inotify subsystem.
This module provides two usage styles:
Message-based — Start a watcher with
start_watcher/2,3, receive{:inotify_event, filename, masks}messages in a process, then stop the watcher withstop_watcher/1.Stream-based — Use
stream/2to get a lazyStreamof{filename, masks}tuples. The underlying watcher is started and stopped automatically.
Event masks
The following atoms can be used as event masks:
| Atom | Description |
|---|---|
:create | A file or directory was created |
:modify | A file was modified |
:delete | A file or directory was deleted |
:moved_from | A file was moved out of the watched directory |
:moved_to | A file was moved into the watched directory |
:access | A file was accessed (read) |
:close_write | A file opened for writing was closed |
:close_nowrite | A file opened read-only was closed |
:open | A file or directory was opened |
:attrib | File metadata (permissions, timestamps, etc.) changed |
:ignored | The watch was removed (directory deleted or unmounted) |
:isdir | Included in the event when the subject is a directory |
Pass the special atom :all as the mask to subscribe to all of the above
events at once.
Filename encoding
Filenames are delivered as binaries containing the raw bytes from the
kernel. Most filenames are valid UTF-8 and can be used as ordinary
Elixir strings. Filenames that are not valid UTF-8 (legal on Linux) are
still delivered with every byte intact and can be passed to file APIs
unchanged, but may not display correctly with IO.puts/1 and similar.
Examples
Message-based usage
{:ok, ref} = Flier.Inotify.start_watcher("/tmp", [:create, :delete])
receive do
{:inotify_event, file, masks} ->
IO.puts("Event on #{file}: #{inspect(masks)}")
end
:stopped = Flier.Inotify.stop_watcher(ref)Stream-based usage
"/tmp"
|> Flier.Inotify.stream([:close_write])
|> Stream.each(fn {file, masks} ->
IO.puts("#{file} was written and closed (masks: #{inspect(masks)})")
end)
|> Stream.run()
Summary
Functions
@spec start_watcher(path :: String.t(), [atom()] | :all, pid()) :: {:ok, reference()} | {:error, term()}
Starts an inotify watcher on path for the given event mask.
Spawns a background OS thread that polls for inotify events and sends
{:inotify_event, filename, masks} messages to pid as they occur.
Parameters
path— Absolute path to the directory to watch.mask— A list of event atoms to subscribe to (see module docs for the full list), or the atom:allto subscribe to every event type.pid— The process to deliver events to. Defaults toself().
Returns
{:ok, ref}— A reference to the watcher resource. Pass this tostop_watcher/1when done.{:error, reason}— If the path does not exist or is not watchable.
Examples
{:ok, ref} = Flier.Inotify.start_watcher("/tmp", [:create])
{:ok, ref} = Flier.Inotify.start_watcher("/tmp", :all)
{:ok, ref} = Flier.Inotify.start_watcher("/tmp", [:modify], some_pid)
@spec stop_watcher(reference()) :: :stopped
Stops a running inotify watcher.
Signals the background OS thread to stop, waits for it to terminate, and releases the underlying inotify file descriptor.
The watcher resource is also stopped automatically when the reference is garbage-collected by the BEAM.
Parameters
ref— The reference returned bystart_watcher/2,3.
Returns
:stopped
Examples
{:ok, ref} = Flier.Inotify.start_watcher("/tmp", [:create])
:stopped = Flier.Inotify.stop_watcher(ref)
@spec stream(path :: String.t(), [atom()] | :all) :: Enumerable.t()
Returns a lazy Stream of inotify events from path.
Each element emitted by the stream is a {filename, masks} tuple, where
filename is a string and masks is a list of event atoms.
The stream starts a watcher when enumeration begins and stops it when the
stream is halted or the enumerating process exits. The stream is infinite
by default — use Stream.take/2, Enum.take/2, or similar to bound it.
Blocking
Enumerating this stream blocks the calling process until an event arrives.
Run it in a dedicated process (e.g., with Task.start/1) if you need to
keep the caller responsive.
Parameters
path— Absolute path to the directory to watch.mask— A list of event atoms (see module docs), or:all. Defaults to all supported event types.
Examples
# Collect the next 5 write events in /tmp
"/tmp"
|> Flier.Inotify.stream([:close_write])
|> Enum.take(5)
|> Enum.each(fn {file, masks} -> IO.inspect({file, masks}) end)
# Run indefinitely in a task
Task.start(fn ->
"/var/log"
|> Flier.Inotify.stream([:modify])
|> Stream.each(fn {file, _masks} -> IO.puts("Modified: #{file}") end)
|> Stream.run()
end)