Generated on 2009-08-09 21:22:57 with etap 0.3.4.
| Name | Total lines | Lines of code | Total coverage | Code coverage | ||||
| couch_config | ?? | ?? | ?? |
|
....1 % Licensed under the Apache License, Version 2.0 (the "License"); you may not ....2 % use this file except in compliance with the License. You may obtain a copy of ....3 % the License at ....4 % ....5 % http://www.apache.org/licenses/LICENSE-2.0 ....6 % ....7 % Unless required by applicable law or agreed to in writing, software ....8 % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT ....9 % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the ...10 % License for the specific language governing permissions and limitations under ...11 % the License. ...12 ...13 % Reads CouchDB's ini file and gets queried for configuration parameters. ...14 % This module is initialized with a list of ini files that it consecutively ...15 % reads Key/Value pairs from and saves them in an ets table. If more an one ...16 % ini file is specified, the last one is used to write changes that are made ...17 % with store/2 back to that ini file. ...18 ...19 -module(couch_config). ...20 -behaviour(gen_server). ...21 ...22 -include("couch_db.hrl"). ...23 ...24 ...25 -export([start_link/1, stop/0]). ...26 -export([all/0, get/1, get/2, get/3, set/3, set/4, delete/2, delete/3]). ...27 -export([register/1, register/2]). ...28 -export([parse_ini_file/1]). ...29 ...30 -export([init/1, terminate/2, code_change/3]). ...31 -export([handle_call/3, handle_cast/2, handle_info/2]). ...32 ...33 -record(config, { ...34 notify_funs=[], ...35 write_filename=undefined ...36 }). ...37 ...38 ...39 start_link(IniFiles) -> ...40 gen_server:start_link({local, ?MODULE}, ?MODULE, IniFiles, []). ...41 ...42 stop() -> ...43 gen_server:cast(?MODULE, stop). ...44 ...45 ...46 all() -> ...47 lists:sort(gen_server:call(?MODULE, all)). ...48 ...49 ...50 get(Section) when is_binary(Section) -> ...51 ?MODULE:get(?b2l(Section)); ...52 get(Section) -> ...53 gen_server:call(?MODULE, {get, Section}). ...54 ...55 get(Section, Key) -> ...56 ?MODULE:get(Section, Key, undefined). ...57 ...58 get(Section, Key, Default) when is_binary(Section) and is_binary(Key) -> ...59 ?MODULE:get(?b2l(Section), ?b2l(Key), Default); ...60 get(Section, Key, Default) -> ...61 gen_server:call(?MODULE, {get, Section, Key, Default}). ...62 ...63 ...64 set(Section, Key, Value) -> ...65 ?MODULE:set(Section, Key, Value, true). ...66 ...67 set(Section, Key, Value, Persist) when is_binary(Section) and is_binary(Key) -> ...68 ?MODULE:set(?b2l(Section), ?b2l(Key), Value, Persist); ...69 set(Section, Key, Value, Persist) -> ...70 gen_server:call(?MODULE, {set, Section, Key, Value, Persist}). ...71 ...72 ...73 delete(Section, Key) when is_binary(Section) and is_binary(Key) -> ...74 delete(?b2l(Section), ?b2l(Key)); ...75 delete(Section, Key) -> ...76 delete(Section, Key, true). ...77 ...78 delete(Section, Key, Persist) when is_binary(Section) and is_binary(Key) -> ...79 delete(?b2l(Section), ?b2l(Key), Persist); ...80 delete(Section, Key, Persist) -> ...81 gen_server:call(?MODULE, {delete, Section, Key, Persist}). ...82 ...83 ...84 register(Fun) -> ...85 ?MODULE:register(Fun, self()). ...86 ...87 register(Fun, Pid) -> ...88 gen_server:call(?MODULE, {register, Fun, Pid}). ...89 ...90 ...91 init(IniFiles) -> ...92 ets:new(?MODULE, [named_table, set, private]), ...93 lists:map(fun(IniFile) -> ...94 {ok, ParsedIniValues} = parse_ini_file(IniFile), ...95 ets:insert(?MODULE, ParsedIniValues) ...96 end, IniFiles), ...97 WriteFile = case length(IniFiles) > 0 of ...98 true -> lists:last(IniFiles); ...99 _ -> undefined ..100 end, ..101 {ok, #config{write_filename=WriteFile}}. ..102 ..103 ..104 terminate(_Reason, _State) -> ..105 ok. ..106 ..107 ..108 handle_call(all, _From, Config) -> ..109 Resp = lists:sort((ets:tab2list(?MODULE))), ..110 {reply, Resp, Config}; ..111 handle_call({get, Section}, _From, Config) -> ..112 Matches = ets:match(?MODULE, {{Section, '$1'}, '$2'}), ..113 Resp = [{Key, Value} || [Key, Value] <- Matches], ..114 {reply, Resp, Config}; ..115 handle_call({get, Section, Key, Default}, _From, Config) -> ..116 Resp = case ets:lookup(?MODULE, {Section, Key}) of ..117 [] -> Default; ..118 [{_, Match}] -> Match ..119 end, ..120 {reply, Resp, Config}; ..121 handle_call({set, Sec, Key, Val, Persist}, _From, Config) -> ..122 true = ets:insert(?MODULE, {{Sec, Key}, Val}), ..123 case {Persist, Config#config.write_filename} of ..124 {true, undefined} -> ..125 ok; ..126 {true, FileName} -> ..127 couch_config_writer:save_to_file({{Sec, Key}, Val}, FileName); ..128 _ -> ..129 ok ..130 end, ..131 [catch F(Sec, Key, Val) || {_Pid, F} <- Config#config.notify_funs], ..132 {reply, ok, Config}; ..133 handle_call({delete, Sec, Key, Persist}, _From, Config) -> ..134 true = ets:delete(?MODULE, {Sec,Key}), ..135 case {Persist, Config#config.write_filename} of ..136 {true, undefined} -> ..137 ok; ..138 {true, FileName} -> ..139 couch_config_writer:save_to_file({{Sec, Key}, ""}, FileName); ..140 _ -> ..141 ok ..142 end, ..143 [catch F(Sec, Key, deleted) || {_Pid, F} <- Config#config.notify_funs], ..144 {reply, ok, Config}; ..145 handle_call({register, Fun, Pid}, _From, #config{notify_funs=PidFuns}=Config) -> ..146 erlang:monitor(process, Pid), ..147 % convert 1 and 2 arity to 3 arity ..148 Fun2 = ..149 case Fun of ..150 _ when is_function(Fun, 1) -> ..151 fun(Section, _Key, _Value) -> Fun(Section) end; ..152 _ when is_function(Fun, 2) -> ..153 fun(Section, Key, _Value) -> Fun(Section, Key) end; ..154 _ when is_function(Fun, 3) -> ..155 Fun ..156 end, ..157 {reply, ok, Config#config{notify_funs=[{Pid, Fun2} | PidFuns]}}. ..158 ..159 ..160 handle_cast(stop, State) -> ..161 {stop, normal, State}; ..162 handle_cast(_Msg, State) -> ..163 {noreply, State}. ..164 ..165 handle_info({'DOWN', _, _, DownPid, _}, #config{notify_funs=PidFuns}=Config) -> ..166 % remove any funs registered by the downed process ..167 FilteredPidFuns = [{Pid,Fun} || {Pid,Fun} <- PidFuns, Pid /= DownPid], ..168 {noreply, Config#config{notify_funs=FilteredPidFuns}}. ..169 ..170 code_change(_OldVsn, State, _Extra) -> ..171 {ok, State}. ..172 ..173 ..174 parse_ini_file(IniFile) -> ..175 IniFilename = couch_util:abs_pathname(IniFile), ..176 IniBin = ..177 case file:read_file(IniFilename) of ..178 {ok, IniBin0} -> ..179 IniBin0; ..180 {error, enoent} -> ..181 Fmt = "Couldn't find server configuration file ~s.", ..182 Msg = ?l2b(io_lib:format(Fmt, [IniFilename])), ..183 ?LOG_ERROR("~s~n", [Msg]), ..184 throw({startup_error, Msg}) ..185 end, ..186 ..187 {ok, Lines} = regexp:split(binary_to_list(IniBin), "\r\n|\n|\r|\032"), ..188 {_, ParsedIniValues} = ..189 lists:foldl(fun(Line, {AccSectionName, AccValues}) -> ..190 case string:strip(Line) of ..191 "[" ++ Rest -> ..192 case regexp:split(Rest, "\\]") of ..193 {ok, [NewSectionName, ""]} -> ..194 {NewSectionName, AccValues}; ..195 _Else -> % end bracket not at end, ignore this line ..196 {AccSectionName, AccValues} ..197 end; ..198 ";" ++ _Comment -> ..199 {AccSectionName, AccValues}; ..200 Line2 -> ..201 case regexp:split(Line2, "\s?=\s?") of ..202 {ok, [_SingleElement]} -> % no "=" found, ignore this line ..203 {AccSectionName, AccValues}; ..204 {ok, [""|_LineValues]} -> % line begins with "=", ignore ..205 {AccSectionName, AccValues}; ..206 {ok, [ValueName|LineValues]} -> % yeehaw, got a line! ..207 RemainingLine = couch_util:implode(LineValues, "="), ..208 % removes comments ..209 case regexp:split(RemainingLine, " ;|\t;") of ..210 {ok, [[]]} -> ..211 % empty line means delete this key ..212 ets:delete(?MODULE, {AccSectionName, ValueName}), ..213 {AccSectionName, AccValues}; ..214 {ok, [LineValue | _Rest]} -> ..215 {AccSectionName, ..216 [{{AccSectionName, ValueName}, LineValue} | AccValues]} ..217 end ..218 end ..219 end ..220 end, {"", []}, Lines), ..221 {ok, ParsedIniValues}. ..222
Generated using etap 0.3.4.