Permalink
Browse files

Extracted agner_repo_server

  • Loading branch information...
yrashk committed Jan 30, 2011
1 parent c4926b2 commit 7ee519f3cc203de5ba3b95cc85fff4d95250a8ab
View
@@ -18,4 +18,6 @@
-type url() :: string().
+-type file() :: string().
+
-include_lib("agner_spec.hrl").
View
@@ -1,5 +1,5 @@
-spec repositories() -> list(agner_repo()) | not_found_error().
-spec tags(agner_repo()) -> list({agner_repo_tag(),sha1()}) | not_found_error().
-spec branches(agner_repo()) -> list({agner_repo_branch(), sha1()}) | not_found_error().
--spec spec(agner_repo(), sha1()) -> agner_spec() | not_found_error().
+-spec spec(agner_repo(), agner_spec_version()) -> agner_spec() | not_found_error().
-spec spec_url(agner_repo(), sha1()) -> url() | not_found_error().
View
@@ -4,8 +4,6 @@
-type hg_rev() :: string().
--type file() :: string().
-
-type agner_named_download_url() ::
{string(), agner_download_url()}.
View
@@ -1,6 +1,7 @@
{compile_post_script, "./scripts/escriptize"}.
-{escript_incl_apps,[getopt,rebar]}.
+{escript_incl_apps,[getopt,gproc,rebar]}.
{deps, [
{typespecs, "0.1", {agner, "typespecs"}},
- {getopt, "0.3.0", {agner, "getopt"}}
+ {getopt, "0.3.0", {agner, "getopt"}},
+ {gproc, ".*", {agner, "gproc"}}
]}.
View
@@ -6,7 +6,8 @@
{applications, [
kernel,
stdlib,
- inets
+ inets,
+ gproc
]},
{env, [{indices, [{github, "agner"}]}]},
{mod, { agner_app, []}},
View
@@ -26,6 +26,8 @@ start() ->
inets:start(),
ssl:start(),
{ok, _Pid} = inets:start(httpc,[{profile, agner}]),
+ application:start(gproc),
+ gproc:start_link(),
application:start(agner).
stop() ->
View
@@ -62,47 +62,39 @@ branches(Name) ->
end, Branches)
end.
-spec(Name, SHA1) ->
- case gen_server:call(agner_server, {pushed_at, Name}) of
+spec(Name, Version) ->
+ {ok, RepoServer} = agner_repo_server:create(Name, Version),
+ case agner_repo_server:pushed_at(RepoServer) of
At when is_list(At) ->
DotDir = filename:join(os:getenv("HOME"),".agner"),
filelib:ensure_dir(DotDir ++ "/"),
- AtFilename = filename:join([DotDir, "cache." ++ Name ++ SHA1 ++ lists:map(fun ($/) ->
- $_;
- ($\s) ->
- $_;
- (C) ->
- C
- end, At)]),
- spec_1(Name, SHA1, AtFilename);
+ AtFilename = filename:join([DotDir, "cache." ++ Name ++ integer_to_list(erlang:phash2(Version)) ++
+ lists:map(fun ($/) ->
+ $_;
+ ($\s) ->
+ $_;
+ (C) ->
+ C
+ end, At)]),
+ spec_1(RepoServer, AtFilename);
undefined ->
{A,B,C} = now(),
N = node(),
TmpFile = lists:flatten(io_lib:format("/tmp/agner-~p-~p.~p.~p",[N,A,B,C])),
- Result = spec_1(Name, SHA1, TmpFile),
+ Result = spec_1(RepoServer, TmpFile),
file:delete(TmpFile),
Result
end.
-spec_1(Name, SHA1, AtFilename) ->
+spec_1(RepoServer, AtFilename) ->
case file:read_file_info(AtFilename) of
{error, enoent} ->
- {A,B,C} = now(),
- N = node(),
- TmpFile = lists:flatten(io_lib:format("/tmp/agner-~p-~p.~p.~p",[N,A,B,C])),
- ClonePort = agner_download:git(["clone", "-q", "git://github.com/" ++ proper_repo_name(Name) ++ ".git", TmpFile]),
- Result = agner_download:process_port(ClonePort,
- fun () ->
- PortCheckout = agner_download:git(["checkout","-q",SHA1],[{cd, TmpFile}]),
- agner_download:process_port(PortCheckout, fun () ->
- Config = filename:join(TmpFile, "agner.config"),
- {ok, S} = file:consult(Config),
- {ok, _} = file:copy(Config, AtFilename),
- S
- end)
- end),
- os:cmd("rm -rf " ++ TmpFile),
- Result;
+ ok = agner_repo_server:clone(RepoServer, fun (Name) -> "git://github.com/" ++ proper_repo_name(Name) ++ ".git" end),
+
+ Config = agner_repo_server:file(RepoServer, "agner.config"),
+ {ok, S} = file:consult(Config),
+ {ok, _} = file:copy(Config, AtFilename),
+ S;
{ok, _} ->
{ok, S} = file:consult(AtFilename),
S
View
@@ -0,0 +1,224 @@
+-module(agner_repo_server).
+-include_lib("typespecs/include/typespecs.hrl").
+-include_lib("agner.hrl").
+-behaviour(gen_server).
+
+%% API
+-export([create/2, start_link/2]).
+-export([set_pushed_at/2, pushed_at/1, clone/2, file/2]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-define(SERVER, ?MODULE).
+
+
+-type pushed_at() :: string() | undefined.
+
+-record(state, {
+ name :: agner_spec_name() | undefined,
+ version :: agner_spec_version() | undefined,
+ pushed_at :: pushed_at(),
+ directory :: directory() | undefined
+ }).
+
+-type gen_server_state() :: #state{}.
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+-spec create(agner_spec_name(), agner_spec_version()) -> {ok, pid()}.
+
+create(Name, Version) ->
+ supervisor:start_child(agner_repo_server_sup, [Name, Version]).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start_link(agner_spec_name(), agner_spec_version()) -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+-spec start_link(agner_spec_name(), agner_spec_version()) -> {ok, pid()} | ignore | {error, term()}.
+
+start_link(Name, Version) ->
+ case gproc:lookup_local_name({?SERVER, Name, Version}) of
+ Pid when is_pid(Pid) ->
+ {ok, Pid};
+ _ ->
+ gen_server:start_link(?MODULE, {Name, Version}, [])
+ end.
+
+-spec set_pushed_at(pid(), pushed_at()) -> ok.
+
+set_pushed_at(Pid, PushedAt) ->
+ gen_server:cast(Pid, {set_pushed_at, PushedAt}).
+
+-spec pushed_at(pid()) -> pushed_at().
+
+pushed_at(Pid) ->
+ gen_server:call(Pid, pushed_at).
+
+-type repo_url_function() :: fun((agner_spec_name()) -> url()).
+
+-spec clone(pid(), repo_url_function()) -> ok.
+
+clone(Pid, Fun) ->
+ gen_server:call(Pid, {clone, Fun}).
+
+-spec file(pid(), file()) -> file() | not_found_error().
+
+file(Pid, Filename) ->
+ gen_server:call(Pid, {file, Filename}).
+
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+-spec init({agner_spec_name(), agner_spec_version()}) -> gen_server_init_result().
+
+init({Name, Version}) ->
+ gproc:add_local_name({?SERVER, Name, Version}),
+ {ok, #state{
+ name = Name,
+ version = Version
+ }}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages
+%%
+%% @spec handle_call(Request, From, State) ->
+%% {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+-type pushed_at_call() :: pushed_at().
+-type clone_call() :: {clone, repo_url_function()}.
+-type file_call() :: {file, file()}.
+
+-spec handle_call(pushed_at_call(), gen_server_from(), gen_server_state()) ->
+ gen_server_handle_call_result(pushed_at());
+ (clone_call(), gen_server_from(), gen_server_state()) ->
+ gen_server_handle_call_result(ok);
+ (file_call(), gen_server_from(), gen_server_state()) ->
+ gen_server_handle_call_result(file() | not_found_error()).
+
+handle_call(pushed_at, _From, #state{ pushed_at = PushedAt } = State) ->
+ {reply, PushedAt, State};
+
+handle_call({clone, Fun}, _From, #state{ directory = undefined, name = Name, version = Version } = State) ->
+ Directory = test_server:temp_name("/tmp/agner"),
+ RepoName = apply(Fun,[Name]),
+ ClonePort = agner_download:git(["clone", "-q", RepoName, Directory]),
+ Result = agner_download:process_port(ClonePort,
+ fun () ->
+ PortCheckout = agner_download:git(["checkout","-q",version_to_ref(Version)],
+ [{cd, Directory}]),
+ agner_download:process_port(PortCheckout, fun () ->
+ ok
+ end),
+ ok
+ end),
+ {reply, Result, State#state{ directory = Directory }};
+
+handle_call({clone, _Fun}, _From, #state{} = State) -> %% already cloned
+ {reply, ok, State};
+
+handle_call({file, Filename}, _From, #state{ directory = Directory} = State) ->
+ F = filename:join(Directory, Filename),
+ case (filelib:is_dir(F) or filelib:is_file(F)) of
+ true ->
+ {reply, F, State};
+ false ->
+ {reply, {error, not_found}, State}
+ end.
+
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+-type set_pushed_at_cast() :: {set_pushed_at, pushed_at()}.
+
+-spec handle_cast(set_pushed_at_cast(), gen_server_state()) -> gen_server_handle_cast_result().
+
+handle_cast({set_pushed_at, PushedAt}, #state{}=State) ->
+ {noreply, State#state{ pushed_at = PushedAt} }.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, #state{ directory = Directory }) when is_list(Directory) ->
+ os:cmd("rm -rf " ++ Directory),
+ ok;
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+-spec version_to_ref(agner_spec_version()) -> string().
+
+version_to_ref({flavour, Branch}) ->
+ "origin/" ++ Branch;
+version_to_ref({release, Tag}) ->
+ Tag.
+
@@ -0,0 +1,30 @@
+%% -*- Mode: Erlang; tab-width: 4 -*-
+-module(agner_repo_server_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%% Helper macro for declaring children of supervisor
+-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
+
+%% ===================================================================
+%% API functions
+%% ===================================================================
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% ===================================================================
+%% Supervisor callbacks
+%% ===================================================================
+
+init([]) ->
+ {ok, { {simple_one_for_one, 5, 10}, [
+ ?CHILD(agner_repo_server, worker)
+ ]} }.
+
Oops, something went wrong.

0 comments on commit 7ee519f

Please sign in to comment.