136 lines
3.3 KiB
Go
136 lines
3.3 KiB
Go
package files
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
// WarmStats 汇报一次启动资源缓存预热结果。
|
|
type WarmStats struct {
|
|
StickerSets int
|
|
Documents int
|
|
Blobs int
|
|
}
|
|
|
|
// WarmCaches 从已持久化的 sticker/reaction 元数据预热小 blob 字节缓存与完整 sticker set 缓存。
|
|
// SeedMedia 在已有数据时会跳过导入;该方法保证普通 server 重启后历史 sticker 首次渲染也不是冷缓存。
|
|
func (s *Service) WarmCaches(ctx context.Context) (WarmStats, error) {
|
|
var stats WarmStats
|
|
seenDocs := make(map[int64]struct{})
|
|
for _, kind := range []domain.StickerSetKind{
|
|
domain.StickerSetKindStickers,
|
|
domain.StickerSetKindEmoji,
|
|
domain.StickerSetKindMasks,
|
|
domain.StickerSetKindSystem,
|
|
} {
|
|
sets, err := s.media.ListStickerSets(ctx, kind)
|
|
if err != nil {
|
|
return stats, err
|
|
}
|
|
for _, set := range sets {
|
|
docs, err := s.media.GetDocuments(ctx, set.DocumentIDs)
|
|
if err != nil {
|
|
return stats, err
|
|
}
|
|
ordered := orderDocuments(docs, set.DocumentIDs)
|
|
s.stickerSetCache.put(set, ordered)
|
|
stats.StickerSets++
|
|
for _, doc := range ordered {
|
|
if _, ok := seenDocs[doc.ID]; ok {
|
|
continue
|
|
}
|
|
seenDocs[doc.ID] = struct{}{}
|
|
stats.Documents++
|
|
warmed, err := s.prewarmDocumentBlobs(ctx, doc)
|
|
if err != nil {
|
|
return stats, err
|
|
}
|
|
stats.Blobs += warmed
|
|
}
|
|
}
|
|
}
|
|
reactions, err := s.media.ListAvailableReactions(ctx)
|
|
if err != nil {
|
|
return stats, err
|
|
}
|
|
reactionIDs := make([]int64, 0, len(reactions)*4)
|
|
for _, reaction := range reactions {
|
|
reactionIDs = append(reactionIDs, reaction.DocumentIDs()...)
|
|
}
|
|
docs, err := s.media.GetDocuments(ctx, reactionIDs)
|
|
if err != nil {
|
|
return stats, err
|
|
}
|
|
for _, doc := range docs {
|
|
if _, ok := seenDocs[doc.ID]; ok {
|
|
continue
|
|
}
|
|
seenDocs[doc.ID] = struct{}{}
|
|
stats.Documents++
|
|
warmed, err := s.prewarmDocumentBlobs(ctx, doc)
|
|
if err != nil {
|
|
return stats, err
|
|
}
|
|
stats.Blobs += warmed
|
|
}
|
|
return stats, nil
|
|
}
|
|
|
|
func (s *Service) prewarmDocumentBlobs(ctx context.Context, doc domain.Document) (int, error) {
|
|
if doc.ID == 0 {
|
|
return 0, nil
|
|
}
|
|
warmed := 0
|
|
ok, err := s.prewarmLocationKey(ctx, fmt.Sprintf("doc:%d", doc.ID))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if ok {
|
|
warmed++
|
|
}
|
|
for _, thumb := range doc.Thumbs {
|
|
if !thumb.Downloadable() {
|
|
continue
|
|
}
|
|
ok, err := s.prewarmLocationKey(ctx, fmt.Sprintf("doc:%d:%s", doc.ID, thumb.Type))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if ok {
|
|
warmed++
|
|
}
|
|
}
|
|
return warmed, nil
|
|
}
|
|
|
|
func (s *Service) prewarmLocationKey(ctx context.Context, locationKey string) (bool, error) {
|
|
blob, ok := s.blobCache.get(locationKey)
|
|
if !ok {
|
|
var (
|
|
found bool
|
|
err error
|
|
)
|
|
blob, found, err = s.media.GetFileBlob(ctx, locationKey)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if !found {
|
|
return false, nil
|
|
}
|
|
s.blobCache.put(locationKey, blob)
|
|
}
|
|
if blob.Size <= 0 || blob.Size > blobBytesCacheMaxEntryBytes || s.byteCache.has(blob.ObjectKey) {
|
|
return false, nil
|
|
}
|
|
data, total, err := s.blobs.GetRange(ctx, blob.ObjectKey, 0, blobBytesCacheMaxEntryBytes+1)
|
|
if err != nil {
|
|
return false, fmt.Errorf("read blob %q: %w", blob.LocationKey, err)
|
|
}
|
|
if total <= blobBytesCacheMaxEntryBytes && int64(len(data)) == total {
|
|
s.byteCache.put(blob.ObjectKey, data)
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|