diff options
Diffstat (limited to 'src/couchdb')
-rw-r--r-- | src/couchdb/couch_config.erl | 28 | ||||
-rw-r--r-- | src/couchdb/couch_config_writer.erl | 20 | ||||
-rw-r--r-- | src/couchdb/couch_httpd.erl | 237 |
3 files changed, 141 insertions, 144 deletions
diff --git a/src/couchdb/couch_config.erl b/src/couchdb/couch_config.erl index 07d47dff..c8c803fe 100644 --- a/src/couchdb/couch_config.erl +++ b/src/couchdb/couch_config.erl @@ -11,9 +11,9 @@ % the License. %% @doc Reads CouchDB's ini file and gets queried for configuration parameters. -%% This module is initialized with a list of ini files that it -%% consecutively reads Key/Value pairs from and saves them in an ets -%% table. If more an one ini file is specified, the last one is used to +%% This module is initialized with a list of ini files that it +%% consecutively reads Key/Value pairs from and saves them in an ets +%% table. If more an one ini file is specified, the last one is used to %% write changes that are made with store/2 back to that ini file. -module(couch_config). @@ -21,13 +21,13 @@ -behaviour(gen_server). -export([start_link/1, init/1, - handle_call/3, handle_cast/2, handle_info/2, + handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([store/2, register/1, register/2, +-export([store/2, register/1, register/2, get/1, get/2, lookup_match/1, lookup_match/2, all/0, unset/1, load_ini_file/1]). - + -record(config, {notify_funs=[], writeback_filename="" @@ -37,7 +37,7 @@ %% @type etstable() = integer(). -start_link(IniFiles) -> gen_server:start_link({local, ?MODULE}, ?MODULE, IniFiles, []). +start_link(IniFiles) -> gen_server:start_link({local, ?MODULE}, ?MODULE, IniFiles, []). %% @spec store(Key::any(), Value::any()) -> {ok, Tab::etsatable()} %% @doc Public API function that triggers storage of a Key/Value pair into the @@ -57,12 +57,12 @@ get(Key, Default) -> fix_lookup_result(ets:lookup(?MODULE, Key), Default). %% @spec lookup_match(Key::any()) -> Value::any() | undefined:atom() -%% @doc Lets you look for a Key's Value specifying a pattern that gets passed +%% @doc Lets you look for a Key's Value specifying a pattern that gets passed %% to ets::match(). Returns undefined::atom() if no Key is found. lookup_match(Key) -> gen_server:call(?MODULE, {lookup_match, Key}). %% @spec lookup_match(Key::any(), Default::any()) -> Value::any() | Default -%% @doc Lets you look for a Key's Value specifying a pattern that gets passed +%% @doc Lets you look for a Key's Value specifying a pattern that gets passed %% to ets::match(). Returns Default::any() if no Key is found lookup_match(Key, Default) -> gen_server:call(?MODULE, {lookup_match, Key, Default}). @@ -74,7 +74,7 @@ register(Fun) -> gen_server:call(?MODULE, {register, Fun, self()}). register(Fun, Pid) -> gen_server:call(?MODULE, {register, Fun, Pid}). %% @spec unset(Key::any) -> ok -%% @doc Public API call to remove the configuration entry from the internal +%% @doc Public API call to remove the configuration entry from the internal %% ets table. This change is _not_ written to the storage ini file. unset(Key) -> gen_server:call(?MODULE, {unset, Key}). @@ -115,7 +115,7 @@ handle_call(all, _From, Config) -> handle_call({register, Fun, Pid}, _From, #config{notify_funs=PidFuns}=Config) -> erlang:monitor(process, Pid), {reply, ok, Config#config{notify_funs=[{Pid, Fun}|PidFuns]}}. - + fix_lookup_result([{_Key, Value}], _Default) -> Value; @@ -125,7 +125,7 @@ fix_lookup_result(Values, _Default) -> [list_to_tuple(Value) || Value <- Values]. %% @spec insert_and_commit(Tab::etstable(), Config::any()) -> ok -%% @doc Inserts a Key/Value pair into the ets table, writes it to the storage +%% @doc Inserts a Key/Value pair into the ets table, writes it to the storage %% ini file and calls all registered callback functions for Key. insert_and_commit(Config, KV) -> true = ets:insert(?MODULE, KV), @@ -146,7 +146,7 @@ load_ini_file(IniFile) -> io:format("~s~n", [Msg]), throw({startup_error, Msg}) end, - + {ok, Lines} = regexp:split(binary_to_list(IniBin), "\r\n|\n|\r|\032"), {_, ParsedIniValues} = lists:foldl(fun(Line, {AccSectionName, AccValues}) -> @@ -173,7 +173,7 @@ load_ini_file(IniFile) -> end end end, {"", []}, Lines), - + [ets:insert(?MODULE, {Key, Value}) || {Key, Value} <- ParsedIniValues], ok. diff --git a/src/couchdb/couch_config_writer.erl b/src/couchdb/couch_config_writer.erl index 6e93c131..6a56c96e 100644 --- a/src/couchdb/couch_config_writer.erl +++ b/src/couchdb/couch_config_writer.erl @@ -24,24 +24,24 @@ -export([save_to_file/2]). %% @spec save_to_file( -%% Config::{{Module::string(), Variable::string()}, Value::string()}, +%% Config::{{Module::string(), Variable::string()}, Value::string()}, %% File::filename()) -> ok %% @doc Saves a Module/Key/Value triple to the ini file File::filename() save_to_file({{Module, Variable}, Value}, File) -> - + ?LOG_DEBUG("saving to file '~s', Congif: '~p'", [File, {{Module, Variable}, Value}]), - + % open file and create a list of lines {ok, Stream} = file:read_file(File), OldFileContents = binary_to_list(Stream), {ok, Lines} = regexp:split(OldFileContents, "\r\n|\n|\r|\032"), - + % prepare input variables ModuleName = "[" ++ Module ++ "]", VariableList = Variable, - + % produce the contents for the config file - NewFileContents = + NewFileContents = case NewFileContents2 = save_loop({{ModuleName, VariableList}, Value}, Lines, "", "", []) of % we didn't change anything, that means we couldn't find a matching % [ini section] in which case we just append a new one. @@ -50,7 +50,7 @@ save_to_file({{Module, Variable}, Value}, File) -> _ -> NewFileContents2 end, - + % do the save, close the config file and get out save_file(File, NewFileContents), file:close(Stream), @@ -102,7 +102,7 @@ save_loop({{Module, Variable}, Value}, [Line|Rest], OldCurrentModule, Contents, % go to next line save_loop({{Module, Variable}, Value}, Rest, NewCurrentModule, Contents2 ++ NewContents, DoneVariables2); - + save_loop(_Config, [], _OldModule, NewFileContents, _DoneVariable) -> % we're out of new lines, just return the new file's contents NewFileContents. @@ -111,7 +111,7 @@ append_new_ini_section({{ModuleName, Variable}, Value}, OldFileContents) -> OldFileContents ++ "\n\n" ++ ModuleName ++ "\n" ++ Variable ++ "=" ++ Value ++ "\n". %% @spec parse_module(Lins::string(), OldModule::string()) -> string() -%% @doc Tries to match a line against a pattern specifying a ini module or +%% @doc Tries to match a line against a pattern specifying a ini module or %% section ("[Module]"). Returns OldModule if no match is found. parse_module(Line, OldModule) -> case regexp:match(Line, "^\\[([a-zA-Z0-9_-]*)\\]$") of @@ -140,7 +140,7 @@ parse_variable(Line, Variable, Value) -> Variable ++ "=" ++ Value end. -%% @spec save_file(File::filename(), Contents::string()) -> +%% @spec save_file(File::filename(), Contents::string()) -> %% ok | {error, Reason::string()} %% @doc Writes Contents to File save_file(File, Contents) -> diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index 51959d7d..1d420ffc 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -40,14 +40,14 @@ start_link() -> % read config and register for configuration changes - + % just stop if one of the config settings change. couch_server_sup % will restart us and then we will pick up the new settings. - + BindAddress = couch_config:get({"HTTPd", "BindAddress"}, any), Port = couch_config:get({"HTTPd", "Port"}, "5984"), DocumentRoot = couch_config:get({"HTTPd", "DocumentRoot"}, "../../share/www"), - + % and off we go Loop = fun (Req) -> apply(couch_httpd, handle_request, [Req, DocumentRoot]) end, {ok, Pid} = mochiweb_http:start([ @@ -64,7 +64,7 @@ start_link() -> ({"HTTPd", "DocumentRoot"}) -> ?MODULE:stop() end, Pid), - + {ok, Pid}. stop() -> @@ -77,7 +77,7 @@ handle_request(Req, DocumentRoot) -> % alias HEAD to GET as mochiweb takes care of stripping the body Method = case Req:get(method) of 'HEAD' -> 'GET'; - Other -> + Other -> % handling of non standard HTTP verbs. Should be fixe din gen_tcp:recv() case Other of "COPY" -> 'COPY'; @@ -110,7 +110,7 @@ handle_request(Req, DocumentRoot) -> Path, Resp:get(code) ]). - + handle_request(Req, DocumentRoot, Method, Path) -> % Start = erlang:now(), X = handle_request0(Req, DocumentRoot, Method, Path), @@ -222,11 +222,11 @@ handle_db_request(Req, 'DELETE', {DbName, []}) -> Error -> throw(Error) end; - + handle_db_request(Req, Method, {DbName, Rest}) -> case couch_db:open(DbName, []) of {ok, Db} -> - try + try handle_db_request(Req, Method, {DbName, Db, Rest}) after couch_db:close(Db) @@ -526,8 +526,8 @@ output_reduce_view(Req, View) -> Resp:write_chunk(AccSeparator ++ Json), {ok, {",",0,AccCount-1}}; (Key, Red, {AccSeparator,0,AccCount}) - when is_integer(GroupLevel) - andalso is_tuple(Key) + when is_integer(GroupLevel) + andalso is_tuple(Key) andalso element(1, Key) /= obj -> Json = lists:flatten(cjson:encode( {obj, [{key, list_to_tuple(lists:sublist(tuple_to_list(Key), GroupLevel))}, @@ -641,57 +641,57 @@ handle_doc_request(Req, 'PUT', _DbName, Db, DocId) -> ]}); handle_doc_request(Req, 'COPY', _DbName, Db, SourceDocId) -> - SourceRev = case extract_header_rev(Req) of - missing_rev -> []; - Rev -> Rev - end, - - {TargetDocId, TargetRev} = parse_copy_destination_header(Req), - - % open revision Rev or Current - {Doc, _DocRev} = couch_doc_open(Db, SourceDocId, SourceRev, []), - - % save new doc - {ok, NewTargetRev} = couch_db:update_doc(Db, Doc#doc{id=TargetDocId, revs=TargetRev}, []), - - send_json(Req, 201, [{"Etag", "\"" ++ NewTargetRev ++ "\""}], {obj, [ - {ok, true}, - {id, TargetDocId}, - {rev, NewTargetRev} - ]}); + SourceRev = case extract_header_rev(Req) of + missing_rev -> []; + Rev -> Rev + end, + + {TargetDocId, TargetRev} = parse_copy_destination_header(Req), + + % open revision Rev or Current + {Doc, _DocRev} = couch_doc_open(Db, SourceDocId, SourceRev, []), + + % save new doc + {ok, NewTargetRev} = couch_db:update_doc(Db, Doc#doc{id=TargetDocId, revs=TargetRev}, []), + + send_json(Req, 201, [{"Etag", "\"" ++ NewTargetRev ++ "\""}], {obj, [ + {ok, true}, + {id, TargetDocId}, + {rev, NewTargetRev} + ]}); handle_doc_request(Req, 'MOVE', _DbName, Db, SourceDocId) -> - SourceRev = case extract_header_rev(Req) of - missing_rev -> - throw({ - bad_request, - "MOVE requires a specified rev parameter for the origin resource."} - ); - Rev -> Rev - end, - - {TargetDocId, TargetRev} = parse_copy_destination_header(Req), - - % open revision Rev or Current - {Doc, _DocRev} = couch_doc_open(Db, SourceDocId, SourceRev, []), - - % save new doc & delete old doc in one operation - Docs = [ - Doc#doc{id=TargetDocId, revs=TargetRev}, - #doc{id=SourceDocId, revs=[SourceRev], deleted=true} - ], - - {ok, ResultRevs} = couch_db:update_docs(Db, Docs, []), - - DocResults = lists:zipwith( - fun(FDoc, NewRev) -> - {obj, [{"id", FDoc#doc.id}, {"rev", NewRev}]} - end, - Docs, ResultRevs), - send_json(Req, 201, {obj, [ - {ok, true}, - {new_revs, list_to_tuple(DocResults)} - ]}); + SourceRev = case extract_header_rev(Req) of + missing_rev -> + throw({ + bad_request, + "MOVE requires a specified rev parameter for the origin resource."} + ); + Rev -> Rev + end, + + {TargetDocId, TargetRev} = parse_copy_destination_header(Req), + + % open revision Rev or Current + {Doc, _DocRev} = couch_doc_open(Db, SourceDocId, SourceRev, []), + + % save new doc & delete old doc in one operation + Docs = [ + Doc#doc{id=TargetDocId, revs=TargetRev}, + #doc{id=SourceDocId, revs=[SourceRev], deleted=true} + ], + + {ok, ResultRevs} = couch_db:update_docs(Db, Docs, []), + + DocResults = lists:zipwith( + fun(FDoc, NewRev) -> + {obj, [{"id", FDoc#doc.id}, {"rev", NewRev}]} + end, + Docs, ResultRevs), + send_json(Req, 201, {obj, [ + {ok, true}, + {new_revs, list_to_tuple(DocResults)} + ]}); handle_doc_request(_Req, _Method, _DbName, _Db, _DocId) -> throw({method_not_allowed, "DELETE,GET,HEAD,PUT,COPY,MOVE"}). @@ -699,35 +699,24 @@ handle_doc_request(_Req, _Method, _DbName, _Db, _DocId) -> % Useful for debugging % couch_doc_open(Db, DocId) -> % couch_doc_open(Db, DocId, [], []). - -couch_doc_open(Db, DocId, Rev, Options) -> - case Rev of - "" -> % open most recent rev - case couch_db:open_doc(Db, DocId, Options) of - {ok, #doc{revs=[DocRev|_]}=Doc} -> - {Doc, DocRev}; - Error -> - throw(Error) - end; - _ -> % open a specific rev (deletions come back as stubs) - case couch_db:open_doc_revs(Db, DocId, [Rev], Options) of - {ok, [{ok, Doc}]} -> - {Doc, Rev}; - {ok, [Else]} -> - throw(Else) - end - end. -parse_copy_destination_header(Req) -> - Destination = Req:get_header_value("Destination"), - case regexp:match(Destination, "\\?") of - nomatch -> - {Destination, []}; - {match, _, _} -> - {ok, [DocId, RevQueryOptions]} = regexp:split(Destination, "\\?"), - {ok, [_RevQueryKey, Rev]} = regexp:split(RevQueryOptions, "="), - {DocId, [Rev]} - end. +couch_doc_open(Db, DocId, Rev, Options) -> + case Rev of + "" -> % open most recent rev + case couch_db:open_doc(Db, DocId, Options) of + {ok, #doc{revs=[DocRev|_]}=Doc} -> + {Doc, DocRev}; + Error -> + throw(Error) + end; + _ -> % open a specific rev (deletions come back as stubs) + case couch_db:open_doc_revs(Db, DocId, [Rev], Options) of + {ok, [{ok, Doc}]} -> + {Doc, Rev}; + {ok, [Else]} -> + throw(Else) + end + end. % Attachment request handlers @@ -761,7 +750,7 @@ handle_attachment_request(Req, Method, _DbName, Db, DocId, FileName) when (Method == 'PUT') or (Method == 'DELETE') -> Rev = extract_header_rev(Req), - + NewAttachment = case Method of 'DELETE' -> []; @@ -772,20 +761,20 @@ handle_attachment_request(Req, Method, _DbName, Db, DocId, FileName) }}] end, - Doc = - case Rev of - missing_rev -> % make the new doc - #doc{id=DocId}; - _ -> - case couch_db:open_doc_revs(Db, DocId, [Rev], []) of - {ok, [{ok, Doc0}]} -> Doc0#doc{revs=[Rev]}; - {ok, [Error]} -> throw(Error) - end + Doc = case Rev of + missing_rev -> % make the new doc + #doc{id=DocId}; + _ -> + case couch_db:open_doc_revs(Db, DocId, [Rev], []) of + {ok, [{ok, Doc0}]} -> Doc0#doc{revs=[Rev]}; + {ok, [Error]} -> throw(Error) + end end, - + #doc{attachments=Attachments} = Doc, DocEdited = Doc#doc{ - attachments = NewAttachment ++ proplists:delete(FileName, Attachments)}, + attachments = NewAttachment ++ proplists:delete(FileName, Attachments) + }, {ok, UpdatedRev} = couch_db:update_doc(Db, DocEdited, []), send_json(Req, case Method of 'DELETE' -> 200; _ -> 201 end, {obj, [ {ok, true}, @@ -796,28 +785,12 @@ handle_attachment_request(Req, Method, _DbName, Db, DocId, FileName) handle_attachment_request(_Req, _Method, _DbName, _Db, _DocId, _FileName) -> throw({method_not_allowed, "GET,HEAD,DELETE,PUT"}). -extract_header_rev(Req) -> - QueryRev = proplists:get_value("rev", Req:parse_qs()), - Etag = case Req:get_header_value("If-Match") of - undefined -> undefined; - Tag -> string:strip(Tag, both, $") - end, - case {QueryRev, Etag} of - {undefined, undefined} -> missing_rev; - {_, undefined} -> QueryRev; - {undefined, _} -> Etag; - _ when QueryRev == Etag -> Etag; - _ -> - throw({bad_request, "Document rev and etag have different values"}) - end. - % Config request handlers handle_config_request(_Req, Method, {config, Config}) -> [Module, Key] = string:tokens(Config, "/"), handle_config_request(_Req, Method, {[Module, Key]}); - % PUT /_config/Module/Key % "value" handle_config_request(_Req, 'PUT', {[Module, Key]}) -> @@ -825,7 +798,6 @@ handle_config_request(_Req, 'PUT', {[Module, Key]}) -> % POST,PUT /_config/Module/Key % "value" - handle_config_request(Req, 'POST', {[Module, Key]}) -> Value = binary_to_list(Req:recv_body()), ok = couch_config:store({Module, Key}, Value), @@ -835,7 +807,7 @@ handle_config_request(Req, 'POST', {[Module, Key]}) -> {key, Key}, {value, Value} ]}); - + % GET /_config/Module/Key handle_config_request(Req, 'GET', {[Module, Key]}) -> case couch_config:get({Module, Key},null) of @@ -850,8 +822,7 @@ handle_config_request(Req, 'GET', {[Module, Key]}) -> ]}) end; - -% DELETE /_config/Key +% DELETE /_config/Module/Key handle_config_request(Req, 'DELETE', {[Module, Key]}) -> case couch_config:get({Module, Key}, null) of null -> @@ -870,7 +841,7 @@ handle_config_request(Req, 'DELETE', {[Module, Key]}) -> % TODO: % POST,PUT /_config/ % [{Key, Value}, {K2, V2}, {K3, V3}] -% +% % POST,PUT/_config/Key?value=Value @@ -1141,6 +1112,32 @@ error_to_json0({Id, Reason}) when is_atom(Id) -> error_to_json0(Error) -> {500, error, Error}. +extract_header_rev(Req) -> + QueryRev = proplists:get_value("rev", Req:parse_qs()), + Etag = case Req:get_header_value("If-Match") of + undefined -> undefined; + Tag -> string:strip(Tag, both, $") + end, + case {QueryRev, Etag} of + {undefined, undefined} -> missing_rev; + {_, undefined} -> QueryRev; + {undefined, _} -> Etag; + _ when QueryRev == Etag -> Etag; + _ -> + throw({bad_request, "Document rev and etag have different values"}) + end. + +parse_copy_destination_header(Req) -> + Destination = Req:get_header_value("Destination"), + case regexp:match(Destination, "\\?") of + nomatch -> + {Destination, []}; + {match, _, _} -> + {ok, [DocId, RevQueryOptions]} = regexp:split(Destination, "\\?"), + {ok, [_RevQueryKey, Rev]} = regexp:split(RevQueryOptions, "="), + {DocId, [Rev]} + end. + send_error(Req, {method_not_allowed, Methods}) -> {ok, Req:respond({405, [{"Allow", Methods}] ++ server_header(), <<>>})}; send_error(Req, {modified, Etag}) -> |