package gallery import ( "bytes" "context" "fmt" "hash/fnv" "log/slog" "os" "path" "strings" "time" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/images" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/rest" "github.com/valkey-io/valkey-go" ) type Image struct { UID string X int Y int Width int Height int } func (i Image) URL() string { return images.URLFromUID(i.UID) } const ( imagesKey = "gallery:images:list" hashKey = "gallery:images:hash" ) func (s *Service) autoRefresh() { t := time.NewTicker(time.Minute) for { ctx, cancle := context.WithTimeout(context.Background(), time.Minute) if err := s.assembleImageList(ctx); err != nil { slog.Error("error while refreshing gallery", "err", err) } cancle() <-t.C } } func (s *Service) assembleImageList(ctx context.Context) error { files, err := s.readDirRecursive(s.cfg.Path) if err != nil { return fmt.Errorf("could not read contents of %s: %w", s.cfg.Path, err) } savedHash, _ := s.cache.Do(ctx, s.cache.B().Get().Key(hashKey).Build()).AsBytes() newHash := hashImageList(files) if bytes.Equal(savedHash, newHash) { slog.Debug("images up to date no refreshing of gallery", "savedHash", savedHash, "newHash", newHash) return nil } slog.Info("starting gallery refresh") defer slog.Info("gallery refresh completed") imgs := make([]Image, 0, len(files)) for _, file := range files { if strings.HasSuffix(file.Name(), ".md") { continue } imagePath := path.Join(s.cfg.Path, file.Name()) img, err := s.img.GetImage(imagePath) if err != nil || img.Image == nil { slog.Warn("could not get image for gallery", "err", err, "img", img) continue } uid, err := img.UID() if err != nil { continue } w := img.Bounds().Dx() h := img.Bounds().Dy() // FIX 2: Defensive check to guard against unreadable/corrupted image boundaries if w <= 0 || h <= 0 { slog.Warn("skipping image with zero or negative dimensions", "path", imagePath, "w", w, "h", h) continue } imgs = append(imgs, Image{ UID: uid, Width: w, Height: h, }) } if err = s.cache.Do(ctx, s.cache.B().Set().Key(hashKey).Value(valkey.BinaryString(newHash)).Build()).Error(); err != nil { return err } if err = s.cache.Do(ctx, s.cache.B().Set().Key(imagesKey).Value(valkey.JSON(imgs)).Build()).Error(); err != nil { return err } return nil } func hashImageList(files []os.FileInfo) []byte { h := fnv.New128() for _, file := range files { fmt.Fprint(h, file.ModTime()) fmt.Fprint(h, file.Size()) fmt.Fprint(h, file.Name()) } return h.Sum([]byte{}) } func (s *Service) GetImages(ctx context.Context) (images []Image, err error) { err = s.cache.Do(ctx, s.cache.B().Get().Key(imagesKey).Build()).DecodeJSON(&images) return } func (s *Service) GetPrevAndNext(ctx context.Context, uid string) (previous *Image, next *Image, err error) { imgs, err := s.GetImages(ctx) if err != nil { return nil, nil, err } for i := range imgs { if imgs[i].UID != uid { continue } if i > 0 { prevImg := imgs[i-1] previous = &prevImg } if i+1 < len(imgs) { nextImg := imgs[i+1] next = &nextImg } return previous, next, nil } return nil, nil, &rest.Error{ Type: rest.ErrorTypeNotFound, Message: "could not find image with uid", } }