From 962a81734152fcc77c8becad20aacfb0898a5017 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 26 Jul 2022 14:57:18 +0900 Subject: [PATCH] Initial --- README.md | 1 + erlang/.gitignore | 15 ++ erlang/Emakefile | 1 + erlang/LICENSE | 19 +++ erlang/ebin/vanillae.app | 9 ++ erlang/src/v_client.erl | 204 ++++++++++++++++++++++++ erlang/src/v_client_man.erl | 298 ++++++++++++++++++++++++++++++++++++ erlang/src/v_client_sup.erl | 70 +++++++++ erlang/src/v_clients.erl | 48 ++++++ erlang/src/v_sup.erl | 46 ++++++ erlang/src/vanillae.erl | 76 +++++++++ erlang/zomp.meta | 17 ++ 12 files changed, 804 insertions(+) create mode 100644 README.md create mode 100644 erlang/.gitignore create mode 100644 erlang/Emakefile create mode 100644 erlang/LICENSE create mode 100644 erlang/ebin/vanillae.app create mode 100644 erlang/src/v_client.erl create mode 100644 erlang/src/v_client_man.erl create mode 100644 erlang/src/v_client_sup.erl create mode 100644 erlang/src/v_clients.erl create mode 100644 erlang/src/v_sup.erl create mode 100644 erlang/src/vanillae.erl create mode 100644 erlang/zomp.meta diff --git a/README.md b/README.md new file mode 100644 index 0000000..83b30a8 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Vanillae diff --git a/erlang/.gitignore b/erlang/.gitignore new file mode 100644 index 0000000..20177b4 --- /dev/null +++ b/erlang/.gitignore @@ -0,0 +1,15 @@ +.eunit +deps +*.o +*.beam +*.plt +*.swp +erl_crash.dump +ebin/*.beam +doc/*.html +doc/*.css +doc/edoc-info +doc/erlang.png +rel/example_project +.concrete/DEV_MODE +.rebar diff --git a/erlang/Emakefile b/erlang/Emakefile new file mode 100644 index 0000000..68c7b67 --- /dev/null +++ b/erlang/Emakefile @@ -0,0 +1 @@ +{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}. diff --git a/erlang/LICENSE b/erlang/LICENSE new file mode 100644 index 0000000..f206bfb --- /dev/null +++ b/erlang/LICENSE @@ -0,0 +1,19 @@ +Copyright 2022 Craig Everett + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/erlang/ebin/vanillae.app b/erlang/ebin/vanillae.app new file mode 100644 index 0000000..6279e2e --- /dev/null +++ b/erlang/ebin/vanillae.app @@ -0,0 +1,9 @@ +{application,vanillae, + [{description,"Erlang library for ecommerce use of the Aeternity blockchain"}, + {registered,[]}, + {included_applications,[]}, + {applications,[stdlib,kernel]}, + {vsn,"0.1.0"}, + {modules,[v_client,v_client_man,v_client_sup,v_clients,v_sup, + vanillae]}, + {mod,{vanillae,[]}}]}. diff --git a/erlang/src/v_client.erl b/erlang/src/v_client.erl new file mode 100644 index 0000000..cd0c58f --- /dev/null +++ b/erlang/src/v_client.erl @@ -0,0 +1,204 @@ +%%% @doc +%%% Vanillae for Erlang Client +%%% +%%% An extremely naive (currently Telnet) client handler. +%%% Unlike other modules that represent discrete processes, this one does not adhere +%%% to any OTP behavior. It does, however, adhere to OTP. +%%% +%%% In some cases it is more comfortable to write socket handlers or a certain +%%% category of state machines as "pure" Erlang processes. This approach is made +%%% OTP-able by use of the proc_lib module, which is the underlying library used +%%% to write the stdlib's behaviors like gen_server, gen_statem, gen_fsm, etc. +%%% +%%% http://erlang.org/doc/design_principles/spec_proc.html +%%% @end + +-module(v_client). +-vsn("0.1.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("MIT"). + +-export([start/1]). +-export([start_link/1, init/2]). +-export([system_continue/3, system_terminate/4, + system_get_state/1, system_replace_state/2]). + + +%%% Type and Record Definitions + + +-record(s, {socket = none :: none | gen_tcp:socket()}). + + +%% An alias for the state record above. Aliasing state can smooth out annoyances +%% that can arise from using the record directly as its own type all over the code. + +-type state() :: #s{}. + + +%%% Service Interface + + +-spec start(ListenSocket) -> Result + when ListenSocket :: gen_tcp:socket(), + Result :: {ok, pid()} + | {error, Reason}, + Reason :: {already_started, pid()} + | {shutdown, term()} + | term(). +%% @private +%% How the v_client_man or a prior v_client kicks things off. +%% This is called in the context of v_client_man or the prior v_client. + +start(ListenSocket) -> + v_client_sup:start_acceptor(ListenSocket). + + +-spec start_link(ListenSocket) -> Result + when ListenSocket :: gen_tcp:socket(), + Result :: {ok, pid()} + | {error, Reason}, + Reason :: {already_started, pid()} + | {shutdown, term()} + | term(). +%% @private +%% This is called by the v_client_sup. While start/1 is called to iniate a startup +%% (essentially requesting a new worker be started by the supervisor), this is +%% actually called in the context of the supervisor. + +start_link(ListenSocket) -> + proc_lib:start_link(?MODULE, init, [self(), ListenSocket]). + + +-spec init(Parent, ListenSocket) -> no_return() + when Parent :: pid(), + ListenSocket :: gen_tcp:socket(). +%% @private +%% This is the first code executed in the context of the new worker itself. +%% This function does not have any return value, as the startup return is +%% passed back to the supervisor by calling proc_lib:init_ack/2. +%% We see the initial form of the typical arity-3 service loop form here in the +%% call to listen/3. + +init(Parent, ListenSocket) -> + ok = io:format("~p Listening.~n", [self()]), + Debug = sys:debug_options([]), + ok = proc_lib:init_ack(Parent, {ok, self()}), + listen(Parent, Debug, ListenSocket). + + +-spec listen(Parent, Debug, ListenSocket) -> no_return() + when Parent :: pid(), + Debug :: [sys:dbg_opt()], + ListenSocket :: gen_tcp:socket(). +%% @private +%% This function waits for a TCP connection. The owner of the socket is still +%% the v_client_man (so it can still close it on a call to v_client_man:ignore/0), +%% but the only one calling gen_tcp:accept/1 on it is this process. Closing the socket +%% is one way a manager process can gracefully unblock child workers that are blocking +%% on a network accept. +%% +%% Once it makes a TCP connection it will call start/1 to spawn its successor. + +listen(Parent, Debug, ListenSocket) -> + case gen_tcp:accept(ListenSocket) of + {ok, Socket} -> + {ok, _} = start(ListenSocket), + {ok, Peer} = inet:peername(Socket), + ok = io:format("~p Connection accepted from: ~p~n", [self(), Peer]), + ok = v_client_man:enroll(), + State = #s{socket = Socket}, + loop(Parent, Debug, State); + {error, closed} -> + ok = io:format("~p Retiring: Listen socket closed.~n", [self()]), + exit(normal) + end. + + +-spec loop(Parent, Debug, State) -> no_return() + when Parent :: pid(), + Debug :: [sys:dbg_opt()], + State :: state(). +%% @private +%% The service loop itself. This is the service state. The process blocks on receive +%% of Erlang messages, TCP segments being received themselves as Erlang messages. + +loop(Parent, Debug, State = #s{socket = Socket}) -> + ok = inet:setopts(Socket, [{active, once}]), + receive + {tcp, Socket, <<"bye\r\n">>} -> + ok = io:format("~p Client saying goodbye. Bye!~n", [self()]), + ok = gen_tcp:send(Socket, "Bye!\r\n"), + ok = gen_tcp:shutdown(Socket, read_write), + exit(normal); + {tcp, Socket, Message} -> + ok = io:format("~p received: ~tp~n", [self(), Message]), + ok = v_client_man:echo(Message), + loop(Parent, Debug, State); + {relay, Sender, Message} when Sender == self() -> + ok = gen_tcp:send(Socket, ["Message from YOU: ", Message]), + loop(Parent, Debug, State); + {relay, Sender, Message} -> + From = io_lib:format("Message from ~tp: ", [Sender]), + ok = gen_tcp:send(Socket, [From, Message]), + loop(Parent, Debug, State); + {tcp_closed, Socket} -> + ok = io:format("~p Socket closed, retiring.~n", [self()]), + exit(normal); + {system, From, Request} -> + sys:handle_system_msg(Request, From, Parent, ?MODULE, Debug, State); + Unexpected -> + ok = io:format("~p Unexpected message: ~tp", [self(), Unexpected]), + loop(Parent, Debug, State) + end. + + +-spec system_continue(Parent, Debug, State) -> no_return() + when Parent :: pid(), + Debug :: [sys:dbg_opt()], + State :: state(). +%% @private +%% The function called by the OTP internal functions after a system message has been +%% handled. If the worker process has several possible states this is one place +%% resumption of a specific state can be specified and dispatched. + +system_continue(Parent, Debug, State) -> + loop(Parent, Debug, State). + + +-spec system_terminate(Reason, Parent, Debug, State) -> no_return() + when Reason :: term(), + Parent :: pid(), + Debug :: [sys:dbg_opt()], + State :: state(). +%% @private +%% Called by the OTP inner bits to allow the process to terminate gracefully. +%% Exactly when and if this is callback gets called is specified in the docs: +%% See: http://erlang.org/doc/design_principles/spec_proc.html#msg + +system_terminate(Reason, _Parent, _Debug, _State) -> + exit(Reason). + + + +-spec system_get_state(State) -> {ok, State} + when State :: state(). +%% @private +%% This function allows the runtime (or anything else) to inspect the running state +%% of the worker process at any arbitrary time. + +system_get_state(State) -> {ok, State}. + + +-spec system_replace_state(StateFun, State) -> {ok, NewState, State} + when StateFun :: fun(), + State :: state(), + NewState :: term(). +%% @private +%% This function allows the system to update the process state in-place. This is most +%% useful for state transitions between code types, like when performing a hot update +%% (very cool, but sort of hard) or hot patching a running system (living on the edge!). + +system_replace_state(StateFun, State) -> + {ok, StateFun(State), State}. diff --git a/erlang/src/v_client_man.erl b/erlang/src/v_client_man.erl new file mode 100644 index 0000000..fa775c3 --- /dev/null +++ b/erlang/src/v_client_man.erl @@ -0,0 +1,298 @@ +%%% @doc +%%% Vanillae for Erlang Client Manager +%%% +%%% This is the "manager" part of the service->worker pattern. +%%% It keeps track of who is connected and can act as a router among the workers. +%%% Having this process allows us to abstract and customize service-level concepts +%%% (the high-level ideas we care about in terms of solving an external problem in the +%%% real world) and keep them separate from the lower-level details of supervision that +%%% OTP should take care of for us. +%%% @end + +-module(v_client_man). +-vsn("0.1.0"). +-behavior(gen_server). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("MIT"). + +-export([listen/1, ignore/0]). +-export([enroll/0, echo/1]). +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + code_change/3, terminate/2]). + + +%%% Type and Record Definitions + + +-record(s, {port_num = none :: none | inet:port_number(), + listener = none :: none | gen_tcp:socket(), + clients = [] :: [pid()]}). + + +-type state() :: #s{}. + + + +%%% Service Interface + + +-spec listen(PortNum) -> Result + when PortNum :: inet:port_number(), + Result :: ok + | {error, Reason}, + Reason :: {listening, inet:port_number()}. +%% @doc +%% Tell the service to start listening on a given port. +%% Only one port can be listened on at a time in the current implementation, so +%% an error is returned if the service is already listening. + +listen(PortNum) -> + gen_server:call(?MODULE, {listen, PortNum}). + + +-spec ignore() -> ok. +%% @doc +%% Tell the service to stop listening. +%% It is not an error to call this function when the service is not listening. + +ignore() -> + gen_server:cast(?MODULE, ignore). + + + +%%% Client Process Interface + + +-spec enroll() -> ok. +%% @doc +%% Clients register here when they establish a connection. +%% Other processes can enroll as well. + +enroll() -> + gen_server:cast(?MODULE, {enroll, self()}). + + +-spec echo(Message) -> ok + when Message :: string(). +%% @doc +%% The function that tells the manager to broadcast a message to all clients. +%% This can broadcast arbitrary strings to clients from non-clients as well. + +echo(Message) -> + gen_server:cast(?MODULE, {echo, Message, self()}). + + + +%%% Startup Functions + + +-spec start_link() -> Result + when Result :: {ok, pid()} + | {error, Reason :: term()}. +%% @private +%% This should only ever be called by v_clients (the service-level supervisor). + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, none, []). + + +-spec init(none) -> {ok, state()}. +%% @private +%% Called by the supervisor process to give the process a chance to perform any +%% preparatory work necessary for proper function. + +init(none) -> + ok = io:format("Starting.~n"), + State = #s{}, + {ok, State}. + + + +%%% gen_server Message Handling Callbacks + + +-spec handle_call(Message, From, State) -> Result + when Message :: term(), + From :: {pid(), reference()}, + State :: state(), + Result :: {reply, Response, NewState} + | {noreply, State}, + Response :: ok + | {error, {listening, inet:port_number()}}, + NewState :: state(). +%% @private +%% The gen_server:handle_call/3 callback. +%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_call-3 + +handle_call({listen, PortNum}, _, State) -> + {Response, NewState} = do_listen(PortNum, State), + {reply, Response, NewState}; +handle_call(Unexpected, From, State) -> + ok = io:format("~p Unexpected call from ~tp: ~tp~n", [self(), From, Unexpected]), + {noreply, State}. + + +-spec handle_cast(Message, State) -> {noreply, NewState} + when Message :: term(), + State :: state(), + NewState :: state(). +%% @private +%% The gen_server:handle_cast/2 callback. +%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_cast-2 + +handle_cast({enroll, Pid}, State) -> + NewState = do_enroll(Pid, State), + {noreply, NewState}; +handle_cast({echo, Message, Sender}, State) -> + ok = do_echo(Message, Sender, State), + {noreply, State}; +handle_cast(ignore, State) -> + NewState = do_ignore(State), + {noreply, NewState}; +handle_cast(Unexpected, State) -> + ok = io:format("~p Unexpected cast: ~tp~n", [self(), Unexpected]), + {noreply, State}. + + +-spec handle_info(Message, State) -> {noreply, NewState} + when Message :: term(), + State :: state(), + NewState :: state(). +%% @private +%% The gen_server:handle_info/2 callback. +%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_info-2 + +handle_info({'DOWN', Mon, process, Pid, Reason}, State) -> + NewState = handle_down(Mon, Pid, Reason, State), + {noreply, NewState}; +handle_info(Unexpected, State) -> + ok = io:format("~p Unexpected info: ~tp~n", [self(), Unexpected]), + {noreply, State}. + + + +%%% OTP Service Functions + +-spec code_change(OldVersion, State, Extra) -> Result + when OldVersion :: {down, Version} | Version, + Version :: term(), + State :: state(), + Extra :: term(), + Result :: {ok, NewState} + | {error, Reason :: term()}, + NewState :: state(). +%% @private +%% The gen_server:code_change/3 callback. +%% See: http://erlang.org/doc/man/gen_server.html#Module:code_change-3 + +code_change(_, State, _) -> + {ok, State}. + + +-spec terminate(Reason, State) -> no_return() + when Reason :: normal + | shutdown + | {shutdown, term()} + | term(), + State :: state(). +%% @private +%% The gen_server:terminate/2 callback. +%% See: http://erlang.org/doc/man/gen_server.html#Module:terminate-2 + +terminate(_, _) -> + ok. + + + +%%% Doer Functions + +-spec do_listen(PortNum, State) -> {Result, NewState} + when PortNum :: inet:port_number(), + State :: state(), + Result :: ok + | {error, Reason :: {listening, inet:port_number()}}, + NewState :: state(). +%% @private +%% The "doer" procedure called when a "listen" message is received. + +do_listen(PortNum, State = #s{port_num = none}) -> + SocketOptions = + [inet6, + {packet, line}, + {active, once}, + {mode, binary}, + {keepalive, true}, + {reuseaddr, true}], + {ok, Listener} = gen_tcp:listen(PortNum, SocketOptions), + {ok, _} = v_client:start(Listener), + {ok, State#s{port_num = PortNum, listener = Listener}}; +do_listen(_, State = #s{port_num = PortNum}) -> + ok = io:format("~p Already listening on ~p~n", [self(), PortNum]), + {{error, {listening, PortNum}}, State}. + + +-spec do_ignore(State) -> NewState + when State :: state(), + NewState :: state(). +%% @private +%% The "doer" procedure called when an "ignore" message is received. + +do_ignore(State = #s{listener = none}) -> + State; +do_ignore(State = #s{listener = Listener}) -> + ok = gen_tcp:close(Listener), + State#s{port_num = none, listener = none}. + + +-spec do_enroll(Pid, State) -> NewState + when Pid :: pid(), + State :: state(), + NewState :: state(). + +do_enroll(Pid, State = #s{clients = Clients}) -> + case lists:member(Pid, Clients) of + false -> + Mon = monitor(process, Pid), + ok = io:format("Monitoring ~tp @ ~tp~n", [Pid, Mon]), + State#s{clients = [Pid | Clients]}; + true -> + State + end. + + +-spec do_echo(Message, Sender, State) -> ok + when Message :: string(), + Sender :: pid(), + State :: state(). +%% @private +%% The "doer" procedure called when an "echo" message is received. + +do_echo(Message, Sender, #s{clients = Clients}) -> + Send = fun(Client) -> Client ! {relay, Sender, Message} end, + lists:foreach(Send, Clients). + + +-spec handle_down(Mon, Pid, Reason, State) -> NewState + when Mon :: reference(), + Pid :: pid(), + Reason :: term(), + State :: state(), + NewState :: state(). +%% @private +%% Deal with monitors. When a new process enrolls as a client a monitor is set and +%% the process is added to the client list. When the process terminates we receive +%% a 'DOWN' message from the monitor. More sophisticated work managers typically have +%% an "unenroll" function, but this echo service doesn't need one. + +handle_down(Mon, Pid, Reason, State = #s{clients = Clients}) -> + case lists:member(Pid, Clients) of + true -> + NewClients = lists:delete(Pid, Clients), + State#s{clients = NewClients}; + false -> + Unexpected = {'DOWN', Mon, process, Pid, Reason}, + ok = io:format("~p Unexpected info: ~tp~n", [self(), Unexpected]), + State + end. diff --git a/erlang/src/v_client_sup.erl b/erlang/src/v_client_sup.erl new file mode 100644 index 0000000..0c0ca96 --- /dev/null +++ b/erlang/src/v_client_sup.erl @@ -0,0 +1,70 @@ +%%% @doc +%%% Vanillae for Erlang Client Supervisor +%%% +%%% This process supervises the client socket handlers themselves. It is a peer of the +%%% v_client_man (the manager interface to this network service component), +%%% and a child of the supervisor named v_clients. +%%% +%%% Because we don't know (or care) how many client connections the server may end up +%%% handling this is a simple_one_for_one supervisor which can spawn and manage as +%%% many identically defined workers as required, but cannot supervise any other types +%%% of processes (one of the tradeoffs of the "simple" in `simple_one_for_one'). +%%% +%%% http://erlang.org/doc/design_principles/sup_princ.html#id79244 +%%% @end + +-module(v_client_sup). +-vsn("0.1.0"). +-behaviour(supervisor). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("MIT"). + + +-export([start_acceptor/1]). +-export([start_link/0]). +-export([init/1]). + + + +-spec start_acceptor(ListenSocket) -> Result + when ListenSocket :: gen_tcp:socket(), + Result :: {ok, pid()} + | {error, Reason}, + Reason :: {already_started, pid()} + | {shutdown, term()} + | term(). +%% @private +%% Spawns the first listener at the request of the v_client_man when +%% vanillae:listen/1 is called, or the next listener at the request of the +%% currently listening v_client when a connection is made. +%% +%% Error conditions, supervision strategies and other important issues are +%% explained in the supervisor module docs: +%% http://erlang.org/doc/man/supervisor.html + +start_acceptor(ListenSocket) -> + supervisor:start_child(?MODULE, [ListenSocket]). + + +-spec start_link() -> {ok, pid()}. +%% @private +%% This supervisor's own start function. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, none). + + +-spec init(none) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +%% @private +%% The OTP init/1 function. + +init(none) -> + RestartStrategy = {simple_one_for_one, 1, 60}, + Client = {v_client, + {v_client, start_link, []}, + temporary, + brutal_kill, + worker, + [v_client]}, + {ok, {RestartStrategy, [Client]}}. diff --git a/erlang/src/v_clients.erl b/erlang/src/v_clients.erl new file mode 100644 index 0000000..2dce21f --- /dev/null +++ b/erlang/src/v_clients.erl @@ -0,0 +1,48 @@ +%%% @doc +%%% Vanillae for Erlang Client Service Supervisor +%%% +%%% This is the service-level supervisor of the system. It is the parent of both the +%%% client connection handlers and the client manager (which manages the client +%%% connection handlers). This is the child of v_sup. +%%% +%%% See: http://erlang.org/doc/apps/kernel/application.html +%%% @end + +-module(v_clients). +-vsn("0.1.0"). +-behavior(supervisor). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("MIT"). + +-export([start_link/0]). +-export([init/1]). + + +-spec start_link() -> {ok, pid()}. +%% @private +%% This supervisor's own start function. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, none). + +-spec init(none) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +%% @private +%% The OTP init/1 function. + +init(none) -> + RestartStrategy = {rest_for_one, 1, 60}, + ClientMan = {v_client_man, + {v_client_man, start_link, []}, + permanent, + 5000, + worker, + [v_client_man]}, + ClientSup = {v_client_sup, + {v_client_sup, start_link, []}, + permanent, + 5000, + supervisor, + [v_client_sup]}, + Children = [ClientSup, ClientMan], + {ok, {RestartStrategy, Children}}. diff --git a/erlang/src/v_sup.erl b/erlang/src/v_sup.erl new file mode 100644 index 0000000..d71820f --- /dev/null +++ b/erlang/src/v_sup.erl @@ -0,0 +1,46 @@ +%%% @doc +%%% Vanillae for Erlang Top-level Supervisor +%%% +%%% The very top level supervisor in the system. It only has one service branch: the +%%% client handling service. In a more complex system the client handling service would +%%% only be one part of a larger system. Were this a game system, for example, the +%%% item data management service would be a peer, as would a login credential provision +%%% service, game world event handling, and so on. +%%% +%%% See: http://erlang.org/doc/design_principles/applications.html +%%% See: http://zxq9.com/archives/1311 +%%% @end + +-module(v_sup). +-vsn("0.1.0"). +-behaviour(supervisor). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("MIT"). + +-export([start_link/0]). +-export([init/1]). + + +-spec start_link() -> {ok, pid()}. +%% @private +%% This supervisor's own start function. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + + +-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +%% @private +%% The OTP init/1 function. + +init([]) -> + RestartStrategy = {one_for_one, 1, 60}, + Clients = {v_clients, + {v_clients, start_link, []}, + permanent, + 5000, + supervisor, + [v_clients]}, + Children = [Clients], + {ok, {RestartStrategy, Children}}. diff --git a/erlang/src/vanillae.erl b/erlang/src/vanillae.erl new file mode 100644 index 0000000..8e1a93b --- /dev/null +++ b/erlang/src/vanillae.erl @@ -0,0 +1,76 @@ +%%% @doc +%%% Vanillae for Erlang +%%% @end + +-module(vanillae). +-vsn("0.1.0"). +-behavior(application). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("MIT"). + +-export([listen/1, ignore/0]). +-export([start/0, start/1]). +-export([start/2, stop/1]). + + + +-spec listen(PortNum) -> Result + when PortNum :: inet:port_num(), + Result :: ok + | {error, {listening, inet:port_num()}}. +%% @doc +%% Make the server start listening on a port. +%% Returns an {error, Reason} tuple if it is already listening. + +listen(PortNum) -> + v_client_man:listen(PortNum). + + +-spec ignore() -> ok. +%% @doc +%% Make the server stop listening if it is, or continue to do nothing if it isn't. + +ignore() -> + v_client_man:ignore(). + + +-spec start() -> ok. +%% @doc +%% Start the server in an "ignore" state. + +start() -> + ok = application:ensure_started(sasl), + ok = application:start(vanillae), + io:format("Starting..."). + + +-spec start(PortNum) -> ok + when PortNum :: inet:port_number(). +%% @doc +%% Start the server and begin listening immediately. Slightly more convenient when +%% playing around in the shell. + +start(PortNum) -> + ok = start(), + ok = v_client_man:listen(PortNum), + io:format("Startup complete, listening on ~w~n", [PortNum]). + + +-spec start(normal, term()) -> {ok, pid()}. +%% @private +%% Called by OTP to kick things off. This is for the use of the "application" part of +%% OTP, not to be called by user code. +%% See: http://erlang.org/doc/apps/kernel/application.html + +start(normal, _Args) -> + v_sup:start_link(). + + +-spec stop(term()) -> ok. +%% @private +%% Similar to start/2 above, this is to be called by the "application" part of OTP, +%% not client code. Causes a (hopefully graceful) shutdown of the application. + +stop(_State) -> + ok. diff --git a/erlang/zomp.meta b/erlang/zomp.meta new file mode 100644 index 0000000..bb0aac5 --- /dev/null +++ b/erlang/zomp.meta @@ -0,0 +1,17 @@ +{a_email,"ceverett@tsuriai.jp"}. +{author,"Craig Everett"}. +{c_email,"ceverett@tsuriai.jp"}. +{copyright,"Craig Everett"}. +{deps,[]}. +{desc,"Erlang library for ecommerce use of the Aeternity blockchain"}. +{file_exts,[]}. +{key_name,none}. +{license,"MIT"}. +{modules,[]}. +{name,"Vanillae for Erlang"}. +{package_id,{"otpr","vanillae",{0,1,0}}}. +{prefix,"v"}. +{repo_url,"https://github.com/aeternity/Vanillae"}. +{tags,[]}. +{type,app}. +{ws_url,"https://github.com/aeternity/Vanillae"}.