zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

fs_windows.ex (5072B)


      1 require Logger
      2 
      3 defmodule FileSystem.Backends.FSWindows do
      4   @moduledoc """
      5   This file is a fork from https://github.com/synrc/fs.
      6   FileSysetm backend for windows, a GenServer receive data from Port, parse event
      7   and send it to the worker process.
      8   Need binary executable file packaged in to use this backend.
      9 
     10   ## Backend Options
     11 
     12     * `:recursive` (bool, default: true), monitor directories and their contents recursively
     13 
     14   ## Executable File Path
     15 
     16   The default executable file is `inotifywait.exe` in `priv` dir of `:file_system` application, there're two ways to custom it, useful when run `:file_system` with escript.
     17 
     18     * config with `config.exs`
     19       `config :file_system, :fs_windows, executable_file: "YOUR_EXECUTABLE_FILE_PATH"`
     20 
     21     * config with `FILESYSTEM_FSWINDOWS_EXECUTABLE_FILE` os environment
     22       FILESYSTEM_FSWINDOWS_EXECUTABLE_FILE=YOUR_EXECUTABLE_FILE_PATH
     23   """
     24 
     25   use GenServer
     26   @behaviour FileSystem.Backend
     27   @sep_char <<1>>
     28 
     29   @default_exec_file "inotifywait.exe"
     30 
     31   def bootstrap do
     32     exec_file = executable_path()
     33     if not is_nil(exec_file) and File.exists?(exec_file) do
     34       :ok
     35     else
     36       Logger.error "Can't find executable `inotifywait.exe`"
     37       {:error, :fs_windows_bootstrap_error}
     38     end
     39   end
     40 
     41   def supported_systems do
     42     [{:win32, :nt}]
     43   end
     44 
     45   def known_events do
     46     [:created, :modified, :removed, :renamed, :undefined]
     47   end
     48 
     49   defp executable_path do
     50     executable_path(:system_env) || executable_path(:config) || executable_path(:system_path) || executable_path(:priv)
     51   end
     52 
     53   defp executable_path(:config) do
     54     Application.get_env(:file_system, :fs_windows)[:executable_file]
     55   end
     56 
     57   defp executable_path(:system_env) do
     58     System.get_env("FILESYSTEM_FSMWINDOWS_EXECUTABLE_FILE")
     59   end
     60 
     61   defp executable_path(:system_path) do
     62     System.find_executable(@default_exec_file)
     63   end
     64 
     65   defp executable_path(:priv) do
     66     case :code.priv_dir(:file_system) do
     67       {:error, _} ->
     68         Logger.error "`priv` dir for `:file_system` application is not avalible in current runtime, appoint executable file with `config.exs` or `FILESYSTEM_FSWINDOWS_EXECUTABLE_FILE` env."
     69         nil
     70       dir when is_list(dir) ->
     71         Path.join(dir, @default_exec_file)
     72     end
     73   end
     74 
     75   def parse_options(options) do
     76     case Keyword.pop(options, :dirs) do
     77       {nil, _} ->
     78         Logger.error "required argument `dirs` is missing"
     79         {:error, :missing_dirs_argument}
     80       {dirs, rest} ->
     81         format = ["%w", "%e", "%f"] |> Enum.join(@sep_char) |> to_charlist
     82         args = [
     83           '--format', format, '--quiet', '-m', '-r'
     84           | dirs |> Enum.map(&Path.absname/1) |> Enum.map(&to_charlist/1)
     85         ]
     86         parse_options(rest, args)
     87     end
     88   end
     89 
     90   defp parse_options([], result), do: {:ok, result}
     91   defp parse_options([{:recursive, true} | t], result) do
     92     parse_options(t, result)
     93   end
     94   defp parse_options([{:recursive, false} | t], result) do
     95     parse_options(t, result -- ['-r'])
     96   end
     97   defp parse_options([{:recursive, value} | t], result) do
     98     Logger.error "unknown value `#{inspect value}` for recursive, ignore"
     99     parse_options(t, result)
    100   end
    101   defp parse_options([h | t], result) do
    102     Logger.error "unknown option `#{inspect h}`, ignore"
    103     parse_options(t, result)
    104   end
    105 
    106   def start_link(args) do
    107     GenServer.start_link(__MODULE__, args, [])
    108   end
    109 
    110   def init(args) do
    111     {worker_pid, rest} = Keyword.pop(args, :worker_pid)
    112     case parse_options(rest) do
    113       {:ok, port_args} ->
    114         port = Port.open(
    115           {:spawn_executable, to_charlist(executable_path())},
    116           [:stream, :exit_status, {:line, 16384}, {:args, port_args}, {:cd, System.tmp_dir!()}]
    117         )
    118         Process.link(port)
    119         Process.flag(:trap_exit, true)
    120         {:ok, %{port: port, worker_pid: worker_pid}}
    121       {:error, _} ->
    122         :ignore
    123     end
    124   end
    125 
    126   def handle_info({port, {:data, {:eol, line}}}, %{port: port}=state) do
    127     {file_path, events} = line |> parse_line
    128     send(state.worker_pid, {:backend_file_event, self(), {file_path, events}})
    129     {:noreply, state}
    130   end
    131 
    132   def handle_info({port, {:exit_status, _}}, %{port: port}=state) do
    133     send(state.worker_pid, {:backend_file_event, self(), :stop})
    134     {:stop, :normal, state}
    135   end
    136 
    137   def handle_info({:EXIT, port, _reason}, %{port: port}=state) do
    138     send(state.worker_pid, {:backend_file_event, self(), :stop})
    139     {:stop, :normal, state}
    140   end
    141 
    142   def handle_info(_, state) do
    143     {:noreply, state}
    144   end
    145 
    146   def parse_line(line) do
    147     {path, flags} =
    148       case line |> to_string |> String.split(@sep_char, trim: true) do
    149         [dir, flags, file] -> {Enum.join([dir, file], "\\"), flags}
    150         [path, flags]      -> {path, flags}
    151       end
    152     {path |> Path.split() |> Path.join(), flags |> String.split(",") |> Enum.map(&convert_flag/1)}
    153   end
    154 
    155   defp convert_flag("CREATE"),   do: :created
    156   defp convert_flag("MODIFY"),   do: :modified
    157   defp convert_flag("DELETE"),   do: :removed
    158   defp convert_flag("MOVED_TO"), do: :renamed
    159   defp convert_flag(_),          do: :undefined
    160 end