-
Notifications
You must be signed in to change notification settings - Fork 3
/
ws_utils.go
147 lines (138 loc) · 3.51 KB
/
ws_utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
* WebSocket auxiliary functions
*
* Copyright (C) 2024 Runxi Yu <https://runxiyu.org>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package main
import (
"context"
"fmt"
"log/slog"
"sync/atomic"
"github.com/coder/websocket"
)
/*
* The message format is a WebSocket message separated with spaces.
* The contents of each field could contain anything other than spaces,
* The first character of each argument cannot be a colon. As an exception, the
* last argument may contain spaces and the first character thereof may be a
* colon, if the argument is prefixed with a colon. The colon used for the
* prefix is not considered part of the content of the message. For example, in
*
* SQUISH POP :cat purr!!
*
* the first field is "SQUISH", the second field is "POP", and the third
* field is "cat purr!!".
*
* It is essentially an RFC 1459 IRC message without trailing CR-LF and
* without prefixes. See section 2.3.1 of RFC 1459 for an approximate
* BNF representation.
*
* The reason this was chosen instead of using protobuf etc. is that it
* is simple to parse without external libraries, and it also happens to
* be a format I'm very familiar with, having extensively worked with the
* IRC protocol.
*/
/*
* Split an IRC-style message of type []byte into type []string where each
* element is a complete argument. Generally, arguments are separated by
* spaces, and an argument that begins with a ':' causes the rest of the
* line to be treated as a single argument.
*/
func splitMsg(b *[]byte) []string {
mar := make([]string, 0, config.Perf.MessageArgumentsCap)
elem := make([]byte, 0, config.Perf.MessageBytesCap)
for i, c := range *b {
switch c {
case ' ':
if (*b)[i+1] == ':' {
mar = append(mar, string(elem))
mar = append(mar, string((*b)[i+2:]))
goto endl
}
mar = append(mar, string(elem))
elem = make([]byte, 0, config.Perf.MessageBytesCap)
default:
elem = append(elem, c)
}
}
mar = append(mar, string(elem))
endl:
return mar
}
func propagateSelectedUpdate(course *courseT) {
course.Usems.Range(func(key, value interface{}) bool {
_ = key
usem, ok := value.(*usemT)
if !ok {
slog.Error(errType.Error())
return false
}
usem.set()
return true
})
}
func sendSelectedUpdate(
ctx context.Context,
conn *websocket.Conn,
courseID int,
) error {
_course, ok := courses.Load(courseID)
if !ok {
return fmt.Errorf("%w: %d", errNoSuchCourse, courseID)
}
course, ok := _course.(*courseT)
if !ok {
return errType
}
if course == nil {
return fmt.Errorf("%w: %d", errNoSuchCourse, courseID)
}
selected := atomic.LoadUint32(&course.Selected)
err := writeText(ctx, conn, fmt.Sprintf("M %d %d", courseID, selected))
if err != nil {
return fmt.Errorf(
"error sending to websocket for course selected update: %w",
err,
)
}
return nil
}
func propagate(yeargroup string, msg string) error {
chanSubPool, ok := chanPool[yeargroup]
if !ok {
return errNoSuchYearGroup
}
var err error
chanSubPool.Range(func(_userID, _ch interface{}) bool {
ch, ok := _ch.(*chan string)
if !ok {
err = errType
return false
}
select {
case *ch <- msg:
default:
userID, ok := _userID.(string)
if !ok {
err = errType
return false
}
slog.Warn(
"sendq",
"user", userID,
"msg", msg,
)
}
return true
})
return err
}
func writeText(ctx context.Context, c *websocket.Conn, msg string) error {
err := c.Write(ctx, websocket.MessageText, []byte(msg))
if err != nil {
return wrapError(errWebSocketWrite, err)
}
return nil
}