chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
270
internal/store/memory/saved_dialog.go
Normal file
270
internal/store/memory/saved_dialog.go
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
// savedDialogTopsLocked 聚合 self-chat 按 saved_peer 分组的 top message。
|
||||
// 返回按 top box id 降序。
|
||||
func (s *MessageStore) savedDialogTopsLocked(userID int64) []domain.Message {
|
||||
tops := make(map[domain.Peer]domain.Message)
|
||||
selfPeer := domain.Peer{Type: domain.PeerTypeUser, ID: userID}
|
||||
for _, msg := range s.m[userID] {
|
||||
if msg.Peer != selfPeer || msg.SavedPeer.ID == 0 {
|
||||
continue
|
||||
}
|
||||
if cur, ok := tops[msg.SavedPeer]; !ok || msg.ID > cur.ID {
|
||||
tops[msg.SavedPeer] = msg
|
||||
}
|
||||
}
|
||||
out := make([]domain.Message, 0, len(tops))
|
||||
for _, msg := range tops {
|
||||
out = append(out, msg)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].ID > out[j].ID })
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *MessageStore) savedPinIndexLocked(userID int64, peer domain.Peer) int {
|
||||
for i, p := range s.savedPins[userID] {
|
||||
if p == peer {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (s *MessageStore) appendSavedDialogLocked(out *domain.SavedDialogList, msg domain.Message, pinned bool) {
|
||||
out.Dialogs = append(out.Dialogs, domain.SavedDialog{
|
||||
Peer: msg.SavedPeer,
|
||||
TopMessage: msg.ID,
|
||||
Pinned: pinned,
|
||||
})
|
||||
out.Messages = append(out.Messages, cloneMessage(msg))
|
||||
}
|
||||
|
||||
func (s *MessageStore) ListSavedDialogs(_ context.Context, userID int64, filter domain.SavedDialogsFilter) (domain.SavedDialogList, error) {
|
||||
out := domain.SavedDialogList{}
|
||||
if userID == 0 {
|
||||
return out, fmt.Errorf("list saved dialogs: missing user id")
|
||||
}
|
||||
limit := filter.Limit
|
||||
if limit <= 0 || limit > domain.MaxSavedDialogsLimit {
|
||||
limit = domain.MaxSavedDialogsLimit
|
||||
}
|
||||
offsetID := filter.OffsetID
|
||||
// DrKLO Android 首页发 offset_id = MaxMessageBoxID(int32 max),用 >= 命中首页
|
||||
// 分支,避免被误判为续页跳过置顶块(与 postgres 对齐)。
|
||||
firstPage := offsetID <= 0 || offsetID >= domain.MaxMessageBoxID
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
tops := s.savedDialogTopsLocked(userID)
|
||||
topByPeer := make(map[domain.Peer]domain.Message, len(tops))
|
||||
for _, msg := range tops {
|
||||
topByPeer[msg.SavedPeer] = msg
|
||||
}
|
||||
// 首页且不排除置顶:置顶块按 pinned_order 在前。
|
||||
if firstPage && !filter.ExcludePinned {
|
||||
for _, peer := range s.savedPins[userID] {
|
||||
if len(out.Dialogs) >= limit {
|
||||
break
|
||||
}
|
||||
if msg, ok := topByPeer[peer]; ok {
|
||||
s.appendSavedDialogLocked(&out, msg, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 普通块恒排除置顶(置顶只随首页返回)。
|
||||
hasMore := false
|
||||
for _, msg := range tops {
|
||||
if len(out.Dialogs) >= limit {
|
||||
if s.savedPinIndexLocked(userID, msg.SavedPeer) < 0 &&
|
||||
(firstPage || msg.ID < offsetID) {
|
||||
hasMore = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
if s.savedPinIndexLocked(userID, msg.SavedPeer) >= 0 {
|
||||
continue
|
||||
}
|
||||
if !firstPage && msg.ID >= offsetID {
|
||||
continue
|
||||
}
|
||||
s.appendSavedDialogLocked(&out, msg, false)
|
||||
}
|
||||
total := 0
|
||||
for _, msg := range tops {
|
||||
if filter.ExcludePinned && s.savedPinIndexLocked(userID, msg.SavedPeer) >= 0 {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
}
|
||||
out.Count = total
|
||||
out.Full = !hasMore
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *MessageStore) ListPinnedSavedDialogs(_ context.Context, userID int64) (domain.SavedDialogList, error) {
|
||||
out := domain.SavedDialogList{Full: true}
|
||||
if userID == 0 {
|
||||
return out, fmt.Errorf("list pinned saved dialogs: missing user id")
|
||||
}
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
tops := s.savedDialogTopsLocked(userID)
|
||||
topByPeer := make(map[domain.Peer]domain.Message, len(tops))
|
||||
for _, msg := range tops {
|
||||
topByPeer[msg.SavedPeer] = msg
|
||||
}
|
||||
for _, peer := range s.savedPins[userID] {
|
||||
if msg, ok := topByPeer[peer]; ok {
|
||||
s.appendSavedDialogLocked(&out, msg, true)
|
||||
}
|
||||
}
|
||||
out.Count = len(out.Dialogs)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *MessageStore) ListSavedDialogsByPeers(_ context.Context, userID int64, peers []domain.Peer) (domain.SavedDialogList, error) {
|
||||
out := domain.SavedDialogList{Full: true}
|
||||
if userID == 0 {
|
||||
return out, fmt.Errorf("list saved dialogs by peers: missing user id")
|
||||
}
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
tops := s.savedDialogTopsLocked(userID)
|
||||
topByPeer := make(map[domain.Peer]domain.Message, len(tops))
|
||||
for _, msg := range tops {
|
||||
topByPeer[msg.SavedPeer] = msg
|
||||
}
|
||||
seen := make(map[domain.Peer]struct{}, len(peers))
|
||||
for _, peer := range peers {
|
||||
if peer.Type == "" || peer.ID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[peer]; ok {
|
||||
continue
|
||||
}
|
||||
seen[peer] = struct{}{}
|
||||
if msg, ok := topByPeer[peer]; ok {
|
||||
s.appendSavedDialogLocked(&out, msg, s.savedPinIndexLocked(userID, peer) >= 0)
|
||||
}
|
||||
}
|
||||
out.Count = len(out.Dialogs)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *MessageStore) ToggleSavedDialogPin(_ context.Context, userID int64, peer domain.Peer, pinned bool) (bool, error) {
|
||||
if userID == 0 || peer.Type == "" || peer.ID == 0 {
|
||||
return false, fmt.Errorf("toggle saved dialog pin: invalid input")
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
idx := s.savedPinIndexLocked(userID, peer)
|
||||
if !pinned {
|
||||
if idx < 0 {
|
||||
return false, nil
|
||||
}
|
||||
s.savedPins[userID] = append(s.savedPins[userID][:idx], s.savedPins[userID][idx+1:]...)
|
||||
return true, nil
|
||||
}
|
||||
if idx >= 0 {
|
||||
return false, nil
|
||||
}
|
||||
if len(s.savedPins[userID]) >= domain.MaxPinnedSavedDialogs {
|
||||
return false, domain.ErrPinnedSavedDialogsTooMuch
|
||||
}
|
||||
s.savedPins[userID] = append([]domain.Peer{peer}, s.savedPins[userID]...)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *MessageStore) ReorderPinnedSavedDialogs(_ context.Context, userID int64, order []domain.Peer, force bool) error {
|
||||
if userID == 0 {
|
||||
return fmt.Errorf("reorder pinned saved dialogs: missing user id")
|
||||
}
|
||||
if len(order) > domain.MaxPinnedSavedDialogs {
|
||||
return domain.ErrPinnedSavedDialogsTooMuch
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
next := make([]domain.Peer, 0, len(order))
|
||||
seen := make(map[domain.Peer]struct{}, len(order))
|
||||
for _, peer := range order {
|
||||
if peer.Type == "" || peer.ID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[peer]; ok {
|
||||
continue
|
||||
}
|
||||
seen[peer] = struct{}{}
|
||||
next = append(next, peer)
|
||||
}
|
||||
if !force {
|
||||
// 非 force:order 之外的既有置顶保持原相对顺序排在后面。
|
||||
for _, peer := range s.savedPins[userID] {
|
||||
if _, ok := seen[peer]; !ok {
|
||||
next = append(next, peer)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.savedPins[userID] = next
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MessageStore) DeleteSavedHistory(_ context.Context, req domain.DeleteSavedHistoryRequest) (domain.DeleteSavedHistoryResult, error) {
|
||||
res := domain.DeleteSavedHistoryResult{}
|
||||
if req.OwnerUserID == 0 || req.SavedPeer.Type == "" || req.SavedPeer.ID == 0 {
|
||||
return res, fmt.Errorf("delete saved history: invalid input")
|
||||
}
|
||||
if req.Date == 0 {
|
||||
req.Date = int(time.Now().Unix())
|
||||
}
|
||||
selfPeer := domain.Peer{Type: domain.PeerTypeUser, ID: req.OwnerUserID}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
match := func(msg domain.Message) bool {
|
||||
if msg.Peer != selfPeer || msg.SavedPeer != req.SavedPeer {
|
||||
return false
|
||||
}
|
||||
if req.MaxID > 0 && msg.ID > req.MaxID {
|
||||
return false
|
||||
}
|
||||
if req.MinDate > 0 && msg.Date < req.MinDate {
|
||||
return false
|
||||
}
|
||||
if req.MaxDate > 0 && msg.Date > req.MaxDate {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
deleted, _, more := s.deleteMemoryMessagesLocked(req.OwnerUserID, domain.MaxDeleteHistoryBatch, match)
|
||||
delRes := s.finishMemoryDeleteLocked(domain.DeleteMessagesResult{OwnerUserID: req.OwnerUserID}, deleted, req.Date, false)
|
||||
res.More = more
|
||||
for _, d := range delRes.Deleted {
|
||||
if d.UserID == req.OwnerUserID {
|
||||
res.MessageIDs = d.MessageIDs
|
||||
res.Event = d.Event
|
||||
}
|
||||
}
|
||||
if len(deleted) > 0 && !more {
|
||||
// 子会话删空时清掉它的置顶行(与 PG 实现同语义)。
|
||||
alive := false
|
||||
for _, msg := range s.m[req.OwnerUserID] {
|
||||
if msg.Peer == selfPeer && msg.SavedPeer == req.SavedPeer {
|
||||
alive = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alive {
|
||||
if idx := s.savedPinIndexLocked(req.OwnerUserID, req.SavedPeer); idx >= 0 {
|
||||
s.savedPins[req.OwnerUserID] = append(s.savedPins[req.OwnerUserID][:idx], s.savedPins[req.OwnerUserID][idx+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue