diff --git a/README.md b/README.md
index 1b98763..94f13a5 100644
--- a/README.md
+++ b/README.md
@@ -7,3 +7,15 @@
- client connects to server
- server writes back "hello"
- server closes connection
+
+- x01-noisy-hello:
+ - was going to be hello world over noise
+ - abandoned
+
+- `x02-_`:
+ - skipped because the `2` key on my laptop doesn't work
+
+- x03-echo:
+ - telnet echo server
+ - budget version of zx template "traditional erlang service
+ application"
diff --git a/x02-echo/echoc.erl b/x02-echo/echoc.erl
deleted file mode 100644
index f2fc793..0000000
--- a/x02-echo/echoc.erl
+++ /dev/null
@@ -1,7 +0,0 @@
-% @doc echo server client
--module(echoc).
-
--export([main/0]).
-
-main() ->
- io:format("you can't help people who won't help themselves.~n", []).
diff --git a/x02-echo/echod.erl b/x02-echo/echod.erl
deleted file mode 100644
index daa8ce1..0000000
--- a/x02-echo/echod.erl
+++ /dev/null
@@ -1,265 +0,0 @@
-%% @doc
-%% super simple telnet echo server daemon
-%%
-%% no noise yet
-%%
-%% pimps and hos model (from trash talk template):
-%%
-%% ```
-%% t_sup
-%% |
-%% t_clients
-%% |
-%% +--- t_client_man pimp
-%% |
-%% +--- t_client_sup bottom bitch
-%% |
-%% +--- <0.1.0> (t_client) ho
-%% +--- <0.2.0> (t_client) ho
-%% +--- <0.3.0> (t_client) ho
-%% '''
-%%
-%% each ho:
-%%
-%%
-%% - Takes listen socket as arg
-%% - spawns next ho
-%%
-%%
-%% ```
-%% start(ListenSocket) ->
-%% t_client_sup:start_acceptor(ListenSocket).
-%%
-%% start_link(ListenSocket) ->
-%% proc_lib:start_link(?MODULE, init, [self(), ListenSocket]).
-%%
-%% 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).
-
-
-%%
-%% 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 = t_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.
-%% '''
-%%
-%% @end
--module(echod).
-
--include("./ansi.hrl").
--export([main/0]).
-
-
-%% pimp state
--record(ps,
- {lsock = none :: gen_tcp:socket(),
- hos = [] :: [{pid(), reference()}],
- ho_names = init_ho_names() :: [string()]}).
-
--type pimp_state() :: #ps{}.
-
-
-%% ho state
--record(hs,
- {name = none :: string(),
- daddy = none :: pid(),
- vagene = none :: gen_tcp:socket()}).
-
--type ho_state() :: #hs{}.
-
-
-
--spec main() -> no_return().
-%% @doc
-%% run the echo server daemon
-
-main() ->
- %% show the splash screen nigga
- ok = splash(),
- case gen_tcp:listen(6969, [binary, {packet, line}, {active, once}]) of
- {ok, LSock} ->
- %% we out here
- %% we not wuz sockitz nigga
- %% we **be** sockets
- %% nigga
- ok = pimp_logfln("Butters iz in the hizzouse: ~tp", [self()]),
- pimp_loop(#ps{lsock = LSock});
- {error, closed} ->
- %% dont be pimp
- io:format("PIMP ERROR: ~tp~n", [Error])
- end.
-
-
-
--spec splash() -> ok.
-%% @private
-%% show splash screen
-
-splash() ->
- SplashStr = """
- RBX example 2 telnet echo server daemon
- "one" part of the many-to-one
- """,
- io:format("~s~n", [SplashStr]).
-
-
-
--spec pimp_loop(PimpState) -> NoReturn when
- PimpState :: pimp_state(),
- NoReturn :: no_return().
-%% @private
-%% runs in context of main
-
-pimp_loop(PS = #ps{lsock = LSock, hos = Hos, ho_names = Names}) ->
- %% blocks until client connects
- case gen_tcp:accept(LSock) of
- {ok, ASock} ->
- {HoName, NewNames} = recycle_ho_names(Names),
- InitHoState = #hs{name = HoName,
- daddy = self(),
- vagene = ASock},
- SpawnHo = fun() -> do_be_ho(InitHoState) end,
- NewHo = {_Pid, _Ref} = erlang:spawn_monitor(SpawnHo),
- NewPimpState = PS#ps{hos = [NewHo | Hos],
- ho_names = NewNames},
- pimp_loop(NewPimpState);
- {echo, From, Data} ->
- echo_except(From, Data, Hos),
- pimp_loop(PS);
- {'DOWN', Ref, process, Pid, Info} ->
- ok = pimp_logfln("ho down: ~tp; reason: ~tw", [Pid, Info]),
- NewHos = lists:delete({Pid, Ref}, Hos),
- NewPS = PS#ps{hos = NewHos},
- pimp_loop(NewPS);
- Bad ->
- pimp_logfln("bad msg: ~tw", [Bad]),
- pimp_logln("liquidating the hos"),
- liquidate_hos(Hos),
- gen_tcp:close(LSock)
- end.
-
-liquidate_hos([{HoPid, _HoRef} | Hos]) ->
- pimp_logfln("liquidating ~tp", [HoPid]),
- exit(HoPid, kill),
- liquidate_hos(Hos);
-liquidate_hos([]) ->
- pimp_logln("it's done").
-
-
-% skip if equal
-echo_except(SrcPid, Data, [{SrcPid, _Ref} | Rest]) ->
- echo_except(SrcPid, Data, Rest);
-echo_except(SrcPid, Data, [{Pid, _Ref} | Rest]) ->
- Pid ! {echo, Data},
- echo_except(SrcPid, Data, Rest);
-echo_except(_, _, []) ->
- ok.
-
-
--spec spawn_ho(HoState) ->
-
-
--spec do_be_ho(HoState) -> NoReturn when
- HoState :: ho_state(),
- NoReturn :: no_return().
-%% @doc
-%% ain't no rest for the wicked
-
-do_be_ho(HS = #hs{name = Name, daddy = Daddy, vagene = ASock}) ->
- Msg = io:format("HI! I'm ~ts. What's your name?\r\n", [Name]),
- ok = ho_send(ASock, Msg),
- receive
- {tcp, ASock, Data} ->
- ho_rcvd(Data),
- Daddy ! {echo, self(), Data},
- do_be_ho(HS);
- {echo, Data} ->
- ho_send(ASock, Data),
- do_be_ho(HS);
- {tcp_closed, ASock} ->
- exit(its_fine)
- end.
-
-
-pimp_logfln(MsgStr, Args) ->
- pimp_logln(io_lib:format(MsgStr, Args)).
-
-pimp_logln(MsgStr) ->
- PimpPfx = io_lib:format("pimp ~tp :: ", [self()]),
- Msg = [?ANSI_BOLD([PimpPfx, MsgStr, "\n"])],
- ok = io:put_chars(Msg),
- ok.
-
-
-ho_rcvd(MsgStr) ->
- ok = io:format("ho ~tp << ~tw~n", [self(), MsgStr]),
- ok.
-
-ho_send(Socket, Msg) ->
- ok = io:format("ho ~tp >> ~tw~n", [self(), Msg]),
- ok = gen_tcp:send(Socket, Msg),
- ok.
-
-
-init_ho_names() ->
- ["Crystal",
- "Tiffany",
- "Amber",
- "Brandy",
- "Lola",
- "Angel",
- "Ginger",
- "Candy",
- "Charity",
- "Anastasia",
- "Cherry",
- "Kitty",
- "Jade",
- "Destiny",
- "Devon",
- "Chastity",
- "Raven",
- "Scarlett",
- "Bambi",
- "Star",
- "Paris",
- "Dallas",
- "Diamond",
- "Skye",
- "Trinity",
- "Tawny",
- "Layla",
- "Lexie",
- "Roxy",
- "Porsche",
- "Nevaeh",
- "Ashlynn",
- "Aspen",
- "Chyna",
- "Lexus",
- "Unique",
- "Chardonnay",
- "Houston",
- "London",
- "Coco",
- "Luscious",
- "Delight",
- "Capri",
- "Trixie",
- "Cinnamon"].
-
-recycle_ho_names([N | Ns]) ->
- {N, Ns ++ [N]}.
diff --git a/x02-echo/ansi.hrl b/x03-echo/ansi.hrl
similarity index 100%
rename from x02-echo/ansi.hrl
rename to x03-echo/ansi.hrl
diff --git a/x03-echo/echoc.erl b/x03-echo/echoc.erl
new file mode 100644
index 0000000..9fe70f8
--- /dev/null
+++ b/x03-echo/echoc.erl
@@ -0,0 +1,20 @@
+% @doc echo server client
+-module(echoc).
+
+-export([main/0]).
+
+main() ->
+ {ok, Socket} = gen_tcp:connect("localhost", 6969, [binary, {packet, 0}]),
+ loop(Socket).
+
+
+loop(Socket) ->
+ ok = inet:setopts(Socket, [{active, once}]),
+ receive
+ {tcp, Socket, Bytes} ->
+ ok = io:format("< ~tp~n", [Bytes]),
+ loop(Socket);
+ {tcp_closed, Socket} ->
+ ok = io:format("connection terminated~n", []),
+ exit(normal)
+ end.
diff --git a/x03-echo/echod.erl b/x03-echo/echod.erl
new file mode 100644
index 0000000..c438788
--- /dev/null
+++ b/x03-echo/echod.erl
@@ -0,0 +1,249 @@
+%% @doc
+%% super simple telnet echo server daemon
+%%
+%% no noise yet
+%%
+%% pimps and hos model (from trash talk template):
+%%
+%% ```
+%% t_sup
+%% |
+%% t_clients
+%% |
+%% +--- t_client_man pimp
+%% |
+%% +--- t_client_sup bottom bitch
+%% |
+%% +--- <0.1.0> (t_client) ho
+%% +--- <0.2.0> (t_client) ho
+%% +--- <0.3.0> (t_client) ho
+%% '''
+%%
+%% each ho:
+%%
+%%
+%% - Takes listen socket as arg
+%% - spawns next ho
+%%
+%%
+%% ```
+%% start(ListenSocket) ->
+%% t_client_sup:start_acceptor(ListenSocket).
+%%
+%% start_link(ListenSocket) ->
+%% proc_lib:start_link(?MODULE, init, [self(), ListenSocket]).
+%%
+%% 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).
+%%
+%% 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 = t_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.
+%% '''
+%%
+%% @end
+-module(echod).
+
+-include("./ansi.hrl").
+-export([main/0]).
+
+
+% ho from pimp pov
+-record(pho,
+ {pid :: pid(),
+ ref :: reference(),
+ name :: string()}).
+
+%% pimp state
+-record(ps,
+ {lsock = none :: gen_tcp:socket(),
+ hos = [] :: [#pho{}],
+ ho_names = init_ho_names() :: [string()]}).
+
+
+%% ho state
+-record(hs,
+ {name = none :: string(),
+ daddy = none :: pid(),
+ asock = none :: gen_tcp:socket()}).
+
+
+main() ->
+ io:format("RBX example 2 telnet echo server daemon~n"
+ "`one' part of the many-to-one relationship~n",
+ []),
+ p_init().
+
+
+
+% pimp process startup
+p_init() ->
+ p_logln("pimp startup (p_init)"),
+ Port = 6969,
+ Opts = [binary, {packet, line}, {active, once}],
+ case gen_tcp:listen(Port, Opts) of
+ {ok, LSock} ->
+ ok = p_logfln("pimp starting: ~tp", [self()]),
+ PS_I = #ps{lsock = LSock},
+ PS_II = p_spawn_ho(PS_I),
+ p_loop(PS_II);
+ {error, closed} ->
+ %% dont be pimp
+ p_logln("listen socket closed")
+ end.
+
+p_loop(PS = #ps{hos = Hos}) ->
+ receive
+ {ho, _HoPid, taken} ->
+ NewPS = p_spawn_ho(PS),
+ p_loop(NewPS);
+ {ho, HoPid, sent, Data} ->
+ HoName = p_ho_name(HoPid, PS),
+ SendMessage = io_lib:format("~ts :: ~ts\r\n", [HoName, Data]),
+ ok = p_echo_except(HoPid, HoName, SendMessage, Hos),
+ p_loop(PS);
+ Down = {'DOWN', _, _, _, _} ->
+ NewPS = p_ho_down(Down, PS),
+ p_loop(NewPS);
+ Unknown ->
+ ok = p_logfln("unknown message: ~tw", [Unknown]),
+ p_loop(PS)
+ end.
+
+% given pid of ho, get her name
+p_ho_name(HoPid, #ps{hos = Hos}) ->
+ case lists:keyfind(HoPid, #pho.pid, Hos) of
+ #pho{name = Name} -> Name;
+ false -> "JaneDoe"
+ end.
+
+% pimp spawns new ho
+p_spawn_ho(PS = #ps{lsock = LSock, hos = Hos, ho_names = Names}) ->
+ % new name for ho, initial ho state
+ {N, NewNames} = recycle(Names),
+ InitHS = #hs{name = N, daddy = self()},
+ % spawn the new ho
+ ok = p_logfln("spawning new ho: ~ts", [N]),
+ SpawnHo = fun() -> h_init(LSock, InitHS) end,
+ {HPid, HRef} = erlang:spawn_monitor(SpawnHo),
+ % new ho record from Pimp POV
+ NewHo = #pho{pid = HPid, ref = HRef, name = N},
+ ok = p_logfln("spawned new ho: ~tw", [NewHo]),
+ NewPS = PS#ps{hos = [NewHo | Hos], ho_names = NewNames},
+ NewPS.
+
+% ho down, remove her from roster
+p_ho_down({'DOWN', _Ref, process, Pid, Info}, PS = #ps{hos = Hos}) ->
+ ok = p_logfln("ho down: ~tp; reason: ~tw", [Pid, Info]),
+ NewHos = lists:keydelete(Pid, #pho.pid, Hos),
+ NewPS = PS#ps{hos = NewHos},
+ NewPS.
+
+
+% echo message from one ho to all the rest
+% skip if equal
+p_echo_except(SrcPid, SrcName, Data, [#pho{pid = SrcPid} | Rest]) ->
+ p_echo_except(SrcPid, SrcName, Data, Rest);
+p_echo_except(SrcPid, SrcName, Data, [#pho{pid = DstPid} | Rest]) ->
+ DstPid ! {pimp, send, Data},
+ p_echo_except(SrcPid, SrcName, Data, Rest);
+p_echo_except(_, _, _, []) ->
+ ok.
+
+%% runs in ho context
+h_init(LSock, HS = #hs{name = Name, daddy = Daddy, asock = none}) ->
+ h_logln(Name, "h_init"),
+ % this is key: accept needs to run in ho context, otherwise she
+ % doesn't own the acceptor socket
+ %
+ % blocks until client connects
+ case gen_tcp:accept(LSock) of
+ {ok, ASock} ->
+ h_logln(Name, "taken"),
+ % tell daddy to spawn the next ho
+ Daddy ! {ho, self(), taken},
+ Welcome = io_lib:format("HI! I'm ~ts. What's your name?\r\n",
+ [Name]),
+ ok = h_send(Name, ASock, Welcome),
+ HS_II = HS#hs{asock = ASock},
+ h_loop(HS_II);
+ {error, closed} ->
+ h_logln(Name, "didn't get taken")
+ end.
+
+
+h_loop(HS = #hs{name = Name, daddy = Daddy, asock = ASock}) ->
+ ok = inet:setopts(ASock, [{active, once}]),
+ receive
+ {tcp, ASock, Data} ->
+ Data_II = string:chomp(Data),
+ ok = h_rcvd(Name, Data_II),
+ Daddy ! {ho, self(), sent, Data_II},
+ h_loop(HS);
+ {pimp, send, Data} ->
+ ok = h_send(Name, ASock, Data),
+ h_loop(HS);
+ {tcp_closed, ASock} ->
+ exit(its_fine);
+ Unknown ->
+ ok = h_logfln(Name, "unknown message: ~tw", [Unknown]),
+ h_loop(HS)
+ end.
+
+
+% pimp log in bold
+p_logln(LogStr) ->
+ Msg = io_lib:format("!! Daddy :: ~ts~n", [LogStr]),
+ PimpChars = ?ANSI_BOLD([Msg]),
+ io:put_chars(PimpChars).
+
+p_logfln(LogStr, Args) ->
+ p_logln(io_lib:format(LogStr, Args)).
+
+
+% ho log dimmed
+h_logln(HoName, LogStr) ->
+ FMsg = io_lib:format("!! ~ts :: ~ts~n", [HoName, LogStr]),
+ HoChars = ?ANSI_DIM([FMsg]),
+ io:put_chars(HoChars).
+
+h_logfln(HoName, MsgStr, Args) ->
+ h_logln(HoName, io_lib:format(MsgStr, Args)).
+
+% normal: john messages
+h_rcvd(Name, MsgStr) ->
+ Msg = io_lib:format("<< ~ts :: ~ts~n", [Name, MsgStr]),
+ io:put_chars(Msg).
+
+h_send(Name, Socket, Msg) ->
+ ok = io:format(">> ~ts :: ~ts~n", [Name, Msg]),
+ gen_tcp:send(Socket, Msg).
+
+
+init_ho_names() ->
+ ["Crystal", "Tiffany", "Amber", "Brandy", "Lola", "Angel",
+ "Ginger", "Candy", "Charity", "Anastasia", "Cherry", "Kitty",
+ "Jade", "Destiny", "Devon", "Chastity", "Raven", "Scarlett",
+ "Bambi", "Star", "Paris", "Dallas", "Diamond", "Skye",
+ "Trinity", "Tawny", "Layla", "Lexie", "Roxy", "Porsche",
+ "Nevaeh", "Ashlynn", "Aspen", "Chyna", "Lexus", "Unique",
+ "Chardonnay", "Houston", "London", "Coco", "Luscious",
+ "Delight", "Capri", "Trixie", "Cinnamon"].
+
+
+
+recycle([N | Ns]) ->
+ {N, Ns ++ [N]}.
diff --git a/x02-echo/johns.txt b/x03-echo/johns.txt
similarity index 100%
rename from x02-echo/johns.txt
rename to x03-echo/johns.txt
diff --git a/x03-echo/johns_ii.txt b/x03-echo/johns_ii.txt
new file mode 100644
index 0000000..d178b94
--- /dev/null
+++ b/x03-echo/johns_ii.txt
@@ -0,0 +1,62 @@
+
+Alexander
+Anthony
+Asher
+Beau
+Benjamin
+Bennett
+Caleb
+Carter
+Charles
+Cooper
+Daniel
+David
+Dylan
+Elias
+Elijah
+Ethan
+Ezekiel
+Ezra
+Gabriel
+Grayson
+Henry
+Hudson
+Isaac
+Isaiah
+Jack
+Jackson
+Jacob
+James
+John
+Joseph
+Josiah
+Julian
+Leo
+Levi
+Liam
+Logan
+Luca
+Lucas
+Luke
+Mason
+Mateo
+Matthew
+Maverick
+Michael
+Miles
+Noah
+Oliver
+Owen
+Roman
+Rowan
+Samuel
+Santiago
+Sebastian
+Theodore
+Thiago
+Thomas
+Waylon
+Wesley
+Weston
+William
+Wyatt
diff --git a/x02-echo/names.txt b/x03-echo/names.txt
similarity index 100%
rename from x02-echo/names.txt
rename to x03-echo/names.txt
diff --git a/x02-echo/names2.txt b/x03-echo/names2.txt
similarity index 100%
rename from x02-echo/names2.txt
rename to x03-echo/names2.txt
diff --git a/x02-echo/names3.txt b/x03-echo/names3.txt
similarity index 100%
rename from x02-echo/names3.txt
rename to x03-echo/names3.txt
diff --git a/x03-echo/scratch.erl b/x03-echo/scratch.erl
new file mode 100644
index 0000000..c629f18
--- /dev/null
+++ b/x03-echo/scratch.erl
@@ -0,0 +1,9 @@
+
+liquidate_hos([{HoPid, _HoRef} | Hos]) ->
+ p_logfln("liquidating ~tp", [HoPid]),
+ exit(HoPid, kill),
+ liquidate_hos(Hos);
+liquidate_hos([]) ->
+ p_logln("it's done").
+
+