refactor Router.relay to allow sending to multiple targets at once
This commit is contained in:
parent
d399ea73de
commit
7f941a68a1
|
@ -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 ()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue