153 lines
4.2 KiB
Go
153 lines
4.2 KiB
Go
package dialogs
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"telesrv/internal/domain"
|
|
"telesrv/internal/readmodelcache"
|
|
)
|
|
|
|
const (
|
|
defaultDialogDraftReadModelTTL = 24 * time.Hour
|
|
defaultDialogDraftReadModelMaxEntries = 1000000
|
|
defaultDialogDraftReadModelMaxBytes int64 = 256 << 20
|
|
)
|
|
|
|
type dialogDraftCacheEntry struct {
|
|
draft domain.DialogDraft
|
|
found bool
|
|
}
|
|
|
|
type dialogDraftReadModelCache struct {
|
|
cache *readmodelcache.Cache[dialogPeerCacheKey, dialogDraftCacheEntry]
|
|
}
|
|
|
|
func newDialogDraftReadModelCache(maxEntries int, maxBytes int64, ttl time.Duration) *dialogDraftReadModelCache {
|
|
if ttl <= 0 {
|
|
ttl = defaultDialogDraftReadModelTTL
|
|
}
|
|
return &dialogDraftReadModelCache{cache: readmodelcache.New[dialogPeerCacheKey, dialogDraftCacheEntry](readmodelcache.Config[dialogPeerCacheKey, dialogDraftCacheEntry]{
|
|
MaxEntries: maxEntries,
|
|
MaxWeight: maxBytes,
|
|
Weight: dialogDraftEntryApproxBytes,
|
|
TTL: ttl,
|
|
Clone: cloneDialogDraftCacheEntry,
|
|
})}
|
|
}
|
|
|
|
func (s *Service) dialogDraftsReadModel(ctx context.Context, userID int64, peers []domain.Peer) (map[domain.Peer]dialogDraftCacheEntry, error) {
|
|
out := make(map[domain.Peer]dialogDraftCacheEntry, len(peers))
|
|
if s == nil || s.dialogs == nil || userID == 0 || len(peers) == 0 {
|
|
return out, nil
|
|
}
|
|
unique := uniqueDialogPeers(peers)
|
|
if len(unique) == 0 {
|
|
return out, nil
|
|
}
|
|
keys := make([]dialogPeerCacheKey, 0, len(unique))
|
|
for _, peer := range unique {
|
|
keys = append(keys, dialogPeerCacheKey{userID: userID, peer: peer})
|
|
}
|
|
hashes := map[domain.Peer]int64{}
|
|
if s.versions != nil {
|
|
var err error
|
|
hashes, err = s.dialogHashes(ctx, userID, unique)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
var cache *readmodelcache.Cache[dialogPeerCacheKey, dialogDraftCacheEntry]
|
|
if s.draftCache != nil {
|
|
cache = s.draftCache.cache
|
|
}
|
|
loaded, err := cache.GetOrLoadBatch(ctx, keys,
|
|
func(key dialogPeerCacheKey) (int64, bool) {
|
|
hash := hashes[key.peer]
|
|
return hash, s.versions != nil && hash != 0
|
|
},
|
|
func(ctx context.Context, missing []dialogPeerCacheKey) (map[dialogPeerCacheKey]dialogDraftCacheEntry, error) {
|
|
requested := make([]domain.Peer, 0, len(missing))
|
|
for _, key := range missing {
|
|
requested = append(requested, key.peer)
|
|
}
|
|
drafts, err := s.dialogs.ListDraftsByPeers(ctx, userID, requested)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
entries := make(map[dialogPeerCacheKey]dialogDraftCacheEntry, len(missing))
|
|
for _, key := range missing {
|
|
entries[key] = dialogDraftCacheEntry{}
|
|
}
|
|
for _, draft := range drafts {
|
|
if draft.TopMessageID != 0 {
|
|
continue
|
|
}
|
|
key := dialogPeerCacheKey{userID: userID, peer: draft.Peer}
|
|
if _, ok := entries[key]; ok {
|
|
entries[key] = dialogDraftCacheEntry{draft: cloneDraft(draft), found: true}
|
|
}
|
|
}
|
|
return entries, nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for key, entry := range loaded {
|
|
out[key.peer] = entry
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func uniqueDialogPeers(peers []domain.Peer) []domain.Peer {
|
|
out := make([]domain.Peer, 0, len(peers))
|
|
seen := make(map[domain.Peer]struct{}, len(peers))
|
|
for _, peer := range peers {
|
|
if peer.ID == 0 || (peer.Type != domain.PeerTypeUser && peer.Type != domain.PeerTypeChannel) {
|
|
continue
|
|
}
|
|
if _, ok := seen[peer]; ok {
|
|
continue
|
|
}
|
|
seen[peer] = struct{}{}
|
|
out = append(out, peer)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (c *dialogDraftReadModelCache) invalidate(key dialogPeerCacheKey) {
|
|
if c != nil {
|
|
c.cache.Invalidate(key)
|
|
}
|
|
}
|
|
|
|
func (c *dialogDraftReadModelCache) flush() {
|
|
if c != nil {
|
|
c.cache.Flush()
|
|
}
|
|
}
|
|
|
|
func cloneDialogDraftCacheEntry(entry dialogDraftCacheEntry) dialogDraftCacheEntry {
|
|
if entry.found {
|
|
entry.draft = cloneDraft(entry.draft)
|
|
}
|
|
return entry
|
|
}
|
|
|
|
func dialogDraftEntryApproxBytes(entry dialogDraftCacheEntry) int64 {
|
|
if !entry.found {
|
|
return 64
|
|
}
|
|
draft := entry.draft
|
|
weight := int64(256 + len(draft.Message) + len(draft.Entities)*64)
|
|
if draft.ReplyTo != nil {
|
|
weight += int64(128 + len(draft.ReplyTo.QuoteText) + len(draft.ReplyTo.QuoteEntities)*64)
|
|
}
|
|
if draft.WebPage != nil {
|
|
weight += int64(64 + len(draft.WebPage.URL))
|
|
}
|
|
if draft.RichMessage != nil {
|
|
weight += int64(len(draft.RichMessage.Blocks) + len(draft.RichMessage.BotAPIProjection) + len(draft.RichMessage.Photos)*256 + len(draft.RichMessage.Documents)*256)
|
|
}
|
|
return weight
|
|
}
|