refactor Router.relay to allow sending to multiple targets at once

This commit is contained in:
tali 2024-01-14 13:21:16 -05:00
parent d399ea73de
commit 7f941a68a1
3 changed files with 63 additions and 48 deletions

View File

@ -24,7 +24,7 @@ let shutdown ?reason t =
Option.iter Option.iter
(fun me -> (fun me ->
let reason = Option.value reason ~default:"Goot bye" in let reason = Option.value reason ~default:"Goot bye" in
Router.relay (Irc.Msg.make "QUIT" [reason]) ~from:me (`to_interested me); Router.relay (Irc.Msg.make "QUIT" [reason]) ~from:me [`to_interested];
User.part_all me; User.part_all me;
(* TODO: BUG: unregister empty channels *) (* TODO: BUG: unregister empty channels *)
User.unregister me ~router:t.router) User.unregister me ~router:t.router)
@ -109,7 +109,7 @@ let on_msg_nick ~welcome t nick =
match match
User.set_nick me nick User.set_nick me nick
~router:t.router ~router:t.router
~success_callback:(fun () -> Router.relay msg ~from:me (`to_interested me)) ~success_callback:(fun () -> Router.relay msg ~from:me [`to_interested])
with with
| `nick_in_use -> Error (nicknameinuse nick) | `nick_in_use -> Error (nicknameinuse nick)
| `nick_set -> Ok () | `nick_set -> Ok ()
@ -133,21 +133,26 @@ let on_msg_user ~welcome t username realname =
(* messages and channels *) (* messages and channels *)
let on_msg_privmsg t tgt txt = let on_msg_privmsg t name txt =
let* me = require_registered t in let* me = require_registered t in
let* dst = let* tgt =
try try
match Irc.name_type tgt with match Irc.name_type name with
| `chan -> Ok (`to_chan (Router.find_chan t.router tgt)) | `chan -> Ok (`chan (Router.find_chan t.router name))
| _ -> Ok (`to_user (Router.find_user t.router tgt)) | _ -> Ok (`user (Router.find_user t.router name))
with Not_found -> with Not_found ->
Error (nosuchnick tgt) Error (nosuchnick name)
in in
(* TODO: check if user is away *) (* TODO: check if user is away *)
(* TODO: check if channel is +n and user is not a member *) (* TODO: check if channel is +n and user is not a member *)
(* TODO: check if channel is +m and user is not priviledged *) (* TODO: check if channel is +m and user is not priviledged *)
(* TODO: check if channel is +b <user> *) (* TODO: check if channel is +b <user> *)
let msg = Irc.Msg.make "PRIVMSG" [tgt; txt] ~always_trailing:true in let name, dst =
match tgt with
| `chan c -> Chan.name c, [`to_chan c]
| `user u -> User.nick u, [`to_user u]
in
let msg = Irc.Msg.make "PRIVMSG" [name; txt] ~always_trailing:true in
Router.relay msg ~from:me dst; Router.relay msg ~from:me dst;
Ok () Ok ()
@ -194,8 +199,7 @@ let on_msg_join t name =
(* TODO: check if channel is +k *) (* TODO: check if channel is +k *)
Chan.join chan me; Chan.join chan me;
let msg = Irc.Msg.make "JOIN" [name] in let msg = Irc.Msg.make "JOIN" [name] in
Router.relay msg ~from:me `to_self; Router.relay msg ~from:me [`to_chan chan; `to_self];
Router.relay msg ~from:me (`to_chan chan);
(* TODO: send channel topic *) (* TODO: send channel topic *)
list_names t chan; list_names t chan;
Ok () Ok ()
@ -213,8 +217,7 @@ let on_msg_part t name =
in in
let* () = if Chan.is_member chan me then Ok () else Error (notonchannel name) in let* () = if Chan.is_member chan me then Ok () else Error (notonchannel name) in
let msg = Irc.Msg.make "PART" [name] in let msg = Irc.Msg.make "PART" [name] in
Router.relay msg ~from:me `to_self; Router.relay msg ~from:me [`to_chan chan; `to_self];
Router.relay msg ~from:me (`to_chan chan);
Chan.part chan me; Chan.part chan me;
if Chan.no_members chan then begin if Chan.no_members chan then begin
debug (fun m -> m "recycling channel %S" name); debug (fun m -> m "recycling channel %S" name);
@ -228,7 +231,7 @@ let on_msg_part t name =
let user_get_mode user me = let user_get_mode user me =
let modestr = Fmt.str "+%a" Irc.Mode.Set.pp (User.mode user) in let modestr = Fmt.str "+%a" Irc.Mode.Set.pp (User.mode user) in
let msg = Irc.Msg.make "MODE" [User.nick user; modestr] ~always_trailing:true in let msg = Irc.Msg.make "MODE" [User.nick user; modestr] ~always_trailing:true in
Router.relay msg ~from:me `to_self; Router.relay msg ~from:me [`to_self];
Ok () Ok ()
let user_set_mode user me modestr _args = let user_set_mode user me modestr _args =
@ -248,8 +251,7 @@ let user_set_mode user me modestr _args =
mode, Fmt.str "%a" Irc.Mode.Parse.pp_user_mode_set { add; rem } mode, Fmt.str "%a" Irc.Mode.Parse.pp_user_mode_set { add; rem }
in in
let msg = Irc.Msg.make "MODE" [User.nick user; modestr] ~always_trailing:true in let msg = Irc.Msg.make "MODE" [User.nick user; modestr] ~always_trailing:true in
(* TODO: if setting mode for user besides self, notify them too *) Router.relay msg ~from:me [`to_user user; `to_self];
Router.relay msg ~from:me `to_self;
User.set_mode user mode; User.set_mode user mode;
Ok () Ok ()

View File

@ -5,30 +5,35 @@ open! Import
type t = { type t = {
stream : Irc.Msg.t Lwt_stream.t; stream : Irc.Msg.t Lwt_stream.t;
push : Irc.Msg.t option -> unit; push : Irc.Msg.t option -> unit;
mutable bcc : t Dllist.node option; mutable bcc_incl : bool;
} }
let make () = let make () =
let stream, push = Lwt_stream.create () in let stream, push = Lwt_stream.create () in
{ stream; push; bcc = None } { stream; push; bcc_incl = false }
let stream t = t.stream let stream t = t.stream
let send t msg = try t.push (Some msg) with Lwt_stream.Closed -> () let send t msg = try t.push (Some msg) with Lwt_stream.Closed -> ()
let close t = try t.push None with Lwt_stream.Closed -> () let close t = try t.push None with Lwt_stream.Closed -> ()
module Bcc = struct module Bcc = struct
(** this module is used to send a message to a number of outboxes at once without sending
to the same destination twice. it uses a global linked list of recipients with an
intrusive bool ([outbox.bcc_incl]) to prevent enqueuing the same destination twice.
[Bcc.add a; Bcc.add b; Bcc.send_all msg;
(* sends [msg] to [a] and [b]. *)]
*)
let _recipients = Dllist.create () let _recipients = Dllist.create ()
let incl obx = let add obx =
Option.iter Dllist.remove obx.bcc; if not obx.bcc_incl then
obx.bcc <- Some (Dllist.add_r obx _recipients) let _ = Dllist.add_r obx _recipients in
obx.bcc_incl <- true
let excl obx =
Option.iter Dllist.remove obx.bcc;
obx.bcc <- None
let rec send_all msg = let rec send_all msg =
match Dllist.take_l _recipients with match Dllist.take_l _recipients with
| obx -> obx.bcc <- None; send obx msg; send_all msg | obx -> obx.bcc_incl <- false; send obx msg; send_all msg
| exception Dllist.Empty -> () | exception Dllist.Empty -> ()
end end

View File

@ -43,25 +43,32 @@ let find_chan t name =
let user_prefix user = let user_prefix user =
Irc.Msg.User_prefix (user.nick, Some user.userinfo) Irc.Msg.User_prefix (user.nick, Some user.userinfo)
let relay ~(from : user) (msg : Irc.Msg.t) target = let chan_members chan =
let msg = { msg with prefix = user_prefix from } in Dllist.fold_r (fun m xs -> m.mem_user :: xs) chan.members []
match target with
| `to_self -> let user_channels user =
Outbox.send from.outbox msg Dllist.fold_r (fun m xs -> m.mem_chan :: xs) user.membership []
| `to_user dst ->
Outbox.send dst.outbox msg let relay ~(from : user) (msg : Irc.Msg.t) tgts =
| `to_chan dst -> let msg =
Dllist.iter_l (fun m -> Outbox.Bcc.incl m.mem_user.outbox) dst.members; if msg.prefix = No_prefix then
Outbox.Bcc.excl from.outbox; { msg with prefix = user_prefix from }
Outbox.Bcc.send_all msg else msg
| `to_interested user -> in
Dllist.iter_l let bcc u = Outbox.Bcc.add u.outbox in
(fun m -> let bcc_not_self u = if u != from then bcc u in
Dllist.iter_l let bcc_channel c = List.iter bcc_not_self (chan_members c) in
(fun m -> Outbox.Bcc.incl m.mem_user.outbox) List.iter
m.mem_chan.members) (function
user.membership; | `to_self -> bcc from
Outbox.Bcc.send_all msg | `to_user tgt -> bcc tgt
| `to_chan tgt -> bcc_channel tgt
| `to_interested -> bcc from; List.iter bcc_channel (user_channels from))
tgts;
Outbox.Bcc.send_all msg
(* TODO: split out [User] and [Chan] into separate files *)
module User = struct module User = struct
type t = user type t = user
@ -79,6 +86,7 @@ module User = struct
let outbox t = t.outbox let outbox t = t.outbox
let nick t = t.nick let nick t = t.nick
let mode t = t.mode let mode t = t.mode
let channels = user_channels
let prefix = user_prefix let prefix = user_prefix
(* let is_registered t = t.nick_key <> empty_string_ci *) (* let is_registered t = t.nick_key <> empty_string_ci *)
@ -104,6 +112,7 @@ module User = struct
t.mode <- new_mode t.mode <- new_mode
let rec part_all t = let rec part_all t =
(* List.iter (fun c -> Chan.part c t) (channels t) *)
match Dllist.take_l t.membership with match Dllist.take_l t.membership with
| m -> | m ->
Option.iter Dllist.remove m.mem_in_chan; Option.iter Dllist.remove m.mem_in_chan;
@ -112,6 +121,7 @@ module User = struct
() ()
end end
module Chan = struct module Chan = struct
type t = chan type t = chan
@ -125,6 +135,7 @@ module Chan = struct
let name t = t.name let name t = t.name
let topic t = t.topic let topic t = t.topic
let members = chan_members
let no_members t = Dllist.is_empty t.members let no_members t = Dllist.is_empty t.members
let register t ~router = let register t ~router =
@ -133,9 +144,6 @@ module Chan = struct
let unregister t ~router = let unregister t ~router =
Hashtbl.remove router.channels t.name_key Hashtbl.remove router.channels t.name_key
let members t =
Dllist.fold_r (fun m xs -> m.mem_user :: xs) t.members []
let is_member t user = let is_member t user =
let is_mem m = m.mem_chan == t in let is_mem m = m.mem_chan == t in
try try