owpengram-server/internal/store/memory/message_delete.go

256 lines
7 KiB
Go

package memory
import (
"context"
"fmt"
"sort"
"telesrv/internal/domain"
"time"
)
func (s *MessageStore) DeleteMessages(_ context.Context, req domain.DeleteMessagesRequest) (domain.DeleteMessagesResult, error) {
res := domain.DeleteMessagesResult{OwnerUserID: req.OwnerUserID}
ids := normalizeMemoryMessageIDs(req.IDs)
if req.OwnerUserID == 0 || len(ids) == 0 {
return res, nil
}
if len(ids) > domain.MaxDeleteMessageIDs {
return res, fmt.Errorf("delete messages: too many ids: %d > %d", len(ids), domain.MaxDeleteMessageIDs)
}
if req.Date == 0 {
req.Date = int(time.Now().Unix())
}
idSet := make(map[int]struct{}, len(ids))
for _, id := range ids {
idSet[id] = struct{}{}
}
s.mu.Lock()
defer s.mu.Unlock()
deleted, revokeUIDs, _ := s.deleteMemoryMessagesLocked(req.OwnerUserID, 0, func(msg domain.Message) bool {
_, ok := idSet[msg.ID]
return ok
})
if req.Revoke && len(revokeUIDs) > 0 {
deleted = append(deleted, s.deleteMemoryMessagesByUIDLocked(revokeUIDs, req.OwnerUserID)...)
}
return s.finishMemoryDeleteLocked(res, deleted, req.Date, nil), nil
}
type deletedMemoryMessage struct {
userID int64
peer domain.Peer
id int
privateMessageID int64
messageSenderID int64
randomID int64
}
type memoryHistoryClearAnchor struct {
message domain.Message
materialized bool
}
func (s *MessageStore) finishMemoryDeleteLocked(res domain.DeleteMessagesResult, deleted []deletedMemoryMessage, date int, anchors map[int64]memoryHistoryClearAnchor) domain.DeleteMessagesResult {
if len(deleted) == 0 && len(anchors) == 0 {
return res
}
idsByOwner := make(map[int64][]int)
peersByOwner := make(map[int64]map[domain.Peer]struct{})
for _, row := range deleted {
if byMessage := s.savedMessageTags[row.userID]; byMessage != nil {
delete(byMessage, row.id)
if len(byMessage) == 0 {
delete(s.savedMessageTags, row.userID)
}
}
idsByOwner[row.userID] = append(idsByOwner[row.userID], row.id)
if peersByOwner[row.userID] == nil {
peersByOwner[row.userID] = make(map[domain.Peer]struct{})
}
peersByOwner[row.userID][row.peer] = struct{}{}
}
for userID, anchor := range anchors {
if peersByOwner[userID] == nil {
peersByOwner[userID] = make(map[domain.Peer]struct{})
}
peersByOwner[userID][anchor.message.Peer] = struct{}{}
}
ownerSet := make(map[int64]struct{}, len(idsByOwner)+len(anchors))
for userID := range idsByOwner {
ownerSet[userID] = struct{}{}
}
for userID, anchor := range anchors {
if !anchor.materialized {
ownerSet[userID] = struct{}{}
}
}
ownerIDs := make([]int64, 0, len(ownerSet))
for userID := range ownerSet {
ownerIDs = append(ownerIDs, userID)
}
sort.Slice(ownerIDs, func(i, j int) bool { return ownerIDs[i] < ownerIDs[j] })
for _, userID := range ownerIDs {
ids := normalizeMemoryMessageIDs(idsByOwner[userID])
anchor, hasAnchor := anchors[userID]
materializeAnchor := hasAnchor && !anchor.materialized
totalPtsCount := len(ids)
if materializeAnchor {
totalPtsCount += 2
}
if totalPtsCount == 0 {
continue
}
pts := s.nextPtsNLocked(userID, totalPtsCount)
cursor := pts - totalPtsCount
item := domain.DeletedMessagesForUser{
UserID: userID,
MessageIDs: ids,
Pts: pts,
PtsCount: totalPtsCount,
Events: make([]domain.UpdateEvent, 0, 3),
}
if len(ids) > 0 {
cursor += len(ids)
event := domain.UpdateEvent{
UserID: userID,
Type: domain.UpdateEventDeleteMessages,
Pts: cursor,
PtsCount: len(ids),
Date: date,
MessageIDs: ids,
}
for _, row := range deleted {
if row.userID != userID || row.messageSenderID != userID || row.randomID == 0 || row.privateMessageID == 0 {
continue
}
key := privateSendDedupKey{senderUserID: userID, randomID: row.randomID}
record, ok := s.privateSendDedup[key]
if !ok {
continue
}
cloned := cloneUpdateEvent(event)
record.senderDeleteEvent = &cloned
s.privateSendDedup[key] = record
}
item.Event = event
item.Events = append(item.Events, event)
}
if materializeAnchor {
readPts := cursor + 1
editPts := readPts + 1
msg := domain.NewHistoryClearMessage(
userID,
anchor.message.Peer,
anchor.message.ID,
anchor.message.UID,
anchor.message.Date,
editPts,
)
for i := range s.m[userID] {
if s.m[userID][i].ID == anchor.message.ID && s.m[userID][i].Peer == anchor.message.Peer {
s.m[userID][i] = msg
break
}
}
if byMessage := s.savedMessageTags[userID]; byMessage != nil {
delete(byMessage, anchor.message.ID)
if len(byMessage) == 0 {
delete(s.savedMessageTags, userID)
}
}
readEvent := domain.UpdateEvent{
UserID: userID,
Type: domain.UpdateEventReadHistoryInbox,
Pts: readPts,
PtsCount: 1,
Date: date,
Peer: anchor.message.Peer,
MaxID: anchor.message.ID,
StillUnreadCount: 0,
}
editEvent := domain.UpdateEvent{
UserID: userID,
Type: domain.UpdateEventEditMessage,
Pts: editPts,
PtsCount: 1,
Date: date,
Message: cloneMessage(msg),
}
item.Events = append(item.Events, readEvent, editEvent)
cursor = editPts
}
if s.dialogs != nil {
s.dialogs.mu.Lock()
for peer := range peersByOwner[userID] {
s.rebuildMemoryDialogLocked(userID, peer)
}
if materializeAnchor {
s.advanceMemoryHistoryClearDialogLocked(userID, anchor.message.Peer, anchor.message.ID)
}
s.dialogs.mu.Unlock()
}
if cursor != pts {
panic(fmt.Sprintf("memory delete history pts cursor %d does not reach reserved pts %d", cursor, pts))
}
res.Deleted = append(res.Deleted, item)
}
return res
}
func (s *MessageStore) advanceMemoryHistoryClearDialogLocked(userID int64, peer domain.Peer, maxID int) {
list := s.dialogs.m[userID]
for i := range list.Dialogs {
if list.Dialogs[i].Peer != peer {
continue
}
if list.Dialogs[i].ReadInboxMaxID < maxID {
list.Dialogs[i].ReadInboxMaxID = maxID
}
list.Dialogs[i].UnreadCount = 0
list.Dialogs[i].UnreadMark = false
list.Dialogs[i].UnreadMentions = 0
list.Dialogs[i].UnreadReactions = 0
break
}
s.dialogs.m[userID] = list
}
func (s *MessageStore) rebuildMemoryDialogLocked(userID int64, peer domain.Peer) {
list := s.dialogs.m[userID]
topID := 0
topDate := 0
unread := 0
for _, msg := range s.m[userID] {
if msg.Peer != peer {
continue
}
if msg.ID > topID {
topID = msg.ID
topDate = msg.Date
}
}
dialogs := list.Dialogs[:0]
for _, dialog := range list.Dialogs {
if dialog.Peer != peer {
dialogs = append(dialogs, dialog)
continue
}
if topID == 0 {
continue
}
for _, msg := range s.m[userID] {
if msg.Peer == peer && !msg.Out && msg.ID > dialog.ReadInboxMaxID {
unread++
}
}
dialog.TopMessage = topID
dialog.TopMessageDate = topDate
dialog.UnreadCount = unread
dialog.UnreadMentions = 0
dialog.UnreadReactions = 0
dialogs = append(dialogs, dialog)
}
list.Dialogs = dialogs
list.Messages = cloneMessages(s.m[userID])
s.dialogs.m[userID] = list
}