owpengram-server/internal/app/files/seed_state.go
A 599453a3c4 fix: harden channel rights and sticker compatibility
Co-authored-by: HSgram <3013954224@qq.com>
2026-07-01 22:38:13 +08:00

167 lines
4 KiB
Go

package files
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"io"
"os"
"path/filepath"
"sort"
)
const (
seedEffectsStateKey = "files.effects"
seedEffectsStateVersion = "effects-v2"
seedAppearanceStateKey = "files.appearance"
seedAppearanceStateVersion = "appearance-v1"
)
func (s *Service) seedStateMatches(ctx context.Context, key, want string) (bool, error) {
if want == "" {
return false, nil
}
got, found, err := s.media.GetSeedState(ctx, key)
if err != nil || !found {
return false, err
}
return got == want, nil
}
func (s *Service) putSeedState(ctx context.Context, key, hash string) error {
if hash == "" {
return nil
}
return s.media.PutSeedState(ctx, key, hash)
}
func seedStateHash(write func(hash.Hash) error) (string, error) {
h := sha256.New()
if err := write(h); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}
func writeSeedStateHeader(h io.Writer, version string, dc int) {
_, _ = fmt.Fprintf(h, "version=%s\ndc=%d\n", version, dc)
}
func writeSeedDirFingerprint(h io.Writer, dir string) error {
entries, err := os.ReadDir(dir)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
names := make([]string, 0, len(entries))
byName := make(map[string]os.DirEntry, len(entries))
for _, entry := range entries {
if entry.IsDir() {
continue
}
names = append(names, entry.Name())
byName[entry.Name()] = entry
}
sort.Strings(names)
for _, name := range names {
info, err := byName[name].Info()
if err != nil {
return err
}
rel := filepath.ToSlash(name)
_, _ = fmt.Fprintf(h, "file=%s\x00size=%d\x00mtime=%d\n", rel, info.Size(), info.ModTime().UnixNano())
}
return nil
}
func seedDocumentJSONLocationKeys(dj seedDocumentJSON, index seedDirIndex) []string {
storageID := seedDocumentStorageID(dj.ID)
if storageID == 0 {
return nil
}
keys := make([]string, 0, 1+len(dj.Thumbs))
if _, ok := index.main[dj.ID]; ok {
keys = append(keys, fmt.Sprintf("doc:%d", storageID))
}
for _, tj := range dj.Thumbs {
ps, downloadable := seedPhotoSize(tj)
if !downloadable || ps.Type == "" {
continue
}
if _, ok := index.thumb[dj.ID][ps.Type]; ok {
keys = append(keys, fmt.Sprintf("doc:%d:%s", storageID, ps.Type))
}
}
if seedDocumentJSONNeedsSyntheticTGStickerPreviewThumb(dj) {
keys = append(keys, fmt.Sprintf("doc:%d:%s", storageID, seedSyntheticDocumentThumbType))
}
return keys
}
func seedDocumentJSONNeedsSyntheticTGStickerPreviewThumb(dj seedDocumentJSON) bool {
return dj.MimeType == "application/x-tgsticker" && len(dj.Thumbs) == 0
}
func (s *Service) seedDocumentJSONsReady(ctx context.Context, docs []seedDocumentJSON, index seedDirIndex) (bool, error) {
expected := make(map[int64]seedDocumentJSON, len(docs))
ids := make([]int64, 0, len(docs))
locationKeys := make([]string, 0, len(docs))
seenLocationKeys := make(map[string]struct{}, len(docs))
for _, dj := range docs {
storageID := seedDocumentStorageID(dj.ID)
if storageID == 0 {
continue
}
if _, ok := expected[storageID]; !ok {
expected[storageID] = dj
ids = append(ids, storageID)
}
for _, key := range seedDocumentJSONLocationKeys(dj, index) {
if _, ok := seenLocationKeys[key]; ok {
continue
}
seenLocationKeys[key] = struct{}{}
locationKeys = append(locationKeys, key)
}
}
if len(ids) == 0 {
return true, nil
}
stored, err := s.media.GetDocuments(ctx, ids)
if err != nil {
return false, err
}
if len(stored) < len(expected) {
return false, nil
}
for _, doc := range stored {
dj, ok := expected[doc.ID]
if !ok {
continue
}
if doc.DCID != s.dc || doc.MimeType != dj.MimeType || doc.Size != dj.Size {
return false, nil
}
delete(expected, doc.ID)
}
if len(expected) > 0 {
return false, nil
}
if len(locationKeys) == 0 {
return true, nil
}
blobs, err := s.media.GetFileBlobs(ctx, locationKeys)
if err != nil {
return false, err
}
for _, key := range locationKeys {
if _, ok := blobs[key]; !ok {
return false, nil
}
}
return true, nil
}