summaryrefslogtreecommitdiff
path: root/src/couchdb
diff options
context:
space:
mode:
Diffstat (limited to 'src/couchdb')
-rw-r--r--src/couchdb/couch_config.erl28
-rw-r--r--src/couchdb/couch_config_writer.erl20
-rw-r--r--src/couchdb/couch_httpd.erl237
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}) ->