active.erl 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. -module(active).
  2. -behaviour(gen_server).
  3. -export([
  4. start_link/0,
  5. init/1,
  6. handle_call/3,
  7. handle_cast/2,
  8. handle_info/2,
  9. terminate/2,
  10. code_change/3
  11. ]).
  12. -record(state, {last, root}).
  13. start_link() ->
  14. gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
  15. init([]) ->
  16. case {application:get_env(fs, backwards_compatible, false),
  17. application:get_env(active, listen_paths, [])} of
  18. %{true, []} -> fs:subscribe();
  19. {true, _} ->
  20. fs:subscribe();
  21. {false, []} ->
  22. fs:start_link(default_fs),
  23. fs:subscribe();
  24. {false, Paths} ->
  25. lists:foldl(fun(Path, ok)->
  26. Name = erlang:list_to_atom(Path),
  27. fs:start_link(Name, Path),
  28. fs:subscribe(Name),
  29. ok
  30. end, ok, Paths)
  31. end,
  32. erlang:process_flag(priority, low),
  33. gen_server:cast(self(), recompile_all),
  34. {ok, #state{last=fresh, root=fs:path()}}.
  35. handle_call(_Request, _From, State) ->
  36. {reply, ok, State}.
  37. handle_cast(recompile_all, State) ->
  38. compile(top(), ["all"], []),
  39. {noreply, State}.
  40. handle_info({_Pid, {fs, file_event}, {Path, Flags}}, #state{root = Root} = State) ->
  41. Cur = path_shorten(filename:split(Root)),
  42. P = filename:split(Path),
  43. Result = case lists:prefix(Cur, P) of
  44. true ->
  45. path_event(P -- Cur, Flags, State, Path);
  46. false -> ok
  47. end,
  48. {noreply, State#state{last = {event, Path, Flags, Result}} };
  49. handle_info({load_ebin, Atom}, State) ->
  50. do_load_ebin(Atom),
  51. {noreply, State#state{last = {do_load_ebin, Atom}}};
  52. handle_info(Info, State) ->
  53. {noreply, State#state{last = {unk, Info}}}.
  54. terminate(_Reason, _State) -> ok.
  55. code_change(_OldVsn, State, _Extra) -> {ok, State}.
  56. path_event(C, [E|_], _, Path) when E =:= created; E =:= modified; E =:= renamed ->
  57. case path_filter(C) of
  58. true -> maybe_otp(C, Path);
  59. false -> ignore
  60. end;
  61. path_event(C, [_E|Events], State, Path) ->
  62. path_event(C, Events, State, Path);
  63. path_event(_, [], _, _) -> done.
  64. maybe_otp(C, Path) ->
  65. case application:get_env(active, handler) of
  66. {ok, {M, F}} -> M:F(C);
  67. _ -> otp(C, Path)
  68. end.
  69. otp([Some, App|Rest], Path) when Some == "deps" orelse Some == "apps" ->
  70. maybe_app(App, Rest, Path);
  71. otp([Some|Rest], Path) ->
  72. maybe_app(top(), [Some|Rest], Path);
  73. otp(_, _) ->
  74. ok.
  75. maybe_app(App, SplitPath, Path) ->
  76. EnabledApps = application:get_env(active, apps, []),
  77. case EnabledApps of
  78. [] ->
  79. app(App, SplitPath, Path);
  80. {ok, L} when erlang:is_list(L) ->
  81. AppAtom = erlang:list_to_atom(App),
  82. case lists:member(AppAtom, L) of
  83. true ->
  84. app(App, SplitPath, Path);
  85. false -> skip
  86. end
  87. end.
  88. app(App, ["ebin", Module|_], _) -> load_ebin(App, Module);
  89. app(_App, ["priv", "fdlink" ++ _], _) -> skip;
  90. app(_App, ["priv", "mac" ++ _], _) -> skip;
  91. app(_App, ["priv", "windows" ++ _], _) -> skip;
  92. app(_App, ["priv", "linux" ++ _], _) -> skip;
  93. app(App, ["priv", "static"|_Rest], Path) ->
  94. compile_skip(compile_on_static, App, _Rest, Path);
  95. app(App, ["priv", "templates"|Rest], Path) ->
  96. compile_skip(compile_on_templates, App, Rest, Path);
  97. app(App, ["priv"|Rest], Path) ->
  98. compile_skip(compile_on_priv, App, Rest, Path);
  99. app(App, ["include"|Rest], Path) ->
  100. compile(App, Rest, Path);
  101. app(App, ["src"|Rest], Path) ->
  102. compile(App, Rest, Path);
  103. app(App, ["lib"|Rest], Path) ->
  104. compile(App, Rest, Path);
  105. app(_, _, _)-> ok.
  106. compile_skip(Key, App, Rest, Path) ->
  107. case application:get_env(active, Key, false) of
  108. false -> skip;
  109. _ -> compile(App, Rest, Path)
  110. end.
  111. top() -> lists:last(filename:split(fs:path())).
  112. compile(_App, _, []) -> ok;
  113. compile(_App, [], _Path) -> ok;
  114. compile(App, Rest, Path) ->
  115. case lists:last(Rest) of
  116. ".#" ++ _ -> skip;
  117. _ ->
  118. try
  119. erlang:put(App, updated),
  120. {M, F} = application:get_env(active, compile, {mad_compile, compile}),
  121. M:F(App, Path)
  122. catch E:R ->
  123. io:format("~p", [erlang:get_stacktrace()]),
  124. io:format("Catch: ~p:~p", [E, R])
  125. end
  126. end.
  127. load_ebin(_App, EName) ->
  128. case lists:reverse(EName) of
  129. "maeb." ++ Tail ->
  130. Name = lists:reverse(Tail),
  131. LoadRes = do_load_ebin(erlang:list_to_atom(lists:flatten(Name))),
  132. io:format("Active: module loaded: ~p~n\n\r", [LoadRes]),
  133. active_events:notify_reload(LoadRes);
  134. "#aeb." ++ _ -> ok;
  135. _ ->
  136. io:format("Active: unknown BEAM file: ~p", [EName]), ok
  137. end.
  138. do_load_ebin(Module) ->
  139. IsLoaded = case code:is_loaded(Module) of
  140. {file, _} ->
  141. true;
  142. false ->
  143. false
  144. end,
  145. {Module, Binary, Filename} = code:get_object_code(Module),
  146. case code:load_binary(Module, Filename, Binary) of
  147. {module, Module} when IsLoaded->
  148. {reloaded, Module};
  149. {module, Module} when not IsLoaded ->
  150. {loaded_new, Module};
  151. {error, Reason} ->
  152. {load_error, Module, Reason}
  153. end.
  154. %% ["a", "b", ".."] -> ["a"]
  155. path_shorten(Coms) ->
  156. path_shorten_r(lists:reverse(Coms), [], 0).
  157. path_shorten_r([".."|Rest], Acc, Count) ->
  158. path_shorten_r(Rest, Acc, Count + 1);
  159. path_shorten_r(["."|Rest], Acc, Count) ->
  160. path_shorten_r(Rest, Acc, Count);
  161. path_shorten_r([_C|Rest], Acc, Count) when Count > 0 ->
  162. path_shorten_r(Rest, Acc, Count - 1);
  163. path_shorten_r([C|Rest], Acc, 0) ->
  164. path_shorten_r(Rest, [C|Acc], 0);
  165. path_shorten_r([], Acc, _) ->
  166. Acc.
  167. path_filter(L) ->
  168. not lists:any(fun(E) -> not path_filter_dir(E) end, L)
  169. andalso path_filter_file( lists:last(L) )
  170. andalso path_filter_ext( ext(L) ).
  171. ext(L) -> filename:extension( lists:last(L) ).
  172. path_filter_dir(".git") -> false;
  173. path_filter_dir(".hg") -> false;
  174. path_filter_dir(".svn") -> false;
  175. path_filter_dir("CVS") -> false;
  176. %path_filter_dir("ebin") -> false;
  177. path_filter_dir("build") -> false;
  178. path_filter_dir("_build") -> false;
  179. path_filter_dir("log") -> false;
  180. path_filter_dir("node_modules") -> false;
  181. path_filter_dir(_) -> true.
  182. path_filter_file(".rebarinfo") -> false;
  183. path_filter_file("LICENSE") -> false;
  184. path_filter_file("4913 (deleted)") -> false; %% vim magical file
  185. path_filter_file("4913") -> false;
  186. path_filter_file(_) -> true.
  187. path_filter_ext(".app") -> false;
  188. path_filter_ext(".jpg") -> false;
  189. path_filter_ext(".png") -> false;
  190. path_filter_ext(".gif") -> false;
  191. path_filter_ext(_) -> true.