diff --git a/lib/file_system.ex b/lib/file_system.ex index c520e48cbe3d3e91086f24ef2fa311a677297755..419ad230f72d589eae26d1e11051db46ab4dc374 100644 --- a/lib/file_system.ex +++ b/lib/file_system.ex @@ -1,15 +1,51 @@ defmodule FileSystem do - defmacro __using__(options) do - quote do - @file_system_module_options unquote(options) - @before_compile FileSystem.ModuleApi - end - end + @moduledoc File.read!("README.md") + + @doc """ + ## Options + + * `:dirs` ([string], requires), the dir list to monitor + + * `:backend` (atom, optional), default backends: `:fs_mac` + for `macos`, `:fs_inotify` for `linux` and `freebsd`, + `:fs_windows` for `windows` + + * `:listener_extra_args` (string, optional), extra args for + port backend. + + * `:name` (atom, optional), `name` can be used to subscribe as + the same as pid when the `name` is given. The `name` should + be the name of worker process. + + ## Example - def start_link(args) do - FileSystem.Worker.start_link(args) + Simple usage: + + iex> {:ok, pid} = FileSystem.start_link(dirs: ["/tmp/fs"]) + iex> FileSystem.subscribe(pid) + + Get nstant notifications on file changes for Mac OS X: + + iex> FileSystem.start_link(dirs: ["/path/to/some/files"], listener_extra_args: "--latency=0.0") + + Named monitir with specialty backend: + + iex> FileSystem.start_link(backend: :fs_mac, dirs: ["/tmp/fs"], name: :worker) + iex> FileSystem.subscribe(:worker) + """ + @spec start_link(Keyword.t) :: {:ok, pid} + def start_link(options) do + FileSystem.Worker.start_link(options) end + @doc """ + Regester current process as a subscriber of file_system worker. + The pid you subscribed from will now receive messages like + + {:file_event, worker_pid, {file_path, events}} + {:file_event, worker_pid, :stop} + """ + @spec subscribe(pid() | atom()) :: :ok def subscribe(pid) do GenServer.call(pid, :subscribe) end diff --git a/lib/file_system/backend.ex b/lib/file_system/backend.ex index 14204d4cf206377b8d344654102112048e13550b..293a09b6d34965dfd1cba6f64be5080e38d7f46c 100644 --- a/lib/file_system/backend.ex +++ b/lib/file_system/backend.ex @@ -1,10 +1,22 @@ require Logger defmodule FileSystem.Backend do + @moduledoc """ + FileSystem Backend Behaviour. + """ + @callback bootstrap() :: :ok | {:error, atom()} @callback supported_systems() :: [{atom(), atom()}] @callback known_events() :: [atom()] + @doc """ + Get and validate backend module, return `{:ok, backend_module}` when success and + return `{:error, reason}` when fail. + When `nil` is given, will return default backend by os. + When a custom module is given, make sure `start_link/1`, `bootstrap/0` and + `supported_system/0` are defnied. + """ + @spec backend(:atom) :: {:ok, atom()} | {:error, atom()} def backend(backend) do with {:ok, module} <- backend_module(backend), :ok <- validate_os(backend, module), diff --git a/lib/file_system/backends/fs_inotify.ex b/lib/file_system/backends/fs_inotify.ex index a57c64ac641388b5f7372503bc9790500d50a317..ba518bb4587145664f35fbe4c305925a6b77e4d0 100644 --- a/lib/file_system/backends/fs_inotify.ex +++ b/lib/file_system/backends/fs_inotify.ex @@ -2,6 +2,13 @@ require Logger alias FileSystem.Utils defmodule FileSystem.Backends.FSInotify do + @moduledoc """ + This file is a fork from https://github.com/synrc/fs. + FileSysetm backend for linux and freebsd, a GenServer receive data from Port, parse event + and send it to the worker process. + Need `inotify-tools` installed to use this backend. + """ + use GenServer @behaviour FileSystem.Backend @sep_char <<1>> @@ -24,7 +31,7 @@ defmodule FileSystem.Backends.FSInotify do [:created, :deleted, :renamed, :closed, :modified, :isdir, :attribute, :undefined] end - def find_executable do + defp find_executable do System.find_executable("inotifywait") end diff --git a/lib/file_system/backends/fs_mac.ex b/lib/file_system/backends/fs_mac.ex index aea5429fa584e1f8b6e90e1388e1605290cb34fe..d56c6932def14ad646336a0bf205b0a514a4ff26 100644 --- a/lib/file_system/backends/fs_mac.ex +++ b/lib/file_system/backends/fs_mac.ex @@ -2,6 +2,13 @@ require Logger alias FileSystem.Utils defmodule FileSystem.Backends.FSMac do + @moduledoc """ + This file is a fork from https://github.com/synrc/fs. + FileSysetm backend for macos, a GenServer receive data from Port, parse event + and send it to the worker process. + will compile executable the buildin executable file when file the first time it is used. + """ + use GenServer @behaviour FileSystem.Backend @@ -33,10 +40,11 @@ defmodule FileSystem.Backends.FSMac do ] end - def find_executable do + defp find_executable do (:code.priv_dir(:file_system) ++ '/mac_listener') |> to_string end + def start_link(args) do GenServer.start_link(__MODULE__, args, []) end diff --git a/lib/file_system/backends/fs_windows.ex b/lib/file_system/backends/fs_windows.ex index 9f194f3d9435a6e49e1e0ebecff0ee78e118d5ba..5027be3da8483f6c4feac63325fdfaea933a1515 100644 --- a/lib/file_system/backends/fs_windows.ex +++ b/lib/file_system/backends/fs_windows.ex @@ -2,6 +2,13 @@ require Logger alias FileSystem.Utils defmodule FileSystem.Backends.FSWindows do + @moduledoc """ + This file is a fork from https://github.com/synrc/fs. + FileSysetm backend for windows, a GenServer receive data from Port, parse event + and send it to the worker process. + Need binary executable file packaged in to use this backend. + """ + use GenServer @behaviour FileSystem.Backend @sep_char <<1>> @@ -24,7 +31,7 @@ defmodule FileSystem.Backends.FSWindows do [:created, :modified, :removed, :renamed, :undefined] end - def find_executable do + defp find_executable do (:code.priv_dir(:file_system) ++ '/inotifywait.exe') |> to_string end diff --git a/lib/file_system/utils.ex b/lib/file_system/utils.ex index b24eeb9a337e1d8374c42e32f83d5d58bf96306b..f87c900381d34030033a1a4ff06f650ce155df15 100644 --- a/lib/file_system/utils.ex +++ b/lib/file_system/utils.ex @@ -1,14 +1,19 @@ defmodule FileSystem.Utils do + @moduledoc false + + @doc false + @spec format_path(String.t() | [String.t()]) :: [charlist()] def format_path(path) when is_list(path) do for i <- path do i |> Path.absname |> to_charlist end end - def format_path(path) do [path] |> format_path end + @doc false + @spec format_args(nil | String.t() | [String.t()]) :: [charlist()] def format_args(nil), do: [] def format_args(str) when is_binary(str) do str |> String.split |> format_args diff --git a/lib/file_system/worker.ex b/lib/file_system/worker.ex index b66fa9474cdf7762755c5a11dea5bed9b2938daa..fbdee6bbbee51262363e5530c021b83aed2a6415 100644 --- a/lib/file_system/worker.ex +++ b/lib/file_system/worker.ex @@ -1,11 +1,18 @@ defmodule FileSystem.Worker do + @moduledoc """ + FileSystem Worker Process with the backend GenServer, receive events from Port Process + and forward it to subscribers. + """ + use GenServer + @doc false def start_link(args) do {args, opts} = Keyword.split(args, [:backend, :dirs, :listener_extra_args]) GenServer.start_link(__MODULE__, args, opts) end + @doc false def init(args) do case FileSystem.Backend.backend(args[:backend]) do {:ok, backend} -> @@ -16,12 +23,14 @@ defmodule FileSystem.Worker do end end + @doc false def handle_call(:subscribe, {pid, _}, state) do ref = Process.monitor(pid) state = put_in(state, [:subscribers, ref], pid) {:reply, :ok, state} end + @doc false def handle_info({:backend_file_event, backend_pid, file_event}, %{backend_pid: backend_pid}=state) do state.subscribers |> Enum.each(fn {_ref, subscriber_pid} -> send(subscriber_pid, {:file_event, self(), file_event})