/src/support/z_config.erl

https://code.google.com/p/zotonic/ · Erlang · 265 lines · 153 code · 46 blank · 66 comment · 0 complexity · 17a11f0dd28c6e0f128816a36ece9246 MD5 · raw file

  1. %% @author Marc Worrell <marc@worrell.nl>
  2. %% @copyright 2010 Marc Worrell
  3. %% @doc Simple configuration server. Holds and updates the global config in priv/config
  4. %% Copyright 2010 Marc Worrell
  5. %%
  6. %% Licensed under the Apache License, Version 2.0 (the "License");
  7. %% you may not use this file except in compliance with the License.
  8. %% You may obtain a copy of the License at
  9. %%
  10. %% http://www.apache.org/licenses/LICENSE-2.0
  11. %%
  12. %% Unless required by applicable law or agreed to in writing, software
  13. %% distributed under the License is distributed on an "AS IS" BASIS,
  14. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. %% See the License for the specific language governing permissions and
  16. %% limitations under the License.
  17. -module(z_config).
  18. -author("Marc Worrell <marc@worrell.nl>").
  19. -behaviour(gen_server).
  20. % gen_server exports
  21. -export([
  22. start_link/0,
  23. init/1,
  24. handle_call/3,
  25. handle_cast/2,
  26. handle_info/2,
  27. terminate/2,
  28. code_change/3
  29. ]).
  30. % API export
  31. -export([
  32. get/1,
  33. set/2,
  34. get_dirty/1,
  35. set_dirty/2
  36. ]).
  37. -record(state, {config=[]}).
  38. -include_lib("zotonic.hrl").
  39. %%====================================================================
  40. %% API
  41. %%====================================================================
  42. %% @spec start_link() -> {ok,Pid} | ignore | {error,Error}
  43. %% @doc Starts the server
  44. start_link() ->
  45. gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
  46. %% @doc Get value from config file (cached)
  47. get(Key) ->
  48. gen_server:call(?MODULE, {get, Key}).
  49. %% @doc Set value in config file, update cache.
  50. set(Key, Value) ->
  51. gen_server:cast(?MODULE, {set, Key, Value}).
  52. %%====================================================================
  53. %% THE FOLLOWING TWO FUNCTIONS ARE USED ON STARTUP.
  54. %% DO NOT USE ON ANY OTHER MOMENT.
  55. %%====================================================================
  56. %% @doc Dirty read of complete config file
  57. get_dirty(Key) ->
  58. case proplists:get_value(Key, ensure_config()) of
  59. undefined ->
  60. case default(Key) of
  61. {ok, Value} ->
  62. case Key of
  63. password -> write_config(Key, Value);
  64. _ -> nop
  65. end,
  66. Value;
  67. undefined ->
  68. undefined
  69. end;
  70. Value ->
  71. Value
  72. end.
  73. %% @doc Dirty write of config key, updates the config file on disk
  74. %% when the value is changed
  75. set_dirty(Key, Value) ->
  76. Config = ensure_config(),
  77. case proplists:get_value(Key, Config) of
  78. Value -> ok;
  79. _ -> write_config(Key, Value)
  80. end.
  81. %%====================================================================
  82. %% gen_server callbacks
  83. %%====================================================================
  84. %% @spec init(Args) -> {ok, State} |
  85. %% {ok, State, Timeout} |
  86. %% ignore |
  87. %% {stop, Reason}
  88. %% @doc Initiates the server.
  89. init(_Args) ->
  90. {ok, #state{config=ensure_config()}}.
  91. %% @spec handle_call(Request, From, State) -> {reply, Reply, State} |
  92. %% {reply, Reply, State, Timeout} |
  93. %% {noreply, State} |
  94. %% {noreply, State, Timeout} |
  95. %% {stop, Reason, Reply, State} |
  96. %% {stop, Reason, State}
  97. %% @doc Get a value
  98. handle_call({get, Key}, _From, State) ->
  99. handle_get(Key, State);
  100. %% @doc Trap unknown calls
  101. handle_call(Message, _From, State) ->
  102. {stop, {unknown_call, Message}, State}.
  103. %% @doc Set a value
  104. handle_cast({set, Key, Value}, State) ->
  105. case proplists:get_value(Key, State#state.config) of
  106. Value ->
  107. {noreply, State};
  108. _ ->
  109. write_config(Key, Value),
  110. {no_reply, State#state{config=ensure_config()}}
  111. end;
  112. %% @doc Trap unknown casts
  113. handle_cast(Message, State) ->
  114. {stop, {unknown_cast, Message}, State}.
  115. %% @spec handle_info(Info, State) -> {noreply, State} |
  116. %% {noreply, State, Timeout} |
  117. %% {stop, Reason, State}
  118. %% @doc Handling all non call/cast messages
  119. handle_info(_Info, State) ->
  120. {noreply, State}.
  121. %% @spec terminate(Reason, State) -> void()
  122. %% @doc This function is called by a gen_server when it is about to
  123. %% terminate. It should be the opposite of Module:init/1 and do any necessary
  124. %% cleaning up. When it returns, the gen_server terminates with Reason.
  125. %% The return value is ignored.
  126. terminate(_Reason, _State) ->
  127. ok.
  128. %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
  129. %% @doc Convert process state when code is changed
  130. code_change(_OldVsn, State, _Extra) ->
  131. {ok, State}.
  132. %%====================================================================
  133. %% support functions
  134. %%====================================================================
  135. ensure_config() ->
  136. File = config_file(),
  137. case filelib:is_regular(File) of
  138. true ->
  139. {ok, Consult} = file:consult(File),
  140. hd(Consult);
  141. false ->
  142. {ok, Password} = default(password),
  143. write_config([{password, Password}]),
  144. ensure_config()
  145. end.
  146. write_config(Key, Value) ->
  147. Config = [{Key,Value}, {modify_date, erlang:localtime()}
  148. | proplists:delete(modify_date, proplists:delete(Key,ensure_config()))],
  149. write_config(Config).
  150. write_config(Config) ->
  151. {ok, Dev} = file:open(config_file(), [write]),
  152. ok = file:write(Dev, <<
  153. "% THIS FILE IS AUTOMATICALLY GENERATED, DO NOT CHANGE WHILE ZOTONIC IS RUNNING.",10,
  154. "%", 10,
  155. "% You can change these options when Zotonic is not running.",10,
  156. "% Delete this file to reset Zotonic to its defaults (when Zotonic is not running).",10,
  157. "% Consult the config.in file for available options and their defaults.",10,
  158. 10>>),
  159. Data = io_lib:format("~p.~n", [Config]),
  160. ok = file:write(Dev, iolist_to_binary(Data)),
  161. ok = file:close(Dev).
  162. config_file() ->
  163. filename:join([z_utils:lib_dir(priv), "config"]).
  164. handle_get(Prop, State) ->
  165. case proplists:get_value(Prop, State#state.config) of
  166. undefined ->
  167. case default(Prop) of
  168. {ok, Value} ->
  169. case Prop of
  170. password ->
  171. write_config([{Prop, Value} | State#state.config]),
  172. {reply, Value, State#state{config=ensure_config()}};
  173. _ ->
  174. {reply, Value, State}
  175. end;
  176. undefined ->
  177. {reply, undefined, State}
  178. end;
  179. Value ->
  180. {reply, Value, State}
  181. end.
  182. default(ssl) -> {ok, false};
  183. default(ssl_cert) -> {ok, filename:join([z_utils:lib_dir(priv), "ssl"])};
  184. default(ssl_key) -> {ok, filename:join([z_utils:lib_dir(priv), "ssl"])};
  185. default(password) -> {ok, generate_id(8)};
  186. default(listen_port) -> {ok, 8000};
  187. default(listen_port_ssl) -> {ok, 8443};
  188. default(listen_ip) -> {ok, any};
  189. default(smtp_verp_as_from) -> {ok, false};
  190. default(smtp_no_mx_lookups) -> {ok, false};
  191. default(smtp_relay) -> {ok, false};
  192. default(smtp_host) -> {ok, "localhost"};
  193. default(smtp_port) -> {ok, 2525};
  194. default(smtp_ssl) -> {ok, false};
  195. default(smtp_bounce_ip) -> {ok, "127.0.0.1"};
  196. default(smtp_bounce_port) -> {ok, 2525};
  197. default(smtp_spamd_port) -> {ok, 783};
  198. default(log_dir) -> {ok, filename:join([z_utils:lib_dir(priv), "log"])};
  199. default(inet_backlog) -> {ok, 500};
  200. default(webmachine_error_handler) -> {ok, z_webmachine_error_handler};
  201. default(dbhost) -> {ok, "localhost"};
  202. default(dbport) -> {ok, 5432};
  203. default(dbuser) -> {ok, "zotonic"};
  204. default(dbpassword) -> {ok, ""};
  205. default(dbschema) -> {ok, "public"};
  206. default(_) -> undefined.
  207. %% @spec generate_id(int()) -> string()
  208. %% @doc Generate a random key
  209. generate_id(Len) ->
  210. generate_id(Len, []).
  211. generate_id(0, Key) ->
  212. Key;
  213. generate_id(Len, Key) ->
  214. Char = case random:uniform(62) of
  215. C when C =< 26 -> C - 1 + $a;
  216. C when C =< 52 -> C - 27 + $A;
  217. C -> C - 53 + $0
  218. end,
  219. generate_id(Len-1, [Char|Key]).