summaryrefslogtreecommitdiff
path: root/src/couchdb/couch_server.erl
diff options
context:
space:
mode:
authorChristopher Lenz <cmlenz@apache.org>2008-03-28 23:32:19 +0000
committerChristopher Lenz <cmlenz@apache.org>2008-03-28 23:32:19 +0000
commit544a38dd45f6a58d34296c6c768afd086eb2ac70 (patch)
treec84cc02340b06aae189cff0dbfaee698f273f1f5 /src/couchdb/couch_server.erl
parent804cbbe033b8e7a3e8d7058aaf31bdf69ef18ac5 (diff)
Imported trunk.
git-svn-id: https://svn.apache.org/repos/asf/incubator/couchdb/trunk@642432 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/couchdb/couch_server.erl')
-rw-r--r--src/couchdb/couch_server.erl215
1 files changed, 215 insertions, 0 deletions
diff --git a/src/couchdb/couch_server.erl b/src/couchdb/couch_server.erl
new file mode 100644
index 00000000..bb3617b2
--- /dev/null
+++ b/src/couchdb/couch_server.erl
@@ -0,0 +1,215 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_server).
+-behaviour(gen_server).
+-behaviour(application).
+
+-export([start/0,start/1,start/2,stop/0,stop/1]).
+-export([open/1,create/2,delete/1,all_databases/0,get_version/0]).
+-export([init/1, handle_call/3,sup_start_link/2]).
+-export([handle_cast/2,code_change/3,handle_info/2,terminate/2]).
+-export([dev_start/0,remote_restart/0]).
+
+-include("couch_db.hrl").
+
+-record(server,{
+ root_dir = [],
+ dbname_regexp,
+ options=[]
+ }).
+
+start() ->
+ start("").
+
+start(IniFile) when is_atom(IniFile) ->
+ couch_server_sup:start_link(atom_to_list(IniFile) ++ ".ini");
+start(IniNum) when is_integer(IniNum) ->
+ couch_server_sup:start_link("couch" ++ integer_to_list(IniNum) ++ ".ini");
+start(IniFile) ->
+ couch_server_sup:start_link(IniFile).
+
+start(_Type, _Args) ->
+ start().
+
+stop() ->
+ couch_server_sup:stop().
+
+stop(_Reason) ->
+ stop().
+
+dev_start() ->
+ stop(),
+ up_to_date = make:all([load, debug_info]),
+ start().
+
+get_version() ->
+ Apps = application:loaded_applications(),
+ case lists:keysearch(couch, 1, Apps) of
+ {value, {_, _, Vsn}} ->
+ Vsn;
+ false ->
+ "0.0.0"
+ end.
+
+sup_start_link(RootDir, Options) ->
+ gen_server:start_link({local, couch_server}, couch_server, {RootDir, Options}, []).
+
+open(Filename) ->
+ gen_server:call(couch_server, {open, Filename}).
+
+create(Filename, Options) ->
+ gen_server:call(couch_server, {create, Filename, Options}).
+
+delete(Filename) ->
+ gen_server:call(couch_server, {delete, Filename}).
+
+remote_restart() ->
+ gen_server:call(couch_server, remote_restart).
+
+init({RootDir, Options}) ->
+ {ok, RegExp} = regexp:parse("^[a-z][a-z0-9\\_\\$()\\+\\-\\/]*$"),
+ {ok, #server{root_dir=RootDir, dbname_regexp=RegExp, options=Options}}.
+
+check_filename(#server{dbname_regexp=RegExp}, Filename) ->
+ case regexp:match(Filename, RegExp) of
+ nomatch ->
+ {error, illegal_database_name};
+ _Match ->
+ ok
+ end.
+
+get_full_filename(Server, Filename) ->
+ filename:join([Server#server.root_dir, "./" ++ Filename ++ ".couch"]).
+
+
+terminate(_Reason, _Server) ->
+ ok.
+
+all_databases() ->
+ {ok, Root} = gen_server:call(couch_server, get_root),
+ Filenames =
+ filelib:fold_files(Root, "^[a-z0-9\\_\\$()\\+\\-]*[\\.]couch$", true,
+ fun(Filename, AccIn) ->
+ case Filename -- Root of
+ [$/ | RelativeFilename] -> ok;
+ RelativeFilename -> ok
+ end,
+ [filename:rootname(RelativeFilename, ".couch") | AccIn]
+ end, []),
+ {ok, Filenames}.
+
+
+handle_call(get_root, _From, #server{root_dir=Root}=Server) ->
+ {reply, {ok, Root}, Server};
+handle_call({open, Filename}, From, Server) ->
+ case check_filename(Server, Filename) of
+ {error, Error} ->
+ {reply, {error, Error}, Server};
+ ok ->
+ Filepath = get_full_filename(Server, Filename),
+ Result = supervisor:start_child(couch_server_sup,
+ {Filename,
+ {couch_db, open, [Filename, Filepath]},
+ transient ,
+ infinity,
+ supervisor,
+ [couch_db]}),
+ case Result of
+ {ok, Db} ->
+ {reply, {ok, Db}, Server};
+ {error, already_present} ->
+ ok = supervisor:delete_child(couch_server_sup, Filename),
+ % call self recursively
+ handle_call({open, Filename}, From, Server);
+ {error, {already_started, Db}} ->
+ {reply, {ok, Db}, Server};
+ {error, {not_found, _}} ->
+ {reply, not_found, Server};
+ {error, {Error, _}} ->
+ {reply, {error, Error}, Server}
+ end
+ end;
+handle_call({create, Filename, Options}, _From, Server) ->
+ case check_filename(Server, Filename) of
+ {error, Error} ->
+ {reply, {error, Error}, Server};
+ ok ->
+ Filepath = get_full_filename(Server, Filename),
+ ChildSpec = {Filename,
+ {couch_db, create, [Filename, Filepath, Options]},
+ transient,
+ infinity,
+ supervisor,
+ [couch_db]},
+ Result =
+ case supervisor:delete_child(couch_server_sup, Filename) of
+ ok ->
+ sup_start_child(couch_server_sup, ChildSpec);
+ {error, not_found} ->
+ sup_start_child(couch_server_sup, ChildSpec);
+ {error, running} ->
+ % a server process for this database already started. Maybe kill it
+ case lists:member(overwrite, Options) of
+ true ->
+ supervisor:terminate_child(couch_server_sup, Filename),
+ ok = supervisor:delete_child(couch_server_sup, Filename),
+ sup_start_child(couch_server_sup, ChildSpec);
+ false ->
+ {error, database_already_exists}
+ end
+ end,
+ case Result of
+ {ok, _Db} -> couch_db_update_notifier:notify({created, Filename});
+ _ -> ok
+ end,
+ {reply, Result, Server}
+ end;
+handle_call({delete, Filename}, _From, Server) ->
+ FullFilepath = get_full_filename(Server, Filename),
+ supervisor:terminate_child(couch_server_sup, Filename),
+ supervisor:delete_child(couch_server_sup, Filename),
+ case file:delete(FullFilepath) of
+ ok ->
+ couch_db_update_notifier:notify({deleted, Filename}),
+ {reply, ok, Server};
+ {error, enoent} ->
+ {reply, not_found, Server};
+ Else ->
+ {reply, Else, Server}
+ end;
+handle_call(remote_restart, _From, #server{options=Options}=Server) ->
+ case proplists:get_value(remote_restart, Options) of
+ true ->
+ exit(self(), restart);
+ _ ->
+ ok
+ end,
+ {reply, ok, Server}.
+
+% this function is just to strip out the child spec error stuff if hit
+sup_start_child(couch_server_sup, ChildSpec) ->
+ case supervisor:start_child(couch_server_sup, ChildSpec) of
+ {error, {Error, _ChildInfo}} ->
+ {error, Error};
+ Else ->
+ Else
+ end.
+
+handle_cast(_Msg, State) ->
+ {noreply,State}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.