PageRenderTime 33ms CodeModel.GetById 17ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 1ms

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