Flier.Inotify (Flier v0.3.1)

Copy Markdown View Source

Watch directories for file-system events using the Linux inotify subsystem.

This module provides two usage styles:

  1. Message-based — Start a watcher with start_watcher/2,3, receive {:inotify_event, filename, masks} messages in a process, then stop the watcher with stop_watcher/1.

  2. Stream-based — Use stream/2 to get a lazy Stream of {filename, masks} tuples. The underlying watcher is started and stopped automatically.

Event masks

The following atoms can be used as event masks:

AtomDescription
:createA file or directory was created
:modifyA file was modified
:deleteA file or directory was deleted
:moved_fromA file was moved out of the watched directory
:moved_toA file was moved into the watched directory
:accessA file was accessed (read)
:close_writeA file opened for writing was closed
:close_nowriteA file opened read-only was closed
:openA file or directory was opened
:attribFile metadata (permissions, timestamps, etc.) changed
:ignoredThe watch was removed (directory deleted or unmounted)
:isdirIncluded 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

start_watcher(path, mask, pid \\ self())

@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 :all to subscribe to every event type.
  • pid — The process to deliver events to. Defaults to self().

Returns

  • {:ok, ref} — A reference to the watcher resource. Pass this to stop_watcher/1 when 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)

stop_watcher(ref)

@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 by start_watcher/2,3.

Returns

:stopped

Examples

{:ok, ref} = Flier.Inotify.start_watcher("/tmp", [:create])
:stopped = Flier.Inotify.stop_watcher(ref)

stream(path, mask \\ [:create, :modify, :delete, :moved_from, :moved_to, :access, :close_write, :close_nowrite, :open, :attrib, :ignored, :isdir])

@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)