diff options
Diffstat (limited to '')
-rw-r--r-- | course_groups.go | 118 | ||||
-rw-r--r-- | course_types.go (renamed from wsx.go) | 24 | ||||
-rw-r--r-- | courses.go | 110 | ||||
-rw-r--r-- | database.go (renamed from db.go) | 0 | ||||
-rw-r--r-- | endpoint_auth.go (renamed from auth.go) | 0 | ||||
-rw-r--r-- | endpoint_export.go (renamed from export.go) | 0 | ||||
-rw-r--r-- | endpoint_index.go (renamed from index.go) | 0 | ||||
-rw-r--r-- | endpoint_newcourses.go (renamed from newcourses.go) | 0 | ||||
-rw-r--r-- | endpoint_ws.go (renamed from wsh.go) | 0 | ||||
-rw-r--r-- | errors.go (renamed from err.go) | 0 | ||||
-rw-r--r-- | misc_utils.go (renamed from utils.go) | 0 | ||||
-rw-r--r-- | usem.go | 2 | ||||
-rw-r--r-- | ws_connection.go (renamed from wsc.go) | 20 | ||||
-rw-r--r-- | ws_utils.go (renamed from wsp.go) | 30 |
14 files changed, 161 insertions, 143 deletions
diff --git a/course_groups.go b/course_groups.go new file mode 100644 index 0000000..c1541ce --- /dev/null +++ b/course_groups.go @@ -0,0 +1,118 @@ +/* + * Course groups + * + * Copyright (C) 2024 Runxi Yu <https://runxiyu.org> + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package main + +import ( + "context" + "fmt" +) + +type userCourseGroupsT map[courseGroupT]struct{} + +type courseGroupT string + +func checkCourseGroup(cg courseGroupT) bool { + _, ok := courseGroups[cg] + return ok +} + +const ( + mw1 courseGroupT = "MW1" + mw2 courseGroupT = "MW2" + mw3 courseGroupT = "MW3" + tt1 courseGroupT = "TT1" + tt2 courseGroupT = "TT2" + tt3 courseGroupT = "TT3" +) + +var courseGroups = map[courseGroupT]string{ + mw1: "Monday/Wednesday CCA1", + mw2: "Monday/Wednesday CCA2", + mw3: "Monday/Wednesday CCA3", + tt1: "Tuesday/Thursday CCA1", + tt2: "Tuesday/Thursday CCA2", + tt3: "Tuesday/Thursday CCA3", +} + +func populateUserCourseGroups( + ctx context.Context, + userCourseGroups *userCourseGroupsT, + userID string, +) error { + rows, err := db.Query( + ctx, + "SELECT courseid FROM choices WHERE userid = $1", + userID, + ) + if err != nil { + return fmt.Errorf( + "%w: %w", + errUnexpectedDBError, + err, + ) + } + for { + if !rows.Next() { + err := rows.Err() + if err != nil { + return fmt.Errorf( + "%w: %w", + errUnexpectedDBError, + err, + ) + } + break + } + var thisCourseID int + err := rows.Scan(&thisCourseID) + if err != nil { + return fmt.Errorf( + "%w: %w", + errUnexpectedDBError, + err, + ) + } + var thisGroupName courseGroupT + _course, ok := courses.Load(thisCourseID) + if !ok { + return fmt.Errorf( + "%w: %d", + errNoSuchCourse, + thisCourseID, + ) + } + course, ok := _course.(*courseT) + if !ok { + panic("courses map has non-\"*courseT\" items") + } + thisGroupName = course.Group + if _, ok := (*userCourseGroups)[thisGroupName]; ok { + return fmt.Errorf( + "%w: user %v, group %v", + errMultipleChoicesInOneGroup, + userID, + thisGroupName, + ) + } + (*userCourseGroups)[thisGroupName] = struct{}{} + } + return nil +} @@ -1,5 +1,5 @@ /* - * Generic WebSocket auxiliary functions + * Course types * * Copyright (C) 2024 Runxi Yu <https://runxiyu.org> * SPDX-License-Identifier: AGPL-3.0-or-later @@ -20,17 +20,19 @@ package main -import ( - "context" - "fmt" +type courseTypeT string - "github.com/coder/websocket" +const ( + sport courseTypeT = "Sport" + nonSport courseTypeT = "Non-sport" ) -func writeText(ctx context.Context, c *websocket.Conn, msg string) error { - err := c.Write(ctx, websocket.MessageText, []byte(msg)) - if err != nil { - return fmt.Errorf("%w: %w", errWebSocketWrite, err) - } - return nil +var courseTypes = map[courseTypeT]struct{}{ + sport: {}, + nonSport: {}, +} + +func checkCourseType(ct courseTypeT) bool { + _, ok := courseTypes[ct] + return ok } @@ -29,11 +29,6 @@ import ( "github.com/coder/websocket" ) -type ( - courseTypeT string - courseGroupT string -) - type courseT struct { /* * Selected is usually accessed atomically, but a lock is still @@ -55,44 +50,6 @@ type courseT struct { Usems sync.Map /* string, *usemT */ } -const ( - sport courseTypeT = "Sport" - nonSport courseTypeT = "Non-sport" -) - -var courseTypes = map[courseTypeT]struct{}{ - sport: {}, - nonSport: {}, -} - -const ( - mw1 courseGroupT = "MW1" - mw2 courseGroupT = "MW2" - mw3 courseGroupT = "MW3" - tt1 courseGroupT = "TT1" - tt2 courseGroupT = "TT2" - tt3 courseGroupT = "TT3" -) - -var courseGroups = map[courseGroupT]string{ - mw1: "Monday/Wednesday CCA1", - mw2: "Monday/Wednesday CCA2", - mw3: "Monday/Wednesday CCA3", - tt1: "Tuesday/Thursday CCA1", - tt2: "Tuesday/Thursday CCA2", - tt3: "Tuesday/Thursday CCA3", -} - -func checkCourseType(ct courseTypeT) bool { - _, ok := courseTypes[ct] - return ok -} - -func checkCourseGroup(cg courseGroupT) bool { - _, ok := courseGroups[cg] - return ok -} - var courses sync.Map /* int, *courseT */ var numCourses uint32 /* atomic */ @@ -172,73 +129,6 @@ func setupCourses(ctx context.Context) error { return nil } -type userCourseGroupsT map[courseGroupT]struct{} - -func populateUserCourseGroups( - ctx context.Context, - userCourseGroups *userCourseGroupsT, - userID string, -) error { - rows, err := db.Query( - ctx, - "SELECT courseid FROM choices WHERE userid = $1", - userID, - ) - if err != nil { - return fmt.Errorf( - "%w: %w", - errUnexpectedDBError, - err, - ) - } - for { - if !rows.Next() { - err := rows.Err() - if err != nil { - return fmt.Errorf( - "%w: %w", - errUnexpectedDBError, - err, - ) - } - break - } - var thisCourseID int - err := rows.Scan(&thisCourseID) - if err != nil { - return fmt.Errorf( - "%w: %w", - errUnexpectedDBError, - err, - ) - } - var thisGroupName courseGroupT - _course, ok := courses.Load(thisCourseID) - if !ok { - return fmt.Errorf( - "%w: %d", - errNoSuchCourse, - thisCourseID, - ) - } - course, ok := _course.(*courseT) - if !ok { - panic("courses map has non-\"*courseT\" items") - } - thisGroupName = course.Group - if _, ok := (*userCourseGroups)[thisGroupName]; ok { - return fmt.Errorf( - "%w: user %v, group %v", - errMultipleChoicesInOneGroup, - userID, - thisGroupName, - ) - } - (*userCourseGroups)[thisGroupName] = struct{}{} - } - return nil -} - func (course *courseT) decrementSelectedAndPropagate( ctx context.Context, conn *websocket.Conn, diff --git a/auth.go b/endpoint_auth.go index 58eb46b..58eb46b 100644 --- a/auth.go +++ b/endpoint_auth.go diff --git a/export.go b/endpoint_export.go index 401c632..401c632 100644 --- a/export.go +++ b/endpoint_export.go diff --git a/index.go b/endpoint_index.go index 512fe1d..512fe1d 100644 --- a/index.go +++ b/endpoint_index.go diff --git a/newcourses.go b/endpoint_newcourses.go index 5963e1b..5963e1b 100644 --- a/newcourses.go +++ b/endpoint_newcourses.go @@ -1,5 +1,5 @@ /* - * Additional synchronization routines + * Increase-unblocking capped semaphores * * Copyright (C) 2024 Runxi Yu <https://runxiyu.org> * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/wsc.go b/ws_connection.go index 2f00302..c407a70 100644 --- a/wsc.go +++ b/ws_connection.go @@ -24,7 +24,6 @@ import ( "context" "errors" "fmt" - "log" "sync" "sync/atomic" "time" @@ -348,22 +347,3 @@ func handleConn( var cancelPool sync.Map /* string, *context.CancelFunc */ var chanPool sync.Map /* string, *chan string */ - -func propagate(msg string) { - chanPool.Range(func(_userID, _ch interface{}) bool { - ch, ok := _ch.(*chan string) - if !ok { - panic("chanPool has non-\"*chan string\" key") - } - select { - case *ch <- msg: - default: - userID, ok := _userID.(string) - if !ok { - panic("chanPool has non-string key") - } - log.Println("WARNING: SendQ exceeded for " + userID) - } - return true - }) -} @@ -1,5 +1,5 @@ /* - * WebSocket-based protocol auxiliary functions + * WebSocket auxiliary functions * * Copyright (C) 2024 Runxi Yu <https://runxiyu.org> * SPDX-License-Identifier: AGPL-3.0-or-later @@ -23,6 +23,7 @@ package main import ( "context" "fmt" + "log" "sync/atomic" "github.com/coder/websocket" @@ -141,3 +142,30 @@ func sendSelectedUpdate( } return nil } + +func propagate(msg string) { + chanPool.Range(func(_userID, _ch interface{}) bool { + ch, ok := _ch.(*chan string) + if !ok { + panic("chanPool has non-\"*chan string\" key") + } + select { + case *ch <- msg: + default: + userID, ok := _userID.(string) + if !ok { + panic("chanPool has non-string key") + } + log.Println("WARNING: SendQ exceeded for " + userID) + } + return true + }) +} + +func writeText(ctx context.Context, c *websocket.Conn, msg string) error { + err := c.Write(ctx, websocket.MessageText, []byte(msg)) + if err != nil { + return fmt.Errorf("%w: %w", errWebSocketWrite, err) + } + return nil +} |