Skip to content

Commit d965094

Browse files
committedJun 28, 2016
Added interface functions to nameserver
1 parent bcee766 commit d965094

File tree

4 files changed

+396
-71
lines changed

4 files changed

+396
-71
lines changed
 

‎README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ To run the server, simple run:
8888
Or to run it as a background process:
8989

9090
~~~~
91-
[user@host chat_server] $ ./_rel/chat_server_release/bin/chat_server_release
91+
[user@host chat_server] $ ./_rel/chat_server_release/bin/chat_server_release start
9292
~~~~
9393

94-
Currently the server only supports the single *MainHall* chat room and simple
94+
Currently the server only supports the single **MainHall** chat room and simple
9595
messaging. Planned additions are to allow name changes, room creation, room
9696
ownership and room bans.
9797

‎chat_server/src/chat_server_nameservice.erl

+331-54
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,15 @@
1414
-export([start_link/0,
1515
% User functions
1616
new_user/0,
17+
get_user/1,
18+
change_user_name/2,
19+
delete_user/1,
1720
% Room functions
18-
get_room/1]).
21+
get_room/1,
22+
new_room/1,
23+
delete_room/1,
24+
get_room_contents/1,
25+
list_rooms/0]).
1926

2027
%% gen_server callbacks
2128
-export([init/1,
@@ -62,6 +69,36 @@ start_link() ->
6269
new_user() ->
6370
gen_server:call(?SERVER, new_user).
6471

72+
%%--------------------------------------------------------------------
73+
%% @doc
74+
%% Retrieve a user's pid based on their name
75+
%%
76+
%% @spec get_user(UserName) -> {ok, UPid} | no_such_user
77+
%% @end
78+
%%--------------------------------------------------------------------
79+
get_user(UserName) ->
80+
gen_server:call(?SERVER, {get_user, UserName}).
81+
82+
%%--------------------------------------------------------------------
83+
%% @doc
84+
%% Change a user's name in the name server
85+
%%
86+
%% @spec change_user_name(UPid, NewName) -> ok | name_taken
87+
%% @end
88+
%%--------------------------------------------------------------------
89+
change_user_name(OldName, NewName) ->
90+
gen_server:call(?SERVER, {change_user_name, OldName, NewName}).
91+
92+
%%--------------------------------------------------------------------
93+
%% @doc
94+
%% Delete a user by name from the name server
95+
%%
96+
%% @spec delete_user(Name) -> ok | {error, Reason}
97+
%% @end
98+
%%--------------------------------------------------------------------
99+
delete_user(Name) ->
100+
gen_server:call(?SERVER, {delete_user, Name}).
101+
65102
%%--------------------------------------------------------------------
66103
%% @doc
67104
%% Get the Pid of a room based on its name
@@ -72,6 +109,47 @@ new_user() ->
72109
get_room(RoomName) ->
73110
gen_server:call(?SERVER, {get_room, RoomName}).
74111

112+
%%--------------------------------------------------------------------
113+
%% @doc
114+
%% Add a new room to the name server
115+
%%
116+
%% @spec new_room(RoomName) -> {ok, RoomPid} | no_such_room
117+
%% @end
118+
%%--------------------------------------------------------------------
119+
new_room(RoomName) ->
120+
gen_server:call(?SERVER, {new_room, RoomName}).
121+
122+
%%--------------------------------------------------------------------
123+
%% @doc
124+
%% Delete a room by name from the name server
125+
%%
126+
%% @spec delete_room(RoomName) -> ok | {error, Reason}
127+
%% @end
128+
%%--------------------------------------------------------------------
129+
delete_room(RoomName) ->
130+
gen_server:call(?SERVER, {delete_room, RoomName}).
131+
132+
%%--------------------------------------------------------------------
133+
%% @doc
134+
%% Get the contents of a room by name
135+
%%
136+
%% @spec get_room_contents(RoomName) -> {ok, Contents, Owner}
137+
%% | {error, Reason}
138+
%% @end
139+
%%--------------------------------------------------------------------
140+
get_room_contents(RoomName) ->
141+
gen_server:call(?SERVER, {room_contents, RoomName}).
142+
143+
%%--------------------------------------------------------------------
144+
%% @doc
145+
%% List all rooms in the server with the number of occupants in each
146+
%%
147+
%% @spec list_rooms() -> {ok, RoomList} | {error, Reason}
148+
%% @end
149+
%%--------------------------------------------------------------------
150+
list_rooms() ->
151+
gen_server:call(?SERVER, list_rooms).
152+
75153
%%%===================================================================
76154
%%% gen_server callbacks
77155
%%%===================================================================
@@ -91,8 +169,8 @@ init([]) ->
91169
State = #state{},
92170
{ok, MainHall} = chat_server_room:new(?MAINHALL, <<>>),
93171
MH_Entry = #room{name=?MAINHALL, pid=MainHall},
94-
ets:insert(State#state.rooms, MH_Entry),
95-
{ok, State}.
172+
NewState = add_room(State, MH_Entry),
173+
{ok, NewState}.
96174

97175
%%--------------------------------------------------------------------
98176
%% @private
@@ -109,15 +187,71 @@ init([]) ->
109187
%% @end
110188
%%--------------------------------------------------------------------
111189
handle_call(new_user, {UPid, _Tag}, State) ->
112-
Name = generate_new_username(State#state.users),
190+
Name = generate_new_username(State),
113191
User = #user{pid = UPid, name = Name},
114192
NewState = add_user(State, User),
115193
{reply, {ok, Name}, NewState};
116194

195+
handle_call({get_user, UserName}, _From, State) ->
196+
Reply = get_user_pid(State, UserName),
197+
{reply, Reply, State};
198+
199+
handle_call({change_user_name, OldName, NewName}, _From, State) ->
200+
case user_name_exists(State, NewName) of
201+
true ->
202+
{reply, name_taken, State};
203+
_ ->
204+
NextState = set_user_name(State, OldName, NewName),
205+
{reply, ok, NextState}
206+
end;
207+
208+
handle_call({delete_user, UserName}, _From, State) ->
209+
case user_name_exists(State, UserName) of
210+
true ->
211+
NewState = remove_user(State, UserName),
212+
{reply, ok, NewState};
213+
_ ->
214+
{reply, {error, no_such_user}, State}
215+
end;
216+
117217
handle_call({get_room, RoomName}, _From, State) ->
118-
Reply = get_room_pid(State#state.rooms, RoomName),
218+
Reply = get_room_pid(State, RoomName),
119219
{reply, Reply, State};
120220

221+
handle_call({new_room, RoomId}, {UPid, _Tag}, State) ->
222+
{Reply, State} = case room_exists(State, RoomId) of
223+
true -> {room_already_exists, State};
224+
_ ->
225+
RoomPid = chat_server_room:new(RoomId, UPid),
226+
Room = #room{name = RoomId, pid = RoomPid},
227+
NewState = add_room(State, Room),
228+
{{room_created, RoomPid}, NewState}
229+
end,
230+
{reply, Reply, State};
231+
232+
handle_call({delete_room, RoomName}, _From, State) ->
233+
case room_exists(State, RoomName) of
234+
true ->
235+
NewState = remove_room(State, RoomName),
236+
{reply, ok, NewState};
237+
_ ->
238+
{reply, {error, no_such_room}, State}
239+
end;
240+
241+
handle_call({room_contents, RoomName}, _From, State) ->
242+
case room_exists(State, RoomName) of
243+
true ->
244+
RoomPid = get_room_pid(State, RoomName),
245+
{Contents, Owner} = chat_server_room:get_contents(RoomPid),
246+
{reply, {ok, Contents, Owner}, State};
247+
_ ->
248+
{reply, {error, no_such_room}, State}
249+
end;
250+
251+
handle_call(list_rooms, _From, State) ->
252+
RoomList = compile_room_list(State),
253+
{reply, {ok, RoomList}, State};
254+
121255
handle_call(_Request, _From, State) ->
122256
Reply = ok,
123257
{reply, Reply, State}.
@@ -177,18 +311,6 @@ code_change(_OldVsn, State, _Extra) ->
177311
%%% Internal functions
178312
%%%===================================================================
179313

180-
%%%-------------------------------------------------------------------
181-
%%% Room functions
182-
%%%-------------------------------------------------------------------
183-
184-
get_room_pid(RoomTable, RoomName) ->
185-
case ets:lookup(RoomTable, RoomName) of
186-
[#room{pid=Pid}] ->
187-
{ok, Pid};
188-
[] ->
189-
no_such_room
190-
end.
191-
192314
%%%-------------------------------------------------------------------
193315
%%% User functions
194316
%%%-------------------------------------------------------------------
@@ -198,25 +320,99 @@ get_room_pid(RoomTable, RoomName) ->
198320
%% @doc
199321
%% Add a new user to the user name table
200322
%%
201-
%% @spec add_user(UserTable, UserRecord) -> boolean()
323+
%% @spec add_user(State, UserRecord) -> #state{}
202324
%% @end
203325
%%--------------------------------------------------------------------
204326
add_user(State, User) ->
205-
ets:insert_new(State#state.users, User),
206-
State.
327+
ets:insert_new(State#state.users, User),
328+
State.
329+
330+
%%--------------------------------------------------------------------
331+
%% @private
332+
%% @doc
333+
%% Remove a user from the user name table
334+
%%
335+
%% @spec remove_user(State, Username) -> #state{}
336+
%% @end
337+
%%--------------------------------------------------------------------
338+
remove_user(State, UserName) ->
339+
ets:delete(State#state.users, UserName),
340+
State.
341+
342+
%%--------------------------------------------------------------------
343+
%% @private
344+
%% @doc
345+
%% Test whether a user name is already recorded in the nameserver
346+
%%
347+
%% @spec user_name_exists(State, UserName) -> boolean()
348+
%% @end
349+
%%--------------------------------------------------------------------
350+
user_name_exists(State, User) ->
351+
ets:member(State#state.users, User).
352+
353+
%%--------------------------------------------------------------------
354+
%% @private
355+
%% @doc
356+
%% Get the pid of a user based on their name
357+
%%
358+
%% @spec get_user_pid(State, UserName) -> {ok, pid()} | no_such_user
359+
%% @end
360+
%%--------------------------------------------------------------------
361+
get_user_pid(State, UserName) ->
362+
case ets:lookup(State#state.users, UserName) of
363+
[User] -> {ok, User#user.pid};
364+
[] -> no_such_user
365+
end.
366+
367+
%%--------------------------------------------------------------------
368+
%% @private
369+
%% @doc
370+
%% Set the name of a user identified by a binary to the given binary
371+
%%
372+
%% @spec set_user_name(State, OldName, NewName) -> {ok, #state{}}
373+
%% | {error, Reason}
374+
%% @end
375+
%%--------------------------------------------------------------------
376+
set_user_name(State, OldName, NewName) ->
377+
UserTable = State#state.users,
378+
case ets:take(UserTable, OldName) of
379+
[User] ->
380+
NewUser = User#user{name = NewName},
381+
case ets:insert_new(UserTable, NewUser) of
382+
true ->
383+
{ok, State};
384+
_ ->
385+
{error, username_taken}
386+
end;
387+
[] ->
388+
{error, no_such_user};
389+
_ ->
390+
{error, multiple_users_with_same_name}
391+
end.
392+
393+
%%--------------------------------------------------------------------
394+
%% @private
395+
%% @doc
396+
%% Delete a user from the name server
397+
%%
398+
%% @spec set_user_name(State, OldName, NewName) -> {ok, #state{}}
399+
%% | {error, Reason}
400+
%% @end
401+
%%--------------------------------------------------------------------
207402

208403
%%--------------------------------------------------------------------
209404
%% @private
210405
%% @doc
211406
%% Generate a new guest username, based on the usernames already in use
212407
%%
213-
%% @spec generate_new_username(UserTable) -> binary()
408+
%% @spec generate_new_username(State) -> binary()
214409
%% @end
215410
%%--------------------------------------------------------------------
216-
generate_new_username(UserTable) ->
217-
Num = get_lowest_unused_guest_num(UserTable),
218-
NumBin = list_to_binary(integer_to_list(Num)),
219-
<<?GUEST, NumBin/binary>>.
411+
generate_new_username(State) ->
412+
UserTable = State#state.users,
413+
Num = get_lowest_unused_guest_num(UserTable),
414+
NumBin = list_to_binary(integer_to_list(Num)),
415+
<<?GUEST, NumBin/binary>>.
220416

221417
%%--------------------------------------------------------------------
222418
%% @private
@@ -227,8 +423,8 @@ generate_new_username(UserTable) ->
227423
%% @end
228424
%%--------------------------------------------------------------------
229425
get_lowest_unused_guest_num(UserTable) ->
230-
GuestNums = ets:foldl(fun collect_guest_num/2, [], UserTable),
231-
lowest_not_in_list(GuestNums).
426+
GuestNums = ets:foldl(fun collect_guest_num/2, [], UserTable),
427+
lowest_not_in_list(GuestNums).
232428

233429
%%--------------------------------------------------------------------
234430
%% @private
@@ -239,14 +435,14 @@ get_lowest_unused_guest_num(UserTable) ->
239435
%% @end
240436
%%--------------------------------------------------------------------
241437
collect_guest_num(UserEntry, NumList) ->
242-
Name = UserEntry#user.name,
243-
case Name of
244-
<<?GUEST, NumBin/binary>> ->
245-
Num = list_to_integer(binary_to_list(NumBin)),
246-
[Num | NumList];
247-
_ ->
248-
NumList
249-
end.
438+
Name = UserEntry#user.name,
439+
case Name of
440+
<<?GUEST, NumBin/binary>> ->
441+
Num = list_to_integer(binary_to_list(NumBin)),
442+
[Num | NumList];
443+
_ ->
444+
NumList
445+
end.
250446

251447
%%--------------------------------------------------------------------
252448
%% @private
@@ -257,8 +453,8 @@ collect_guest_num(UserEntry, NumList) ->
257453
%% @end
258454
%%--------------------------------------------------------------------
259455
lowest_not_in_list(List) ->
260-
SeenArray = array:new(length(List), {default, unseen}),
261-
lowest_acc(List, SeenArray).
456+
SeenArray = array:new(length(List), {default, unseen}),
457+
lowest_acc(List, SeenArray).
262458

263459
%%--------------------------------------------------------------------
264460
%% @private
@@ -270,16 +466,16 @@ lowest_not_in_list(List) ->
270466
%% @end
271467
%%--------------------------------------------------------------------
272468
lowest_acc([Num|Ns], SeenArray) ->
273-
SeenArray1 = case Num-1 < array:size(SeenArray) of
274-
false ->
275-
SeenArray;
276-
true ->
277-
array:set(Num-1, seen, SeenArray)
278-
end,
279-
lowest_acc(Ns, SeenArray1);
469+
SeenArray1 = case Num-1 < array:size(SeenArray) of
470+
false ->
471+
SeenArray;
472+
true ->
473+
array:set(Num-1, seen, SeenArray)
474+
end,
475+
lowest_acc(Ns, SeenArray1);
280476

281477
lowest_acc([], SeenArray) ->
282-
1 + lowest_false_index(0, SeenArray).
478+
1 + lowest_false_index(0, SeenArray).
283479

284480
%%--------------------------------------------------------------------
285481
%% @private
@@ -292,12 +488,93 @@ lowest_acc([], SeenArray) ->
292488
%% @end
293489
%%--------------------------------------------------------------------
294490
lowest_false_index(Idx, SeenArray) ->
295-
case Idx >= array:size(SeenArray) of
296-
true -> Idx;
297-
false ->
298-
case array:get(Idx, SeenArray) of
299-
unseen -> Idx;
300-
seen ->
301-
lowest_false_index(Idx, SeenArray)
302-
end
303-
end.
491+
case Idx >= array:size(SeenArray) of
492+
true -> Idx;
493+
false ->
494+
case array:get(Idx, SeenArray) of
495+
unseen -> Idx;
496+
seen ->
497+
lowest_false_index(Idx, SeenArray)
498+
end
499+
end.
500+
501+
%%%-------------------------------------------------------------------
502+
%%% Room functions
503+
%%%-------------------------------------------------------------------
504+
505+
%%--------------------------------------------------------------------
506+
%% @private
507+
%% @doc
508+
%% Get the process identifier for a room based on its name
509+
%%
510+
%% @spec get_room_pid(State, RoomName) -> {ok, pid()} | no_such_room
511+
%% @end
512+
%%--------------------------------------------------------------------
513+
get_room_pid(State, RoomName) ->
514+
RoomTable = State#state.rooms,
515+
case ets:lookup(RoomTable, RoomName) of
516+
[#room{pid=Pid}] ->
517+
{ok, Pid};
518+
[] ->
519+
no_such_room
520+
end.
521+
522+
%%--------------------------------------------------------------------
523+
%% @private
524+
%% @doc
525+
%% Adds a room to the room table if one with the same name does not
526+
%% already exist
527+
%%
528+
%% @spec add_room(State, Room) -> {ok, #state{}} | {duplicate, #state{}}
529+
%% @end
530+
%%--------------------------------------------------------------------
531+
add_room(State, Room) ->
532+
RoomTable = State#state.rooms,
533+
case ets:insert_new(RoomTable, Room) of
534+
true ->
535+
{ok, State};
536+
_ ->
537+
{duplicate, State}
538+
end.
539+
540+
%%--------------------------------------------------------------------
541+
%% @private
542+
%% @doc
543+
%% Removes a room from the room name table
544+
%%
545+
%% @spec remove_room(State, RoomName) -> state{}
546+
%% @end
547+
%%--------------------------------------------------------------------
548+
remove_room(State, RoomName) ->
549+
ets:delete(State#state.users, RoomName),
550+
State.
551+
552+
%%--------------------------------------------------------------------
553+
%% @private
554+
%% @doc
555+
%% Returns true if a room with the given name already exists, false
556+
%% otherwise
557+
%%
558+
%% @spec room_exists(State, RoomName) -> boolean()
559+
%% @end
560+
%%--------------------------------------------------------------------
561+
room_exists(State, RoomName) ->
562+
RoomTable = State#state.rooms,
563+
ets:member(RoomTable, RoomName).
564+
565+
%%--------------------------------------------------------------------
566+
%% @private
567+
%% @doc
568+
%% Compile a list of room names and number of occupants
569+
%%
570+
%% @spec compile_room_list(State) ->
571+
%% [#{roomid => RoomName, count => OccupantCount}]
572+
%% @end
573+
%%--------------------------------------------------------------------
574+
compile_room_list(State) ->
575+
GetListing = fun (Room, Acc) ->
576+
NumOcc = chat_server_room:count_occupants(Room#room.pid),
577+
Summary = #{roomid => Room#room.name, count => NumOcc},
578+
[Summary | Acc]
579+
end,
580+
ets:foldl(GetListing, [], State#state.rooms).

‎chat_server/src/chat_server_room.erl

+48
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,27 @@ chat_message(RoomPid, SenderName, Message) ->
6161
new_join(RoomPid, SenderPid, SenderName) ->
6262
gen_server:cast(RoomPid, {new_join, SenderName, SenderPid}).
6363

64+
65+
%%--------------------------------------------------------------------
66+
%% @doc
67+
%% Join an already connected user to a room
68+
%%
69+
%% @spec join(RoomPid, SenderPid, SenderName) -> noreply
70+
%% @end
71+
%%--------------------------------------------------------------------
72+
join(RoomPid, SenderPid, SenderName, FromRoomName) ->
73+
gen_server:cast(RoomPid, {join, SenderName, SenderPid, FromRoomName}).
74+
75+
%%--------------------------------------------------------------------
76+
%% @doc
77+
%% Remove a user from a room
78+
%%
79+
%% @spec join(RoomPid, SenderPid, SenderName) -> noreply
80+
%% @end
81+
%%--------------------------------------------------------------------
82+
leave(RoomPid, SenderPid, SenderName, ToRoomName) ->
83+
gen_server:cast(RoomPid, {leave, SenderName, SenderPid, ToRoomName}).
84+
6485
%%%===================================================================
6586
%%% gen_server callbacks
6687
%%%===================================================================
@@ -121,6 +142,21 @@ handle_cast({new_join, SenderName, SenderPid}, State) ->
121142
send_all(NewState#state.users, JoinMsg),
122143
{noreply, NewState};
123144

145+
handle_cast({join, SenderName, SenderPid, FromRoomName}, State) ->
146+
NewState = add_user(State, SenderPid),
147+
Msg = #{type => roomchange, identity => SenderName,
148+
roomid => NewState#state.roomid, former => FromRoomName},
149+
send_all(NewState#state.users, Msg),
150+
{noreply, NewState};
151+
152+
handle_cast({leave, SenderName, SenderPid, ToRoomName}, State) ->
153+
NewState = remove_user(State, SenderPid),
154+
Msg = #{type => roomchange, identity => SenderName,
155+
roomid => ToRoomName, former => State#state.roomid},
156+
send_all(NewState#state.users, Msg),
157+
{noreply, NewState};
158+
159+
124160
handle_cast(_Msg, State) ->
125161
{noreply, State}.
126162

@@ -191,3 +227,15 @@ send_all(UserMap, Message) ->
191227
add_user(State, UserPid) ->
192228
NewUsers = maps:put(UserPid, true, State#state.users),
193229
State#state{users = NewUsers}.
230+
231+
%%--------------------------------------------------------------------
232+
%% @private
233+
%% @doc
234+
%% Remove a user from the room
235+
%%
236+
%% @spec remove_user(State, UserPid) -> #state{}
237+
%% @end
238+
%%--------------------------------------------------------------------
239+
remove_user(State, UserPid) ->
240+
NewUsers = maps:remove(UserPid, State#state.users),
241+
State#state{users = NewUsers}.

‎chat_server/src/chat_server_user.erl

+15-15
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,24 @@
77
-record(state, {name, room, sender}).
88

99
start(Socket) ->
10-
{ok, SendPid} = chat_server_user_out:start_link(Socket),
11-
{ok, Name} = chat_server_nameservice:new_user(),
12-
{ok, MainHall} = chat_server_nameservice:get_room(?MAINHALL),
13-
chat_server_room:new_join(MainHall, SendPid, Name),
14-
State = #state{name = Name, room = MainHall, sender = SendPid},
15-
loop(Socket, State).
10+
{ok, SendPid} = chat_server_user_out:start_link(Socket),
11+
{ok, Name} = chat_server_nameservice:new_user(),
12+
{ok, MainHall} = chat_server_nameservice:get_room(?MAINHALL),
13+
chat_server_room:new_join(MainHall, SendPid, Name),
14+
State = #state{name = Name, room = MainHall, sender = SendPid},
15+
loop(Socket, State).
1616

1717
loop(Socket, State) ->
18-
receive
19-
{tcp, Socket, Bin} ->
20-
io:format("Received message:~n"),
21-
erlang:display(Bin),
22-
Msg = jiffy:decode(Bin, [return_maps]),
23-
process_message(State, Msg)
24-
end,
25-
loop(Socket, State).
18+
receive
19+
{tcp, Socket, Bin} ->
20+
io:format("Received message:~n"),
21+
erlang:display(Bin),
22+
Msg = jiffy:decode(Bin, [return_maps]),
23+
process_message(State, Msg)
24+
end,
25+
loop(Socket, State).
2626

2727
process_message(State,
2828
#{<<"type">> := <<"message">>,
2929
<<"message">> := Msg}) ->
30-
chat_server_room:chat_message(State#state.room, State#state.name, Msg).
30+
chat_server_room:chat_message(State#state.room, State#state.name, Msg).

0 commit comments

Comments
 (0)
Please sign in to comment.