PageRenderTime 32ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/src/3.0/group_history.erl

http://github.com/ferd/erlang-history
Erlang | 258 lines | 192 code | 16 blank | 50 comment | 4 complexity | 0339112e2e50d5316c7aa706a6ffcbd0 MD5 | raw file
  1. -module(group_history).
  2. -export([load/0, add/1]).
  3. -define(DEFAULT_HIST_FILE, ".erlang-hist").
  4. -define(DEFAULT_HIST_SIZE, 500).
  5. -define(TABLE, shell_group_hist).
  6. -define(DEFAULT_AUTOSAVE, 500).
  7. -define(DEFAULT_DROP, []).
  8. %% History is node-based, but history of many jobs on a single node
  9. %% are mixed in together.
  10. -record(opts, {hist=true, hist_file, hist_size}).
  11. %%% PUBLIC
  12. %% Loads the shell history from memory. This function should only be
  13. %% called from group:server/3 to inject itself in the previous commands
  14. %% stack.
  15. load() ->
  16. case opts() of
  17. #opts{hist=false} ->
  18. [];
  19. #opts{hist_file=F, hist_size=S} ->
  20. wait_for_kernel_safe_sup(),
  21. %% We cannot repair the table automatically. The current process
  22. %% is handling output and dets repairing a table outputs a message.
  23. %% Due to how the IO protocol works, this leads to a deadlock where
  24. %% we wait to reply to ourselves in some circumstances.
  25. case dets:open_file(?TABLE, [{file,F}, {auto_save, opt(hist_auto_save)}, {repair, false}]) of
  26. {ok, ?TABLE} -> load_history(S);
  27. {error, {needs_repair, F}} -> repair_table(F);
  28. {error, Error} ->
  29. %% We can't recover from this.
  30. unknown_error_warning(Error),
  31. application:set_env(kernel, hist, false),
  32. []
  33. end
  34. end.
  35. load_history(S) ->
  36. case dets:lookup(?TABLE, ct) of
  37. [] -> % 1st run
  38. dets:insert(?TABLE, {ct,1}),
  39. load(1, 1-S);
  40. [{ct,OldCt}] ->
  41. load(OldCt, OldCt-S);
  42. {error,{{bad_object,_Reason},TableFile}} ->
  43. corrupt_warning(TableFile),
  44. [];
  45. {error,{bad_object_header,TableFile}} ->
  46. corrupt_warning(TableFile),
  47. []
  48. end.
  49. %% Repairing the table means we need to spawn a new process to do it for us.
  50. %% That process will then try to message the current group leader with mentions
  51. %% of trying to repair the table. We need to absorb these, let the process
  52. %% repair, then close the table in order for us to open it again and finally
  53. %% be free!
  54. repair_table(F) ->
  55. %% Start a process to open and close the table to repair it, different
  56. %% from the current shell process.
  57. R = make_ref(),
  58. S = self(),
  59. spawn(fun() ->
  60. {ok, ?TABLE} = dets:open_file(?TABLE, [{file,F},{repair,force}]),
  61. dets:close(?TABLE),
  62. S ! R
  63. end),
  64. %% Messages from the IO protocol will only need to be received if we
  65. %% currently are the group leader (in this case, the 'user' process).
  66. %% if this is not 'user', we can move on.
  67. case process_info(self(), registered_name) of
  68. {registered_name, user} ->
  69. receive
  70. {io_request,From,ReplyAs,
  71. {put_chars,_Encoding,io_lib,format,
  72. ["dets:"++_, [F, [_|_]]]}} -> From ! {io_reply, ReplyAs, ok}
  73. end;
  74. _ -> ok
  75. end,
  76. %% now we wait for the worker to close the table, telling us it's safe
  77. %% to load it on our own.
  78. receive
  79. R -> ok
  80. end,
  81. load().
  82. %% Adds a given line to the history,
  83. add(Line) -> add(Line, opt(hist)).
  84. %% only add lines that do not match something to drop from history. The behaviour
  85. %% to avoid storing empty or duplicate lines is in fact implemented within
  86. %% group:save_line_buffer/2.
  87. add(Line, true) ->
  88. case lists:member(Line, opt(hist_drop)) of
  89. false ->
  90. case dets:lookup(?TABLE, ct) of
  91. [{ct, Ct}] ->
  92. dets:insert(?TABLE, {Ct, Line}),
  93. dets:delete(?TABLE, Ct-opt(hist_size)),
  94. dets:update_counter(?TABLE, ct, {2,1});
  95. {error,{{bad_object,_Reason},HistFile}} ->
  96. corrupt_warning(HistFile);
  97. {error,{bad_object_header, HistFile}} ->
  98. corrupt_warning(HistFile)
  99. end;
  100. true ->
  101. ok
  102. end;
  103. add(_, false) -> ok.
  104. %%% PRIVATE
  105. %% Gets a short record of vital options when setting things up.
  106. opts() ->
  107. #opts{hist=opt(hist),
  108. hist_file=opt(hist_file),
  109. hist_size=opt(hist_size)}.
  110. %% Defines whether history should be allowed at all.
  111. opt(hist) ->
  112. case application:get_env(kernel, hist) of
  113. {ok, true} -> true;
  114. {ok, false} -> false;
  115. _Default -> true
  116. end;
  117. %% Defines what the base name and path of the history file will be.
  118. %% By default, the file sits in the user's home directory as
  119. %% '.erlang-history.'. All filenames get the node name apended
  120. %% to them.
  121. opt(hist_file) ->
  122. case {opt(hist), application:get_env(kernel, hist_file)} of
  123. {true, undefined} ->
  124. case init:get_argument(home) of
  125. {ok, [[Home]]} ->
  126. Name = filename:join([Home, ?DEFAULT_HIST_FILE]),
  127. application:set_env(kernel, hist_file, Name),
  128. opt(hist_file);
  129. _ ->
  130. error_logger:error_msg("No place found to save shell history"),
  131. erlang:error(badarg)
  132. end;
  133. {true, {ok, Val}} -> Val++"."++atom_to_list(node());
  134. {false, _} -> undefined
  135. end;
  136. %% Defines how many commands should be kept in memory. Default is 500.
  137. opt(hist_size) ->
  138. case {opt(hist), application:get_env(kernel, hist_size)} of
  139. {true, undefined} ->
  140. application:set_env(kernel, hist_size, ?DEFAULT_HIST_SIZE),
  141. ?DEFAULT_HIST_SIZE;
  142. {true, {ok,Val}} -> Val;
  143. {false, _} -> undefined
  144. end;
  145. %% This handles the delay of auto-saving of DETS. This isn't public
  146. %% and the value is currently very short so that shell crashes do not
  147. %% corrupt the file history.
  148. opt(hist_auto_save) ->
  149. case application:get_env(kernel, hist_auto_save) of
  150. undefined ->
  151. application:set_env(kernel, hist_auto_save, ?DEFAULT_AUTOSAVE),
  152. ?DEFAULT_AUTOSAVE;
  153. {ok, V} -> V
  154. end;
  155. %% Allows to define a list of strings that should not be kept in history.
  156. %% one example would be ["q().","init:stop().","halt()."] if you do not
  157. %% want to keep ways to shut down the shell.
  158. opt(hist_drop) ->
  159. case application:get_env(kernel, hist_drop) of
  160. undefined ->
  161. application:set_env(kernel, hist_drop, ?DEFAULT_DROP),
  162. ?DEFAULT_DROP;
  163. {ok, V} when is_list(V) -> [Ln++"\n" || Ln <- V];
  164. {ok, _} -> ?DEFAULT_DROP
  165. end.
  166. %% Because loading the shell happens really damn early, processes we depend on
  167. %% might not be there yet. Luckily, the load function is called from the shell
  168. %% after a new process has been spawned, so we can block in here
  169. wait_for_kernel_safe_sup() ->
  170. case whereis(kernel_safe_sup) of
  171. undefined ->
  172. timer:sleep(50),
  173. wait_for_kernel_safe_sup();
  174. _ -> ok
  175. end.
  176. %% Load all the elements previously saved in history
  177. load(N, _) when N =< 0 -> [];
  178. load(N, M) when N =< M -> truncate(M), [];
  179. load(N, M) ->
  180. case dets:lookup(?TABLE, N-1) of
  181. %case dets:lookup(?TABLE, N-1) of
  182. [] -> []; % nothing in history
  183. [{_,Entry}] -> [Entry | load(N-1,M)];
  184. {error, {{bad_object,_Reason},_TableFile}} ->
  185. corrupt_entry_warning(),
  186. load(N-1,M);
  187. {error, {bad_object_header,_TableFile}} ->
  188. corrupt_entry_warning(),
  189. load(N-1,M)
  190. end.
  191. %% If the history size was changed between two shell sessions, we have to
  192. %% truncate the old history.
  193. truncate(N) when N =< 0 -> ok;
  194. truncate(N) ->
  195. dets:delete(?TABLE, N),
  196. truncate(N-1).
  197. corrupt_entry_warning() ->
  198. case get('$#erlang-history-entry-corrupted') of
  199. undefined ->
  200. io:format(standard_error,
  201. "An erlang-history entry was corrupted by DETS. "
  202. "Skipping entry...~n", []),
  203. put('$#erlang-history-entry-corrupted',true),
  204. ok;
  205. true ->
  206. ok
  207. end.
  208. corrupt_warning(TableFile) ->
  209. case get('$#erlang-history-corrupted') of
  210. undefined ->
  211. io:format(standard_error,
  212. "The erlang-history file at ~s was corrupted by DETS. "
  213. "An attempt to repair the file will be made, but if it fails, "
  214. "history will be ignored for the rest of this session. If the "
  215. "problem persists, please delete the history file "
  216. "(and maybe submit it with a bug report).~n", [TableFile]),
  217. dets:close(?TABLE),
  218. dets:open_file(?TABLE, [{file,(opts())#opts.hist_file},{auto_save, opt(hist_auto_save)},{repair,force}]),
  219. put('$#erlang-history-corrupted',true),
  220. ok;
  221. true ->
  222. ok
  223. end.
  224. unknown_error_warning(Error) ->
  225. case get('$#erlang-history-unknown-error') of
  226. undefined ->
  227. %% Don't display if we're user -- children will send it on our
  228. %% behalf
  229. case process_info(self(), registered_name) of
  230. {registered_name, user} ->
  231. ok;
  232. _ ->
  233. io:format(standard_error,
  234. "The erlang-history file could not be opened, and "
  235. "history will be ignored for the rest of the session.~n"
  236. "The error received was: ~p~n", [Error])
  237. end,
  238. put('$#erlang-history-unknown-error',true),
  239. ok;
  240. true ->
  241. ok
  242. end.