14
14
-export ([start_link /0 ,
15
15
% User functions
16
16
new_user /0 ,
17
+ get_user /1 ,
18
+ change_user_name /2 ,
19
+ delete_user /1 ,
17
20
% 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 ]).
19
26
20
27
% % gen_server callbacks
21
28
-export ([init /1 ,
@@ -62,6 +69,36 @@ start_link() ->
62
69
new_user () ->
63
70
gen_server :call (? SERVER , new_user ).
64
71
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
+
65
102
% %--------------------------------------------------------------------
66
103
% % @doc
67
104
% % Get the Pid of a room based on its name
@@ -72,6 +109,47 @@ new_user() ->
72
109
get_room (RoomName ) ->
73
110
gen_server :call (? SERVER , {get_room , RoomName }).
74
111
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
+
75
153
% %%===================================================================
76
154
% %% gen_server callbacks
77
155
% %%===================================================================
@@ -91,8 +169,8 @@ init([]) ->
91
169
State = # state {},
92
170
{ok , MainHall } = chat_server_room :new (? MAINHALL , <<>>),
93
171
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 }.
96
174
97
175
% %--------------------------------------------------------------------
98
176
% % @private
@@ -109,15 +187,71 @@ init([]) ->
109
187
% % @end
110
188
% %--------------------------------------------------------------------
111
189
handle_call (new_user , {UPid , _Tag }, State ) ->
112
- Name = generate_new_username (State # state . users ),
190
+ Name = generate_new_username (State ),
113
191
User = # user {pid = UPid , name = Name },
114
192
NewState = add_user (State , User ),
115
193
{reply , {ok , Name }, NewState };
116
194
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
+
117
217
handle_call ({get_room , RoomName }, _From , State ) ->
118
- Reply = get_room_pid (State # state . rooms , RoomName ),
218
+ Reply = get_room_pid (State , RoomName ),
119
219
{reply , Reply , State };
120
220
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
+
121
255
handle_call (_Request , _From , State ) ->
122
256
Reply = ok ,
123
257
{reply , Reply , State }.
@@ -177,18 +311,6 @@ code_change(_OldVsn, State, _Extra) ->
177
311
% %% Internal functions
178
312
% %%===================================================================
179
313
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
-
192
314
% %%-------------------------------------------------------------------
193
315
% %% User functions
194
316
% %%-------------------------------------------------------------------
@@ -198,25 +320,99 @@ get_room_pid(RoomTable, RoomName) ->
198
320
% % @doc
199
321
% % Add a new user to the user name table
200
322
% %
201
- % % @spec add_user(UserTable , UserRecord) -> boolean()
323
+ % % @spec add_user(State , UserRecord) -> #state{}
202
324
% % @end
203
325
% %--------------------------------------------------------------------
204
326
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
+ % %--------------------------------------------------------------------
207
402
208
403
% %--------------------------------------------------------------------
209
404
% % @private
210
405
% % @doc
211
406
% % Generate a new guest username, based on the usernames already in use
212
407
% %
213
- % % @spec generate_new_username(UserTable ) -> binary()
408
+ % % @spec generate_new_username(State ) -> binary()
214
409
% % @end
215
410
% %--------------------------------------------------------------------
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 >>.
220
416
221
417
% %--------------------------------------------------------------------
222
418
% % @private
@@ -227,8 +423,8 @@ generate_new_username(UserTable) ->
227
423
% % @end
228
424
% %--------------------------------------------------------------------
229
425
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 ).
232
428
233
429
% %--------------------------------------------------------------------
234
430
% % @private
@@ -239,14 +435,14 @@ get_lowest_unused_guest_num(UserTable) ->
239
435
% % @end
240
436
% %--------------------------------------------------------------------
241
437
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 .
250
446
251
447
% %--------------------------------------------------------------------
252
448
% % @private
@@ -257,8 +453,8 @@ collect_guest_num(UserEntry, NumList) ->
257
453
% % @end
258
454
% %--------------------------------------------------------------------
259
455
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 ).
262
458
263
459
% %--------------------------------------------------------------------
264
460
% % @private
@@ -270,16 +466,16 @@ lowest_not_in_list(List) ->
270
466
% % @end
271
467
% %--------------------------------------------------------------------
272
468
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 );
280
476
281
477
lowest_acc ([], SeenArray ) ->
282
- 1 + lowest_false_index (0 , SeenArray ).
478
+ 1 + lowest_false_index (0 , SeenArray ).
283
479
284
480
% %--------------------------------------------------------------------
285
481
% % @private
@@ -292,12 +488,93 @@ lowest_acc([], SeenArray) ->
292
488
% % @end
293
489
% %--------------------------------------------------------------------
294
490
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 ).
0 commit comments