Skip to main content

lib/fs_notify.ex

defmodule FsNotify do
  @moduledoc """
  Filesystem watcher backed by notify-rs (via Rustler).

  Watch one or more paths and receive each filesystem event in your process as
  `{:fs_notify_event, %FsNotify.Event{}}` (mirroring notify-rs: one message per
  event, carrying all affected `paths`).

      {:ok, ref} = FsNotify.watch("/some/dir", recursive: true)
      {:ok, ref} = FsNotify.watch(["/dir/a", "/dir/b"])
      receive do
        {:fs_notify_event, %FsNotify.Event{kind: kind, paths: paths}} -> handle(kind, paths)
      end
      FsNotify.unwatch(ref)

  The watch is tied to the subscriber process: it stops automatically when that
  process dies (and also if `ref` is garbage-collected). `unwatch/1` stops it
  eagerly.
  """

  @doc """
  Start watching `path` (a path or a list of paths).

  Returns `{:ok, ref}`; keep `ref` for as long as you want to watch. Options:

    * `:recursive` (default `true`) — watch subdirectories
    * `:subscriber` (default: the calling process) — process that receives events
    * `:debounce` (default `0`) — coalesce events over this many ms (notify-rs
      debouncer); `0` disables debouncing
    * `:backend` (default `:recommended`) — `:recommended` (the OS-native
      backend) or `:poll` (portable polling watcher)
    * `:poll_interval` (default `0`) — poll interval in ms for the `:poll`
      backend; `0` uses notify's default

  ## Examples

      {:ok, ref} = FsNotify.watch("/tmp/dir", recursive: true)
      {:ok, ref} = FsNotify.watch(["/tmp/a", "/tmp/b"], debounce: 100)
      {:ok, ref} = FsNotify.watch("/tmp/dir", backend: :poll, poll_interval: 200)
  """
  @spec watch(binary() | [binary()], keyword()) :: {:ok, reference()} | {:error, binary()}
  def watch(path, opts \\ []) do
    subscriber = Keyword.get(opts, :subscriber, self())

    native_opts = %{
      recursive: Keyword.get(opts, :recursive, true),
      debounce: Keyword.get(opts, :debounce, 0),
      backend: Keyword.get(opts, :backend, :recommended),
      poll_interval: Keyword.get(opts, :poll_interval, 0)
    }

    FsNotify.Native.watch(subscriber, List.wrap(path), native_opts)
  end

  @doc """
  Stop the watcher identified by `ref`.

  ## Examples

      {:ok, ref} = FsNotify.watch("/tmp/dir")
      :ok = FsNotify.unwatch(ref)
  """
  @spec unwatch(reference()) :: :ok
  def unwatch(ref), do: FsNotify.Native.unwatch(ref)
end