privmsg between users; better cleanup on quit

This commit is contained in:
tali 2024-01-08 00:05:01 -05:00
parent 5e2384b855
commit 4dc454e9a2
3 changed files with 46 additions and 43 deletions

View File

@ -1,11 +1,11 @@
open! Import open! Import
module User = Router.User
type t = { type t = {
addr : sockaddr; addr : sockaddr;
user : Router.user; user : User.t;
outbox : Irc.Msg.t Lwt_stream.t; outbox : Irc.Msg.t Lwt_stream.t;
push_outbox : (Irc.Msg.t option -> unit); push_outbox : (Irc.Msg.t option -> unit);
quit : unit Lwt_condition.t;
} }
let make ~(router : Router.t) ~(addr : sockaddr) : t = let make ~(router : Router.t) ~(addr : sockaddr) : t =
@ -13,25 +13,22 @@ let make ~(router : Router.t) ~(addr : sockaddr) : t =
| ADDR_INET (ia, _) -> Unix.string_of_inet_addr ia | ADDR_INET (ia, _) -> Unix.string_of_inet_addr ia
| ADDR_UNIX path -> path | ADDR_UNIX path -> path
in in
let user = Router.User.make router ~hostname in let user = User.make router ~hostname in
let outbox, push_outbox = Lwt_stream.create () in let outbox, push_outbox = Lwt_stream.create () in
{ { addr; user; outbox; push_outbox }
addr; user; outbox; push_outbox;
quit = Lwt_condition.create ();
}
let quitting t = Lwt_condition.wait t.quit let outbox t = Lwt_stream.choose [t.outbox; User.inbox t.user]
let outbox t = t.outbox
let send t msg = try t.push_outbox (Some msg) with Lwt_stream.Closed -> () let send t msg = try t.push_outbox (Some msg) with Lwt_stream.Closed -> ()
let close t = try t.push_outbox None with Lwt_stream.Closed -> ()
let cleanup t = let cleanup t =
t.push_outbox None; close t;
Router.User.cleanup t.user User.cleanup t.user
(* message handling *) (* message handling *)
let require_registered t f = let require_registered t f =
if Router.User.is_registered t.user then if User.is_registered t.user then
f (Option.get t.user.userinfo) f (Option.get t.user.userinfo)
else else
`notregistered `notregistered
@ -41,18 +38,21 @@ let require_registered t f =
let attempt_to_register t = let attempt_to_register t =
match t.user.nick, t.user.userinfo with match t.user.nick, t.user.userinfo with
| Some nick, Some _userinfo -> | Some nick, Some _userinfo ->
begin match Router.User.set_nick t.user nick with begin match User.set_nick t.user nick with
| `nick_in_use -> `nicknameinuse nick | `nick_in_use -> `nicknameinuse nick
| `nick_set -> | `nick_set ->
Logs.debug (fun m -> m "init mode: +%a" Irc.Mode.pp t.user.mode); Logs.debug (fun m -> m "init mode: +%a" Irc.Mode.pp t.user.mode);
User.send t.user
(Irc.Msg.make "MODE" [nick; Fmt.str "+%a" Irc.Mode.pp t.user.mode]
~prefix:(User_prefix (nick, None, None)));
(* TODO: set initial mode *) (* TODO: set initial mode *)
`welcome `welcome
end end
| _, _ -> `ok | _, _ -> `ok
let on_msg_nick t nick = let on_msg_nick t nick =
if Router.User.is_registered t.user then if User.is_registered t.user then
match Router.User.set_nick t.user nick with match User.set_nick t.user nick with
| `nick_in_use -> `nicknameinuse nick | `nick_in_use -> `nicknameinuse nick
| `nick_set -> `ok | `nick_set -> `ok
else begin else begin
@ -61,10 +61,10 @@ let on_msg_nick t nick =
end end
let on_msg_user t username modestr realname = let on_msg_user t username modestr realname =
if Router.User.is_registered t.user then if User.is_registered t.user then
`alreadyregistered `alreadyregistered
else begin else begin
(* NB: +iw is automatically set, so it's impossible to actually affect the initial (* NB: "+iw" is automatically set, so it's impossible to actually affect the initial
mode with the parameter to USER *) mode with the parameter to USER *)
ignore modestr; ignore modestr;
t.user.userinfo <- Some { username; realname }; t.user.userinfo <- Some { username; realname };
@ -73,13 +73,10 @@ let on_msg_user t username modestr realname =
(* > messages and channels *) (* > messages and channels *)
let on_msg_privmsg t tgt msg _userinfo = let on_msg_privmsg t tgt msg _ =
match Router.find_user t.user.router tgt with match Router.find_user t.user.router tgt with
| None -> `nosuchnick tgt | None -> `nosuchnick tgt
| Some _ -> | Some dst -> Router.privmsg t.user (`user dst) msg; `ok
let _ = msg in
(* TODO: send messages *)
`tryagain
let on_msg_privmsg t tgt msg = require_registered t (on_msg_privmsg t tgt msg) let on_msg_privmsg t tgt msg = require_registered t (on_msg_privmsg t tgt msg)
@ -88,8 +85,8 @@ let on_msg_privmsg t tgt msg = require_registered t (on_msg_privmsg t tgt msg)
let on_msg_quit t why = let on_msg_quit t why =
let why = String.concat " " why in let why = String.concat " " why in
Logs.debug (fun m -> m "%a: quit: %S" pp_sockaddr t.addr why); Logs.debug (fun m -> m "%a: quit: %S" pp_sockaddr t.addr why);
Lwt_condition.broadcast t.quit (); (* broadcast "quit" message *)
`ok `quit
(* message sending *) (* message sending *)
@ -105,13 +102,13 @@ let srv_motd_lines = [
let rpl t cmd params = let rpl t cmd params =
let prefix = Irc.Msg.Server_prefix srv_host in let prefix = Irc.Msg.Server_prefix srv_host in
let target = Router.User.nick t.user in let target = User.nick t.user in
send t (Irc.Msg.make ~prefix cmd (target :: params)) send t (Irc.Msg.make ~prefix cmd (target :: params))
let rpl_tryagain t cmd = rpl t "263" [cmd; "Please wait a while and try again."] let rpl_tryagain t cmd = rpl t "263" [cmd; "Please wait a while and try again."]
let rpl_welcome t = let rpl_welcome t =
let who = Irc.Msg.prefix_string (Router.User.prefix t.user) in let who = Irc.Msg.prefix_string (User.prefix t.user) in
begin begin
rpl t "001" [Fmt.str "Welcome to the tali IRC network %s" who]; rpl t "001" [Fmt.str "Welcome to the tali IRC network %s" who];
rpl t "002" [Fmt.str "Your host is %s, running version %s" srv_host srv_ver]; rpl t "002" [Fmt.str "Your host is %s, running version %s" srv_host srv_ver];
@ -155,6 +152,7 @@ let on_msg t (msg : Irc.Msg.t) : unit =
in in
match result with match result with
| `ok -> () | `ok -> ()
| `quit -> close t
| `welcome -> rpl_welcome t; rpl_motd t | `welcome -> rpl_welcome t; rpl_motd t
| `tryagain -> rpl_tryagain t msg.command | `tryagain -> rpl_tryagain t msg.command
| `alreadyregistered -> err_alreadyregistered t | `alreadyregistered -> err_alreadyregistered t

View File

@ -63,15 +63,14 @@ let handle_client (router : Router.t) (conn_fd : fd) (conn_addr : sockaddr) =
in in
let rd = Lwt_stream.iter (Connection.on_msg conn) (reader conn_fd) in let rd = Lwt_stream.iter (Connection.on_msg conn) (reader conn_fd) in
let wr = writer conn_fd (Connection.outbox conn) in let wr = writer conn_fd (Connection.outbox conn) in
let qt = Connection.quitting conn in
Lwt.finalize Lwt.finalize
(fun () -> (fun () ->
Logs.info (fun m -> m "new connection %a" pp_sockaddr conn_addr); Logs.info (fun m -> m "new connection %a" pp_sockaddr conn_addr);
Lwt.choose [rd; wr; qt]) Lwt.choose [rd; wr])
(fun () -> (fun () ->
Logs.info (fun m -> m "connection closed %a" pp_sockaddr conn_addr);
Connection.cleanup conn; Connection.cleanup conn;
Lwt_unix.close conn_fd) Lwt_unix.close conn_fd >|= fun () ->
Logs.info (fun m -> m "connection closed %a" pp_sockaddr conn_addr))
type config = { type config = {
port : int; port : int;

View File

@ -2,11 +2,6 @@ type nick_key = Nick_key of string [@@unboxed]
let nick_key n = Nick_key (String.lowercase_ascii n) (* TODO: "scandinavian" lowercase *) let nick_key n = Nick_key (String.lowercase_ascii n) (* TODO: "scandinavian" lowercase *)
let unset = Nick_key "" let unset = Nick_key ""
(* TODO: notifications *)
(* type notif = *)
(* | Mode of Irc.nick * Irc.nick * Irc.Mode.t *)
(* | Nick of Irc.Msg.prefix * Irc.nick *)
type t = { type t = {
users : (nick_key, user) Hashtbl.t users : (nick_key, user) Hashtbl.t
(* TODO: channels *) (* TODO: channels *)
@ -19,8 +14,8 @@ and user = {
mutable nick : Irc.nick option; mutable nick : Irc.nick option;
mutable userinfo : Irc.userinfo option; mutable userinfo : Irc.userinfo option;
mutable mode : Irc.Mode.t; mutable mode : Irc.Mode.t;
(* inbox : notif Lwt_stream.t; *) inbox : Irc.Msg.t Lwt_stream.t;
(* push_inbox : (notif option -> unit); *) push_inbox : (Irc.Msg.t option -> unit);
} }
let make () = let make () =
@ -33,7 +28,7 @@ module User = struct
type t = user type t = user
let make router ~hostname = let make router ~hostname =
(* let inbox, push_inbox = Lwt_stream.create () in *) let inbox, push_inbox = Lwt_stream.create () in
{ {
router; router;
hostname; hostname;
@ -41,11 +36,14 @@ module User = struct
nick = None; nick = None;
userinfo = None; userinfo = None;
mode = Irc.Mode.of_string "iw"; mode = Irc.Mode.of_string "iw";
(* inbox; push_inbox; *) inbox; push_inbox;
} }
let nick t = Option.value t.nick ~default:"*" let inbox t = t.inbox
let send t msg = try t.push_inbox (Some msg) with Lwt_stream.Closed -> ()
let close t = try t.push_inbox None with Lwt_stream.Closed -> ()
let nick t = Option.value t.nick ~default:"*"
let prefix t : Irc.Msg.prefix = let prefix t : Irc.Msg.prefix =
match t.nick with match t.nick with
| None -> No_prefix | None -> No_prefix
@ -69,7 +67,15 @@ module User = struct
`nick_set `nick_set
end end
let cleanup user = let cleanup t =
unset_nick user (* TODO: notify other users of quit *)
(* user.push_inbox None *) close t;
unset_nick t
end end
let privmsg src dst txt =
match dst with
| `user dst ->
let prefix = User.prefix src in
let msg = Irc.Msg.make "PRIVMSG" [User.nick dst; txt] ~prefix in
User.send dst msg