Current section

Files

Jump to
fwatch lib fwatch.ex
Raw

lib/fwatch.ex

defmodule Fwatch do
use Application
use GenServer
@type pattern :: String.t | Regex.t
@typedoc """
Callback for file event.
First argument is a path and second one is a list of events.
See [Lists Events from Backend](https://github.com/synrc/fs#list-events-from-backend) for details of events.
"""
@type callback :: (String.t, [atom] -> any)
# files :: {:file, [any], callback}
# dirs :: {:dir, [any], callback}
# Application callback
def start(_type, _args) do
GenServer.start_link(__MODULE__, [], name: :fwatch)
end
# GenServer callbacks
def init(state) do
:ok = :fs.subscribe
{:ok, state}
end
def handle_cast({:add_target, item}, targets) do
{:noreply, [item|targets]}
end
def handle_info({_pid, {:fs, :file_event}, {path, events}}, targets) do
handle_file_event(targets, to_string(path), events)
{:noreply, targets}
end
# Fwatch functions
@doc """
Registers callback for given files.
"""
@spec watch_file([pattern], callback) :: any
def watch_file(files, callback) when is_list(files) do
GenServer.cast(:fwatch, {:add_target, {:file, files, callback}})
end
@spec watch_file(pattern, callback) :: any
def watch_file(file, callback) do
watch_file([file], callback)
end
@doc """
Registers the callback for given dirs.
"""
@spec watch_dir([pattern], callback) :: any
def watch_dir(dirs, callback) when is_list(dirs) do
GenServer.cast(:fwatch, {:add_target, {:dir, dirs, callback}})
end
@spec watch_dir(pattern, callback) :: any
def watch_dir(dir, callback) do
watch_dir([dir], callback)
end
defp handle_file_event(targets, path, events) do
Enum.map(targets, fn
{:file, files, callback} ->
if Enum.any?(files, &(match_file?(path, &1))) do
callback.(path, events)
end
{:dir, dirs, callback} ->
if Enum.any?(dirs, &(match_dir?(path, &1))) do
callback.(path, events)
end
end)
end
defp match_file?(file, pattern) when is_binary(pattern), do: file == expath(pattern)
defp match_file?(file, pattern) do
cond do
Regex.regex?(pattern) -> Regex.match?(pattern, file)
true -> false
end
end
defp match_dir?(file, pattern) when is_binary(pattern), do: contain_path?(Path.dirname(file), expath(pattern))
defp match_dir?(file, pattern) do
cond do
Regex.regex?(pattern) -> Regex.match?(pattern, Path.dirname(file))
true -> false
end
end
defp contain_path?(path, target_path) do
path1 = Path.split(path)
path2 = Path.split(target_path)
length = length(path2)
cond do
length(path1) < length -> false
true -> Enum.slice(path1, 0, length) == path2
end
end
defp expath(path), do: Path.expand(path, System.cwd!())
end