0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-04-16 23:55:47 +02:00
gitea/services/websocket/stopwatch_notifier.go

103 lines
2.6 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package websocket
import (
"context"
"time"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/pubsub"
)
type stopwatchesEvent struct {
Type string `json:"type"`
Data any `json:"data"`
}
// PublishEmptyStopwatches immediately pushes an empty stopwatches list to the
// given user's WebSocket clients — used when the user's last stopwatch is cancelled.
func PublishEmptyStopwatches(userID int64) {
msg, err := json.Marshal(stopwatchesEvent{
Type: "stopwatches",
Data: []any{},
})
if err != nil {
log.Error("websocket: marshal empty stopwatches: %v", err)
return
}
pubsub.DefaultBroker.Publish(pubsub.UserTopic(userID), msg)
}
// InitStopwatch starts the background goroutine that polls active stopwatches
// and pushes updates to connected WebSocket clients.
func InitStopwatch() error {
if !setting.Service.EnableTimetracking {
return nil
}
go graceful.GetManager().RunWithShutdownContext(runStopwatch)
return nil
}
func runStopwatch(ctx context.Context) {
ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Service: WebSocket Stopwatch", process.SystemProcessType, true)
defer finished()
if setting.UI.Notification.EventSourceUpdateTime <= 0 {
return
}
timer := time.NewTicker(setting.UI.Notification.EventSourceUpdateTime)
defer timer.Stop()
for {
select {
case <-ctx.Done():
return
case <-timer.C:
if !pubsub.DefaultBroker.HasSubscribers() {
continue
}
userStopwatches, err := issues_model.GetUIDsAndStopwatch(ctx)
if err != nil {
log.Error("websocket: GetUIDsAndStopwatch: %v", err)
continue
}
for _, us := range userStopwatches {
u, err := user_model.GetUserByID(ctx, us.UserID)
if err != nil {
log.Error("websocket: GetUserByID %d: %v", us.UserID, err)
continue
}
apiSWs, err := convert.ToStopWatches(ctx, u, us.StopWatches)
if err != nil {
if !issues_model.IsErrIssueNotExist(err) {
log.Error("websocket: ToStopWatches: %v", err)
}
continue
}
msg, err := json.Marshal(stopwatchesEvent{
Type: "stopwatches",
Data: apiSWs,
})
if err != nil {
continue
}
pubsub.DefaultBroker.Publish(pubsub.UserTopic(us.UserID), msg)
}
}
}
}