-module(olive@watcher). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -define(FILEPATH, "src/olive/watcher.gleam"). -export([start/2]). -export_type([watcher_error/0, change/0, message/0, state/0, internal_msg/0, watch_events/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " The `watcher` module watches a list of dir and triggers a `FilesChanged` message\n" " to be handled by calling code.\n" ). -type watcher_error() :: no_file_watcher_supported_for_os | {no_file_watcher_installed, gleam@dynamic:dynamic_()}. -type change() :: {source_change, binary()} | {priv_change, binary()}. -type message() :: {files_changed, list(change())}. -type state() :: {state, gleam@option:option(gleam@erlang@process:timer()), integer(), gleam@erlang@process:subject(message()), gleam@erlang@process:subject(internal_msg()), gleam@set:set(change())}. -opaque internal_msg() :: {trigger_files_changed, binary(), olive@config:directory()} | trigger_watcher | ignore_changes. -type watch_events() :: updated_file | other_events. -file("src/olive/watcher.gleam", 78). -spec check_watcher_install() -> {ok, nil} | {error, binary()}. check_watcher_install() -> case olive_ffi:check_watcher_installed() of {error, no_file_watcher_supported_for_os} -> {error, <<"No file watcher supported for your OS"/utf8>>}; {error, {no_file_watcher_installed, Watcher}} -> {error, <<"No file watcher installed, please install: "/utf8, (gleam@string:inspect(Watcher))/binary>>}; {ok, _} -> {ok, nil} end. -file("src/olive/watcher.gleam", 139). -spec maybe_cancel_timer(gleam@option:option(gleam@erlang@process:timer())) -> nil. maybe_cancel_timer(Timer) -> case Timer of none -> nil; {some, Timer@1} -> gleam@erlang@process:cancel_timer(Timer@1), nil end. -file("src/olive/watcher.gleam", 104). -spec do_loop(state(), internal_msg()) -> gleam@otp@actor:next(state(), any()). do_loop(State, Msg) -> case Msg of ignore_changes -> gleam@otp@actor:continue(State); trigger_watcher -> gleam@erlang@process:send( erlang:element(4, State), {files_changed, gleam@set:to_list(erlang:element(6, State))} ), gleam@otp@actor:continue( begin _record = State, {state, none, erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record), gleam@set:new()} end ); {trigger_files_changed, File_name, Dir} -> maybe_cancel_timer(erlang:element(2, State)), New_change = case Dir of {source_directory, _} -> {source_change, File_name}; {priv_directory, _} -> {priv_change, File_name} end, Current_changes = gleam@set:insert( erlang:element(6, State), New_change ), Timer = gleam@erlang@process:send_after( erlang:element(5, State), erlang:element(3, State), trigger_watcher ), gleam@otp@actor:continue( begin _record@1 = State, {state, {some, Timer}, erlang:element(3, _record@1), erlang:element(4, _record@1), erlang:element(5, _record@1), Current_changes} end ) end. -file("src/olive/watcher.gleam", 169). -spec listen_directory_watcher( gleam@erlang@process:selector(internal_msg()), gleam@erlang@process:subject(internal_msg()) ) -> any(). listen_directory_watcher(Selector, Subject) -> Msg = gleam_erlang_ffi:select(Selector), gleam@erlang@process:send(Subject, Msg), listen_directory_watcher(Selector, Subject). -file("src/olive/watcher.gleam", 229). -spec erlang_string_to_string_decoder() -> gleam@dynamic@decode:decoder(binary()). erlang_string_to_string_decoder() -> gleam@dynamic@decode:new_primitive_decoder( <<"ErlangString"/utf8>>, fun(Data) -> _pipe = olive_ffi:coerce(Data), _pipe@1 = unicode:characters_to_binary(_pipe), {ok, _pipe@1} end ). -file("src/olive/watcher.gleam", 236). ?DOC(" Converts an atom to an event\n"). -spec atom_to_watch_events() -> gleam@dynamic@decode:decoder(watch_events()). atom_to_watch_events() -> Modified = erlang:binary_to_atom(<<"modified"/utf8>>), Renamed = erlang:binary_to_atom(<<"renamed"/utf8>>), gleam@dynamic@decode:new_primitive_decoder( <<"Atom"/utf8>>, fun(Data) -> case gleam_erlang_ffi:identity(Data) of Ev when Ev =:= Modified -> {ok, updated_file}; Ev@1 when Ev@1 =:= Renamed -> {ok, updated_file}; _ -> {ok, other_events} end end ). -file("src/olive/watcher.gleam", 178). -spec watch_decoder( olive@logging:logger(), olive@config:directory(), gleam@dynamic:dynamic_() ) -> internal_msg(). watch_decoder(Logger, Dir, Msg) -> Decoder = begin gleam@dynamic@decode:subfield( [2, 0], erlang_string_to_string_decoder(), fun(File_name) -> gleam@dynamic@decode:subfield( [2, 1], gleam@dynamic@decode:list(atom_to_watch_events()), fun(Events) -> gleam@dynamic@decode:success({File_name, Events}) end ) end ) end, case {gleam@dynamic@decode:run(Msg, Decoder), Dir} of {{ok, {File_name@1, Events@1}}, {source_directory, _}} -> case {filepath:extension(File_name@1), gleam@list:contains(Events@1, updated_file)} of {{ok, <<"gleam"/utf8>>}, true} -> {trigger_files_changed, File_name@1, Dir}; {_, _} -> ignore_changes end; {{ok, {File_name@2, Events@2}}, {priv_directory, _}} -> case gleam@list:contains(Events@2, updated_file) of true -> {trigger_files_changed, File_name@2, Dir}; false -> ignore_changes end; {{error, Decode_errors}, _} -> Msg@1 = gleam@list:map( Decode_errors, fun(Error) -> {decode_error, Expected, Found, Path} = Error, <<<<<<<<<<"Expected "/utf8, Expected/binary>>/binary, " at ["/utf8>>/binary, (gleam@string:join(Path, <<","/utf8>>))/binary>>/binary, "] but found "/utf8>>/binary, Found/binary>> end ), olive@logging:error( Logger, <<"Error occured while watching files:\n"/utf8, (gleam@string:join(Msg@1, <<"\n"/utf8>>))/binary>> ), ignore_changes end. -file("src/olive/watcher.gleam", 149). -spec start_directory_watcher( olive@logging:logger(), gleam@erlang@process:subject(internal_msg()), olive@config:directory() ) -> gleam@erlang@process:pid_(). start_directory_watcher(Logger, Subject, Dir) -> proc_lib:spawn_link( fun() -> Atom = erlang:binary_to_atom( <<"fs_watcher_"/utf8, (erlang:element(2, Dir))/binary>> ), case fs:start_link(Atom, erlang:element(2, Dir)) of {ok, _} -> nil; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"olive/watcher"/utf8>>, function => <<"start_directory_watcher"/utf8>>, line => 159, value => _assert_fail, start => 4247, 'end' => 4295, pattern_start => 4258, pattern_end => 4263}) end, fs:subscribe(Atom), Selector = begin _pipe = gleam_erlang_ffi:new_selector(), gleam@erlang@process:select_other( _pipe, fun(_capture) -> watch_decoder(Logger, Dir, _capture) end ) end, listen_directory_watcher(Selector, Subject) end ). -file("src/olive/watcher.gleam", 90). -spec start_directory_watchers( olive@config:config(), gleam@erlang@process:subject(internal_msg()) ) -> nil. start_directory_watchers(Config, Subject) -> _pipe = erlang:element(8, Config), gleam@list:each( _pipe, fun(_capture) -> start_directory_watcher( erlang:element(2, Config), Subject, _capture ) end ). -file("src/olive/watcher.gleam", 51). -spec start(olive@config:config(), gleam@erlang@process:subject(message())) -> {ok, gleam@otp@actor:started(nil)} | {error, gleam@otp@actor:start_error()}. start(Config, Watch_subject) -> _pipe@2 = gleam@otp@actor:new_with_initialiser( 5000, fun(_) -> case check_watcher_install() of {error, Err} -> {error, Err}; {ok, _} -> Subject = gleam@erlang@process:new_subject(), start_directory_watchers(Config, Subject), Selector = begin _pipe = gleam_erlang_ffi:new_selector(), gleam@erlang@process:select(_pipe, Subject) end, {ok, begin _pipe@1 = gleam@otp@actor:initialised( {state, none, erlang:element(9, Config), Watch_subject, Subject, gleam@set:new()} ), gleam@otp@actor:selecting(_pipe@1, Selector) end} end end ), _pipe@3 = gleam@otp@actor:on_message(_pipe@2, fun do_loop/2), gleam@otp@actor:start(_pipe@3).