Skip to content

Commit

Permalink
Source for the TDD hands on project now on github
Browse files Browse the repository at this point in the history
  • Loading branch information
Gianfranco committed Mar 5, 2011
0 parents commit a442260
Show file tree
Hide file tree
Showing 13 changed files with 1,986 additions and 0 deletions.
12 changes: 12 additions & 0 deletions Makefile
@@ -0,0 +1,12 @@
all:
erlc -pa . -o ebin/ src/*.erl test/*.erl

test: all
erl -pa ebin/ -eval 'eunit:test(wn_resource_layer,[verbose]), init:stop().'
erl -pa ebin/ -eval 'eunit:test(wn_file_layer,[verbose]), init:stop().'
erl -pa ebin/ -eval 'eunit:test(wn_job_layer,[verbose]), init:stop().'

dialyze:
dialyzer src/*.erl test/*.erl

full: all test dialyze
31 changes: 31 additions & 0 deletions include/worker_net.hrl
@@ -0,0 +1,31 @@
%%% @author Gianfranco <zenon@zen.local>
%%% @copyright (C) 2010, Gianfranco
%%% Created : 10 Dec 2010 by Gianfranco <zenon@zen.local>

-type resource_spec() :: [{atom(),infinity| non_neg_integer()}].

-record(wn_resource,
{name :: string(),
type :: resource_spec(),
resides :: node(),
pid :: pid() | undefined
}).

-record(wn_file,
{id :: string(),
file :: string(),
resides :: node()
}).

-record(wn_job,
{id :: string(),
files :: [#wn_file{}],
resources :: [atom()],
commands :: [string()],
timeout :: integer()
}).

-type date() :: {integer(),integer(),integer()}.
-type time() :: {integer(),integer(),integer()}.
-type now() :: {integer(),integer(),integer()}.
-type time_marker() :: {date(),time(),now()}.
264 changes: 264 additions & 0 deletions src/wn_file_layer.erl
@@ -0,0 +1,264 @@
%%%-------------------------------------------------------------------
%%% @author Gianfranco <zenon@zen.local>
%%% @copyright (C) 2010, Gianfranco
%%% Created : 19 Dec 2010 by Gianfranco <zenon@zen.local>
%%%-------------------------------------------------------------------
-module(wn_file_layer).
-behaviour(gen_server).
-include("include/worker_net.hrl").

%% API
-export([start_link/1,add_file/1,list_files/0,stop/0,
retrieve_file/2,delete_file/2]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).

-record(state,
{ node_root :: string()
}).

%%%===================================================================
%%% API
%%%===================================================================
start_link(NodeRoot) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, NodeRoot, []).

-spec(add_file(#wn_file{}) -> ok | {error,term()}).
add_file(WnFile) ->
case gen_server:call(?MODULE,{add_file,WnFile}) of
{ok,{Pid,Ref}} ->
{ok,IoDev} = file:open(WnFile#wn_file.file,[read,binary]),
Pid ! {Ref,self(),IoDev},
receive
{Ref,Result} -> Result
end;
Error -> Error
end.

-spec(list_files() -> [#wn_file{}]).
list_files() ->
gen_server:call(?MODULE,list_all_files).

-spec(stop() -> ok).
stop() ->
gen_server:call(?MODULE,stop).

-spec(retrieve_file(node(),string()) -> {ok,string()} | {error,term()}).
retrieve_file(Node,Id) ->
case gen_server:call(?MODULE,{retrieve_file,Node,Id}) of
{ok,{ReadDev,Name}} ->
retrieve_file(ReadDev,Name,file:open(Name,[write,binary]));
X -> X
end.
retrieve_file(ReadDev,Name,{ok,WriteDev}) ->
case receive_file_client(WriteDev,ReadDev) of
ok -> {ok,Name};
Err -> Err
end;
retrieve_file(ReadDev,_Name,Err) ->
file:close(ReadDev),
Err.

-spec(delete_file(node(),string()) -> ok | {error,term()}).
delete_file(Node,Id) ->
gen_server:call(?MODULE,{delete_file,Node,Id}).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

init(NodeRoot) ->
ok = filelib:ensure_dir(wn_util:file_root(NodeRoot)),
{ok, #state{node_root = NodeRoot}}.

handle_call(stop,_From,State) ->
{stop,normal,ok,State};

handle_call(list_all_files,From,State) ->
spawn_link(file_collector(From)),
{noreply,State};

handle_call(list_files,_From,State) ->
PropList = check_files(State#state.node_root),
{reply,[V || {_,V} <- PropList],State};

handle_call({delete_file,Node,Id},From,State) ->
case {node() == Node, lists:member(Node,nodes())} of
{true,false} ->
{reply,try_delete_file(Id,State),State};
{false,true} ->
case rpc:call(Node,erlang,whereis,[?MODULE]) of
undefined -> {reply,{error,noresides},State};
_Pid ->
gen_server:cast({?MODULE,Node},{delete_file,From,Id}),
{noreply,State}
end;
{false,false} ->
{reply,{error,noresides},State}
end;

handle_call({retrieve_file,Node,Id},From,State) ->
case {node() == Node, lists:member(Node,nodes())} of
{true,false} ->
Result = try_retrieve(Id,State),
{reply,Result,State};
{false,true} ->
case rpc:call(Node,erlang,whereis,[?MODULE]) of
undefined -> {reply,{error,noresides},State};
_Pid ->
gen_server:cast({?MODULE,Node},{retrieve_file,From,Id}),
{noreply,State}
end;
{false,false} ->
{reply,{error,noresides},State}
end;

handle_call({add_file,WnFile}, From, State) ->
#wn_file{resides=Node} = WnFile,
case {Node == node(),lists:member(Node,nodes())} of
{true,false} ->
Result = try_add(WnFile,State),
{reply,Result,State};
{false,true} ->
case rpc:call(Node,erlang,whereis,[?MODULE]) of
undefined -> {reply,{error,noresides},State};
_Pid ->
gen_server:cast({?MODULE,Node},{add_file,From,WnFile}),
{noreply,State}
end;
{false,false} ->
{reply,{error,noresides},State}
end.

handle_cast({delete_file,From,Id},State) ->
Result = try_delete_file(Id,State),
gen_server:reply(From,Result),
{noreply,State};

handle_cast({retrieve_file,From,Id},State) ->
Result = try_retrieve(Id,State),
gen_server:reply(From,Result),
{noreply,State};

handle_cast({add_file,From,WnFile},State) ->
Result = try_add(WnFile,State),
gen_server:reply(From,Result),
{noreply,State}.

handle_info(_Info, State) ->
{noreply, State}.

terminate(_Reason, _State) ->
ok.

code_change(_OldVsn, State, _Extra) ->
{ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================
check_files(NodeRoot) ->
Path = wn_util:file_root(NodeRoot),
ok = filelib:ensure_dir(Path),
{ok,NameDirs} = file:list_dir(Path),
lists:foldl(
fun(Dir,Acc) ->
{ok,[File]} = file:list_dir(Path++Dir),
[{Dir,#wn_file{id = Dir,
file = Path++Dir++"/"++File,
resides = node()}} | Acc]
end,[],NameDirs).

receive_file(Ref,WriteDev) ->
fun() ->
receive
{Ref,Pid,ReadDev} ->
receive_file(Ref,Pid,WriteDev,ReadDev)
end
end.

receive_file(Ref,Pid,WriteDev,ReadDev) ->
Res = close_transfer(WriteDev,ReadDev,
transfer(WriteDev,ReadDev)),
Pid ! {Ref,Res}.

receive_file_client(WriteDev,ReadDev) ->
close_transfer(WriteDev,ReadDev,
transfer(WriteDev,ReadDev)).

close_transfer(WriteDev,ReadDev,TransferResult) ->
case
lists:dropwhile(fun(X) -> X == ok end,
[TransferResult,
file:close(WriteDev),
file:close(ReadDev)]) of
[] -> ok;
[X|_] -> X
end.

-spec(transfer(pid(),pid()) -> ok | {error,term()}).
transfer(WriteDev,ReadDev) ->
transfer(WriteDev,ReadDev,file:read(ReadDev,1024)).

transfer(WriteDev,ReadDev,{ok,Data}) ->
case file:write(WriteDev,Data) of
ok -> transfer(WriteDev,ReadDev,file:read(ReadDev,1024));
Err -> Err
end;
transfer(_WriteDev,_ReadDev,eof) -> ok;
transfer(_WriteDev,_ReadDev,Err) -> Err.

file_collector(From) ->
Nodes = [node()|nodes()],
fun() ->
Res =
lists:foldl(
fun(Node,Acc) ->
case rpc:call(Node,erlang,whereis,[?MODULE]) of
undefined -> Acc;
_Pid ->
gen_server:call({?MODULE,Node},
list_files)++Acc
end
end,[],Nodes),
gen_server:reply(From,Res)
end.

try_delete_file(Id,State) ->
PropList = check_files(State#state.node_root),
case proplists:lookup(Id,PropList) of
none ->
{error,noexists};
{Id,WnFile} ->
List = [ file:delete(WnFile#wn_file.file),
file:del_dir(wn_util:file_dir(State#state.node_root,WnFile)) ],
case lists:dropwhile(fun(X) -> X == ok end,List) of
[] -> ok;
[X|_] -> X
end
end.

try_retrieve(Id,State) ->
PropList = check_files(State#state.node_root),
case proplists:lookup(Id,PropList) of
none -> {error,noexists};
{Id,WnFile} ->
Path = WnFile#wn_file.file,
{ok,ReadDev} = file:open(Path,[read,binary]),
{ok,{ReadDev,filename:basename(Path)}}
end.

try_add(WnFile,State) ->
Path = wn_util:file_dir(State#state.node_root,WnFile)++
filename:basename(WnFile#wn_file.file),
case filelib:is_file(Path) of
true -> {error,exists};
false ->
ok = filelib:ensure_dir(Path),
{ok,WriteDev} = file:open(Path,[write,binary]),
Ref = make_ref(),
Pid = spawn(receive_file(Ref,WriteDev)),
{ok,{Pid,Ref}}
end.

0 comments on commit a442260

Please sign in to comment.