Files
RawMark/internal/events/hub.go
T
eezd eb63b14c8e Add settings page with synced appearance and password change
- Store app settings as one JSON document on the server, merged per key and
  pushed to other devices; cache locally to avoid a flash on load
- Settings page groups appearance, account, shortcuts, shares, API tokens and
  trash; full screen with a section list on phones
- Font size and content width settings
- Change the login password, signing out other sessions
- Settings writes and password changes only accept a login session
2026-10-17 16:23:39 +08:00

67 lines
1.5 KiB
Go

// Package events 把仓库变更广播给所有在线客户端(WebSocket)。
package events
import (
"encoding/json"
"log/slog"
"sync"
)
// Event 为推送给客户端的消息
//
// tree 目录结构变化(新增/删除/移动),客户端重新拉取目录树
// changed 笔记内容变化,Rev 为新版本
// deleted 笔记被删除
// moved 笔记被移动(From -> Path),内容版本不变
// settings 应用设置变化,客户端重新读取
type Event struct {
Type string `json:"type"`
Path string `json:"path,omitempty"`
From string `json:"from,omitempty"`
Rev string `json:"rev,omitempty"`
}
type Hub struct {
mu sync.Mutex
clients map[chan []byte]struct{}
}
func NewHub() *Hub { return &Hub{clients: map[chan []byte]struct{}{}} }
// Subscribe 返回消息通道与取消函数;通道在客户端跟不上时会被关闭,客户端应重连并全量同步
func (h *Hub) Subscribe() (<-chan []byte, func()) {
ch := make(chan []byte, 64)
h.mu.Lock()
h.clients[ch] = struct{}{}
h.mu.Unlock()
return ch, func() {
h.mu.Lock()
if _, ok := h.clients[ch]; ok {
delete(h.clients, ch)
close(ch)
}
h.mu.Unlock()
}
}
func (h *Hub) Broadcast(events ...Event) {
if len(events) == 0 {
return
}
msg, err := json.Marshal(events)
if err != nil {
slog.Error("序列化事件失败", "err", err)
return
}
h.mu.Lock()
defer h.mu.Unlock()
for ch := range h.clients {
select {
case ch <- msg:
default:
delete(h.clients, ch)
close(ch)
}
}
}