Add mobile save sheet with same-origin photo downloads.
Phones cannot land downloads in the photo library, so the gallery opens a share sheet for a cacheable preview while streaming via a dedicated download endpoint and recording preview sizes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"git.azzamo.net/Michilis/Spanglish/photo-api/internal/store"
|
||||
)
|
||||
|
||||
// statConcurrency bounds the one-off Stat burst below; a gallery of ~70
|
||||
// photos then costs a handful of round-trips against S3, once ever.
|
||||
const statConcurrency = 8
|
||||
|
||||
// fillPreviewSizes measures the preview objects of photos whose
|
||||
// preview_size_bytes is still unset and writes the result back, updating the
|
||||
// slice in place.
|
||||
//
|
||||
// The worker records the size when it generates the preview, so this only
|
||||
// ever fires for rows that predate the column (or arrived via `photo-api
|
||||
// sync`). It is a self-healing cache fill rather than a migration step: the
|
||||
// first gallery view after deploy pays for one Stat per photo, every view
|
||||
// after that reads the stored value. Errors are logged and swallowed — a
|
||||
// missing size costs the save sheet its row label, which is not worth
|
||||
// failing a gallery response over.
|
||||
func (s *Server) fillPreviewSizes(ctx context.Context, photos []store.Photo) {
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
sem = make(chan struct{}, statConcurrency)
|
||||
mu sync.Mutex
|
||||
size = make(map[string]int64, len(photos))
|
||||
)
|
||||
for _, p := range photos {
|
||||
if p.PreviewKey == "" || p.PreviewSizeBytes > 0 {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(p store.Photo) {
|
||||
defer wg.Done()
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
n, err := s.storage.Stat(ctx, p.PreviewKey)
|
||||
if err != nil {
|
||||
log.Printf("preview size %s: %v", p.PreviewKey, err)
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
size[p.ID] = n
|
||||
mu.Unlock()
|
||||
}(p)
|
||||
}
|
||||
wg.Wait()
|
||||
if len(size) == 0 {
|
||||
return
|
||||
}
|
||||
for i := range photos {
|
||||
if n, ok := size[photos[i].ID]; ok {
|
||||
photos[i].PreviewSizeBytes = n
|
||||
if err := s.db.SetPreviewSize(ctx, photos[i].ID, n); err != nil {
|
||||
log.Printf("store preview size %s: %v", photos[i].ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user