208 lines
5.7 KiB
Go
208 lines
5.7 KiB
Go
package dialogs
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sort"
|
|
|
|
"telesrv/internal/app/readmodel"
|
|
"telesrv/internal/domain"
|
|
"telesrv/internal/store"
|
|
)
|
|
|
|
const dialogListSnapshotMaterializeAttempts = 2
|
|
|
|
var errDialogListSnapshotGenerationChanged = errors.New("dialog list snapshot owner generation changed")
|
|
|
|
func (s *Service) loadDialogListSnapshot(
|
|
ctx context.Context,
|
|
key dialogListSnapshotKey,
|
|
) (*dialogListSnapshot, error) {
|
|
if s.sharedListCache == nil {
|
|
list, err := s.loadDialogOwnerSnapshotHeaders(ctx, key.userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newDialogListSnapshot(list), nil
|
|
}
|
|
if s.versions == nil {
|
|
return nil, errors.New("shared dialog list snapshot requires durable read-model versions")
|
|
}
|
|
|
|
for attempt := 0; attempt < dialogListSnapshotMaterializeAttempts; attempt++ {
|
|
ownerHash, err := s.dialogOwnerHash(ctx, key.userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
snap, err := s.loadDialogListSnapshotAtOwnerHash(ctx, key, ownerHash)
|
|
if errors.Is(err, errDialogListSnapshotGenerationChanged) {
|
|
continue
|
|
}
|
|
return snap, err
|
|
}
|
|
return nil, errDialogListSnapshotGenerationChanged
|
|
}
|
|
|
|
func (s *Service) loadDialogListSnapshotAtOwnerHash(
|
|
ctx context.Context,
|
|
key dialogListSnapshotKey,
|
|
ownerHash int64,
|
|
) (*dialogListSnapshot, error) {
|
|
if s.sharedListCache == nil {
|
|
list, err := s.loadDialogOwnerSnapshotHeaders(ctx, key.userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newDialogListSnapshot(list), nil
|
|
}
|
|
if s.versions == nil {
|
|
return nil, errors.New("shared dialog list snapshot requires durable read-model versions")
|
|
}
|
|
if ownerHash == 0 {
|
|
return nil, errors.New("dialog_owner read-model generation missing")
|
|
}
|
|
|
|
sharedKey := sharedDialogListSnapshotKey(key, ownerHash)
|
|
cached, found, err := s.sharedListCache.GetDialogListSnapshot(ctx, sharedKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if found {
|
|
snap := dialogListSnapshotFromShared(cached)
|
|
snap.ownerHash = ownerHash
|
|
dependencyHash, err := s.dialogListSnapshotDependencyHash(ctx, ownerHash, snap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if dependencyHash == cached.DependencyHash {
|
|
return snap, nil
|
|
}
|
|
}
|
|
|
|
list, err := s.loadDialogOwnerSnapshotHeaders(ctx, key.userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
snap := newDialogListSnapshot(list)
|
|
currentOwnerHash, err := s.dialogOwnerHash(ctx, key.userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if currentOwnerHash != ownerHash {
|
|
return nil, errDialogListSnapshotGenerationChanged
|
|
}
|
|
dependencyHash, err := s.dialogListSnapshotDependencyHash(ctx, ownerHash, snap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
snap.ownerHash = ownerHash
|
|
snap.dependencyHash = dependencyHash
|
|
value := sharedDialogListSnapshotValue(snap, dependencyHash)
|
|
if err := s.sharedListCache.PutDialogListSnapshot(ctx, sharedKey, value); err != nil {
|
|
return nil, err
|
|
}
|
|
return snap, nil
|
|
}
|
|
|
|
func (s *Service) dialogOwnerHash(ctx context.Context, userID int64) (int64, error) {
|
|
hash, found, err := s.versions.ReadModelHash(
|
|
ctx, readmodel.ModelDialogOwner, userID, domain.PeerTypeUser, userID,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if !found || hash == 0 {
|
|
return 0, errors.New("dialog_owner read-model generation missing")
|
|
}
|
|
return hash, nil
|
|
}
|
|
|
|
func (s *Service) dialogListSnapshotDependencyHash(
|
|
ctx context.Context,
|
|
ownerHash int64,
|
|
snap *dialogListSnapshot,
|
|
) (int64, error) {
|
|
peers := dialogListSnapshotPeers(snap)
|
|
keys := make([]store.ReadModelKey, 0, len(peers))
|
|
for _, peer := range peers {
|
|
if peer.Type == domain.PeerTypeChannel {
|
|
keys = append(keys, store.ReadModelKey{
|
|
Model: readmodel.ModelChannelBase, PeerType: peer.Type, PeerID: peer.ID,
|
|
})
|
|
}
|
|
}
|
|
hashes, err := s.versions.ReadModelHashes(ctx, keys)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
values := make([]int64, 0, len(keys)+1)
|
|
values = append(values, ownerHash)
|
|
for _, key := range keys {
|
|
hash := hashes[key]
|
|
if hash == 0 {
|
|
return 0, errors.New("dialog snapshot dependency generation missing")
|
|
}
|
|
values = append(values, hash)
|
|
}
|
|
return readmodel.MixHashes(values...), nil
|
|
}
|
|
|
|
func dialogListSnapshotPeers(snap *dialogListSnapshot) []domain.Peer {
|
|
if snap == nil {
|
|
return nil
|
|
}
|
|
seen := make(map[domain.Peer]struct{}, len(snap.dialogs)+1)
|
|
peers := make([]domain.Peer, 0, len(snap.dialogs)+1)
|
|
appendPeer := func(peer domain.Peer) {
|
|
if peer.Type == "" || peer.ID == 0 {
|
|
return
|
|
}
|
|
if _, found := seen[peer]; found {
|
|
return
|
|
}
|
|
seen[peer] = struct{}{}
|
|
peers = append(peers, peer)
|
|
}
|
|
for _, dialog := range snap.dialogs {
|
|
appendPeer(dialog.Peer)
|
|
}
|
|
if snap.archive != nil {
|
|
appendPeer(snap.archive.TopPeer)
|
|
}
|
|
sort.Slice(peers, func(i, j int) bool {
|
|
if peers[i].Type != peers[j].Type {
|
|
return peers[i].Type < peers[j].Type
|
|
}
|
|
return peers[i].ID < peers[j].ID
|
|
})
|
|
return peers
|
|
}
|
|
|
|
func sharedDialogListSnapshotKey(key dialogListSnapshotKey, ownerHash int64) store.DialogListSnapshotCacheKey {
|
|
return store.DialogListSnapshotCacheKey{
|
|
UserID: key.userID, OwnerHash: ownerHash,
|
|
}
|
|
}
|
|
|
|
func sharedDialogListSnapshotValue(snap *dialogListSnapshot, dependencyHash int64) store.DialogListSnapshotCacheValue {
|
|
value := store.DialogListSnapshotCacheValue{DependencyHash: dependencyHash}
|
|
if snap == nil {
|
|
return value
|
|
}
|
|
value.Dialogs = cloneDialogSlice(snap.dialogs)
|
|
value.Messages = cloneDialogMessages(snap.messages)
|
|
value.Users = cloneDialogUsers(snap.users)
|
|
value.State = snap.state
|
|
value.ArchiveSummary = cloneDialogArchiveSummary(snap.archive)
|
|
return value
|
|
}
|
|
|
|
func dialogListSnapshotFromShared(value store.DialogListSnapshotCacheValue) *dialogListSnapshot {
|
|
list := domain.DialogList{
|
|
Dialogs: value.Dialogs, Messages: value.Messages, Users: value.Users,
|
|
State: value.State, ArchiveSummary: value.ArchiveSummary,
|
|
}
|
|
snap := newDialogListSnapshot(list)
|
|
snap.dependencyHash = value.DependencyHash
|
|
return snap
|
|
}
|