From 4dc454e9a27145fad126c5904153256cb4ea1719 Mon Sep 17 00:00:00 2001 From: tali Date: Mon, 8 Jan 2024 00:05:01 -0500 Subject: [PATCH] privmsg between users; better cleanup on quit --- lib/server/connection.ml | 50 +++++++++++++++++++--------------------- lib/server/irc_server.ml | 7 +++--- lib/server/router.ml | 32 ++++++++++++++----------- 3 files changed, 46 insertions(+), 43 deletions(-) diff --git a/lib/server/connection.ml b/lib/server/connection.ml index 9ed7cf9..45852da 100644 --- a/lib/server/connection.ml +++ b/lib/server/connection.ml @@ -1,11 +1,11 @@ open! Import +module User = Router.User type t = { addr : sockaddr; - user : Router.user; + user : User.t; outbox : Irc.Msg.t Lwt_stream.t; push_outbox : (Irc.Msg.t option -> unit); - quit : unit Lwt_condition.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_UNIX path -> path in - let user = Router.User.make router ~hostname in + let user = User.make router ~hostname in let outbox, push_outbox = Lwt_stream.create () in - { - addr; user; outbox; push_outbox; - quit = Lwt_condition.create (); - } + { addr; user; outbox; push_outbox } -let quitting t = Lwt_condition.wait t.quit -let outbox t = t.outbox +let outbox t = Lwt_stream.choose [t.outbox; User.inbox t.user] 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 = - t.push_outbox None; - Router.User.cleanup t.user + close t; + User.cleanup t.user (* message handling *) 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) else `notregistered @@ -41,18 +38,21 @@ let require_registered t f = let attempt_to_register t = match t.user.nick, t.user.userinfo with | 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_set -> 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 *) `welcome end | _, _ -> `ok let on_msg_nick t nick = - if Router.User.is_registered t.user then - match Router.User.set_nick t.user nick with + if User.is_registered t.user then + match User.set_nick t.user nick with | `nick_in_use -> `nicknameinuse nick | `nick_set -> `ok else begin @@ -61,10 +61,10 @@ let on_msg_nick t nick = end let on_msg_user t username modestr realname = - if Router.User.is_registered t.user then + if User.is_registered t.user then `alreadyregistered 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 *) ignore modestr; t.user.userinfo <- Some { username; realname }; @@ -73,13 +73,10 @@ let on_msg_user t username modestr realname = (* > 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 | None -> `nosuchnick tgt - | Some _ -> - let _ = msg in - (* TODO: send messages *) - `tryagain + | Some dst -> Router.privmsg t.user (`user dst) msg; `ok 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 why = String.concat " " why in Logs.debug (fun m -> m "%a: quit: %S" pp_sockaddr t.addr why); - Lwt_condition.broadcast t.quit (); - `ok + (* broadcast "quit" message *) + `quit (* message sending *) @@ -105,13 +102,13 @@ let srv_motd_lines = [ let rpl t cmd params = 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)) let rpl_tryagain t cmd = rpl t "263" [cmd; "Please wait a while and try again."] 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 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]; @@ -155,6 +152,7 @@ let on_msg t (msg : Irc.Msg.t) : unit = in match result with | `ok -> () + | `quit -> close t | `welcome -> rpl_welcome t; rpl_motd t | `tryagain -> rpl_tryagain t msg.command | `alreadyregistered -> err_alreadyregistered t diff --git a/lib/server/irc_server.ml b/lib/server/irc_server.ml index ecf68a5..f9325bf 100644 --- a/lib/server/irc_server.ml +++ b/lib/server/irc_server.ml @@ -63,15 +63,14 @@ let handle_client (router : Router.t) (conn_fd : fd) (conn_addr : sockaddr) = in let rd = Lwt_stream.iter (Connection.on_msg conn) (reader conn_fd) in let wr = writer conn_fd (Connection.outbox conn) in - let qt = Connection.quitting conn in Lwt.finalize (fun () -> Logs.info (fun m -> m "new connection %a" pp_sockaddr conn_addr); - Lwt.choose [rd; wr; qt]) + Lwt.choose [rd; wr]) (fun () -> - Logs.info (fun m -> m "connection closed %a" pp_sockaddr conn_addr); 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 = { port : int; diff --git a/lib/server/router.ml b/lib/server/router.ml index a8766ec..61f5f16 100644 --- a/lib/server/router.ml +++ b/lib/server/router.ml @@ -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 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 = { users : (nick_key, user) Hashtbl.t (* TODO: channels *) @@ -19,8 +14,8 @@ and user = { mutable nick : Irc.nick option; mutable userinfo : Irc.userinfo option; mutable mode : Irc.Mode.t; - (* inbox : notif Lwt_stream.t; *) - (* push_inbox : (notif option -> unit); *) + inbox : Irc.Msg.t Lwt_stream.t; + push_inbox : (Irc.Msg.t option -> unit); } let make () = @@ -33,7 +28,7 @@ module User = struct type t = user let make router ~hostname = - (* let inbox, push_inbox = Lwt_stream.create () in *) + let inbox, push_inbox = Lwt_stream.create () in { router; hostname; @@ -41,11 +36,14 @@ module User = struct nick = None; userinfo = None; 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 = match t.nick with | None -> No_prefix @@ -69,7 +67,15 @@ module User = struct `nick_set end - let cleanup user = - unset_nick user - (* user.push_inbox None *) + let cleanup t = + (* TODO: notify other users of quit *) + close t; + unset_nick t 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