this repo has no description

web: only format sse one time, then send formatted msg to all streams

rockorager.dev 76356eb5 68611dad

verified
+69 -60
+1 -4
src/Server.zig
··· 1257 1257 } 1258 1258 1259 1259 // Send message to any http clients. 1260 - for (channel.streams.items) |es| { 1261 - try es.printMessage(self.gpa, source, msg); 1262 - try self.queueWriteEventStream(es); 1263 - } 1260 + try channel.sendPrivMsgToStreams(self, source, msg); 1264 1261 1265 1262 for (channel.members.items) |m| { 1266 1263 const u = m.user;
+3 -56
src/http.zig
··· 4 4 const uuid = @import("uuid"); 5 5 const xev = @import("xev"); 6 6 7 + const Allocator = std.mem.Allocator; 7 8 const log = @import("log.zig"); 8 9 const irc = @import("irc.zig"); 9 10 const IrcServer = @import("Server.zig"); ··· 91 92 /// the write - and then we can dispose of the connection 92 93 write_c: xev.Completion, 93 94 94 - state: State = .{}, 95 - 96 - /// We store some state in the EventStream because it's view is stateful. It's essentially an 97 - /// IRC client 98 - const State = struct { 99 - last_sender: ?*irc.User = null, 100 - last_timestamp: irc.Timestamp = .{ .milliseconds = 0 }, 101 - }; 102 - 103 95 pub fn print( 104 96 self: *EventStream, 105 97 gpa: std.mem.Allocator, ··· 109 101 return self.write_buf.writer(gpa).print(format, args); 110 102 } 111 103 112 - pub fn printMessage( 113 - self: *EventStream, 114 - gpa: std.mem.Allocator, 115 - sender: *irc.User, 116 - msg: irc.Message, 117 - ) std.mem.Allocator.Error!void { 118 - const state = &self.state; 119 - const cmd = msg.command(); 120 - 121 - const sender_sanitized: sanitize.Html = .{ .bytes = sender.nick }; 122 - 123 - if (std.ascii.eqlIgnoreCase(cmd, "PRIVMSG")) { 124 - defer { 125 - // save the state 126 - self.state.last_sender = sender; 127 - self.state.last_timestamp = msg.timestamp; 128 - } 129 - 130 - // Parse the message 131 - var iter = msg.paramIterator(); 132 - _ = iter.next(); // we can ignore the target 133 - const content = iter.next() orelse return; 134 - const san_content: sanitize.Html = .{ .bytes = content }; 135 - 136 - // We don't reprint the sender if the last message this message are from the same 137 - // person. Unless enough time has elapsed (5 minutes) 138 - if (state.last_sender == sender and 139 - (state.last_timestamp.milliseconds + 5 * std.time.ms_per_min) >= msg.timestamp.milliseconds) 140 - { 141 - const fmt = 142 - \\event: message 143 - \\data: <div class="message"><p class="body">{s}</p></div> 144 - \\ 145 - \\ 146 - ; 147 - return self.print(gpa, fmt, .{san_content}); 148 - } 149 - const fmt = 150 - \\event: message 151 - \\data: <div class="message"><p class="nick"><b>{s}</b></p><p class="body">{s}</p></div> 152 - \\ 153 - \\ 154 - ; 155 - return self.print(gpa, fmt, .{ sender_sanitized, san_content }); 156 - } 157 - 158 - // TODO: other types of messages 104 + pub fn writeAll(self: *EventStream, gpa: Allocator, buf: []const u8) Allocator.Error!void { 105 + return self.write_buf.appendSlice(gpa, buf); 159 106 } 160 107 }; 161 108
+65
src/irc.zig
··· 6 6 const db = @import("db.zig"); 7 7 const http = @import("http.zig"); 8 8 const log = @import("log.zig"); 9 + const sanitize = @import("sanitize.zig"); 9 10 10 11 const Allocator = std.mem.Allocator; 11 12 const Connection = Server.Connection; ··· 452 453 members: std.ArrayListUnmanaged(Member), 453 454 streams: std.ArrayListUnmanaged(*http.EventStream), 454 455 456 + state: State = .{}, 457 + 458 + // We store some state so we can format messages to event streams better. The event stream 459 + // clients are stateless, we just send them messages and they render it 460 + const State = struct { 461 + last_sender: ?*User = null, 462 + last_timestamp: Timestamp = .{ .milliseconds = 0 }, 463 + }; 464 + 455 465 const Member = struct { 456 466 user: *User, 457 467 privileges: ChannelPrivileges, ··· 802 812 ); 803 813 return; 804 814 } 815 + } 816 + } 817 + 818 + pub fn sendPrivMsgToStreams( 819 + self: *Channel, 820 + server: *Server, 821 + sender: *User, 822 + msg: Message, 823 + ) Allocator.Error!void { 824 + // We'll write the format once into buf. Then copy this to each stream for writing to the 825 + // stream 826 + var buf: std.ArrayListUnmanaged(u8) = .empty; 827 + defer buf.deinit(server.gpa); 828 + var writer = buf.writer(server.gpa); 829 + 830 + const sender_sanitized: sanitize.Html = .{ .bytes = sender.nick }; 831 + 832 + defer { 833 + // save the state 834 + self.state.last_sender = sender; 835 + self.state.last_timestamp = msg.timestamp; 836 + } 837 + 838 + // Parse the message 839 + var iter = msg.paramIterator(); 840 + _ = iter.next(); // we can ignore the target 841 + const content = iter.next() orelse return; 842 + const content_sanitized: sanitize.Html = .{ .bytes = content }; 843 + 844 + // We don't reprint the sender if the last message this message are from the same 845 + // person. Unless enough time has elapsed (5 minutes) 846 + if (self.state.last_sender == sender and 847 + (self.state.last_timestamp.milliseconds + 5 * std.time.ms_per_min) >= msg.timestamp.milliseconds) 848 + { 849 + const fmt = 850 + \\event: message 851 + \\data: <div class="message"><p class="body">{s}</p></div> 852 + \\ 853 + \\ 854 + ; 855 + try writer.print(fmt, .{content_sanitized}); 856 + } else { 857 + const fmt = 858 + \\event: message 859 + \\data: <div class="message"><p class="nick"><b>{s}</b></p><p class="body">{s}</p></div> 860 + \\ 861 + \\ 862 + ; 863 + try writer.print(fmt, .{ sender_sanitized, content_sanitized }); 864 + } 865 + 866 + // Now the buf has the text we want to send to each stream. 867 + for (self.streams.items) |stream| { 868 + try stream.writeAll(server.gpa, buf.items); 869 + try server.queueWriteEventStream(stream); 805 870 } 806 871 } 807 872 };