Files

109 lines
2.8 KiB
Go

package storage
import (
"bytes"
"fmt"
"os"
"path/filepath"
"time"
)
// Miniatures PNG des schémas, générées par le navigateur et rangées dans
// .thumbs/ en miroir de l'arborescence (a/b.excalidraw -> .thumbs/a/b.excalidraw.png).
// Une miniature plus ancienne que son schéma est considérée comme périmée.
// MaxThumbSize borne la taille d'une miniature acceptée.
const MaxThumbSize = 1 << 20
var pngMagic = []byte("\x89PNG\r\n\x1a\n")
func (s *Store) thumbAbs(c string, isDir bool) string {
p := filepath.Join(s.root, thumbsDir, filepath.FromSlash(c))
if isDir {
return p
}
return p + ".png"
}
// ReadThumb renvoie la miniature d'un schéma et sa date, ou ErrNotFound si
// elle est absente ou périmée.
func (s *Store) ReadThumb(rel string) ([]byte, time.Time, error) {
c, abs, err := s.resolve(rel)
if err != nil {
return nil, time.Time{}, err
}
if err := requireSchemaPath(c); err != nil {
return nil, time.Time{}, err
}
src, err := os.Stat(abs)
if err != nil {
return nil, time.Time{}, mapErr(err)
}
ta := s.thumbAbs(c, false)
ti, err := os.Stat(ta)
if err != nil {
return nil, time.Time{}, mapErr(err)
}
if ti.ModTime().Before(src.ModTime()) {
return nil, time.Time{}, fmt.Errorf("%w : miniature périmée", ErrNotFound)
}
b, err := os.ReadFile(ta)
return b, ti.ModTime(), mapErr(err)
}
// WriteThumb enregistre la miniature PNG d'un schéma existant.
func (s *Store) WriteThumb(rel string, png []byte) error {
c, abs, err := s.resolve(rel)
if err != nil {
return err
}
if err := requireSchemaPath(c); err != nil {
return err
}
if !bytes.HasPrefix(png, pngMagic) || len(png) > MaxThumbSize {
return fmt.Errorf("%w : PNG de %d Ko max attendu", ErrInvalidPath, MaxThumbSize>>10)
}
if _, err := os.Stat(abs); err != nil {
return mapErr(err)
}
ta := s.thumbAbs(c, false)
if err := os.MkdirAll(filepath.Dir(ta), 0o755); err != nil {
return err
}
return atomicWrite(ta, png)
}
// Les opérations suivantes accompagnent Move/Copy/Delete. Elles sont
// « au mieux » : une miniature manquante sera simplement régénérée.
func (s *Store) moveThumb(from, to string, isDir bool) {
src, dst := s.thumbAbs(from, isDir), s.thumbAbs(to, isDir)
if _, err := os.Stat(src); err != nil {
return
}
_ = os.RemoveAll(dst)
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err == nil {
_ = os.Rename(src, dst)
}
}
func (s *Store) copyThumb(from, to string) {
cf, err1 := Clean(from)
ct, err2 := Clean(to)
if err1 != nil || err2 != nil {
return
}
b, err := os.ReadFile(s.thumbAbs(cf, false))
if err != nil {
return
}
dst := s.thumbAbs(ct, false)
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err == nil {
_ = atomicWrite(dst, b) // écrite après la copie du schéma : donc à jour
}
}
func (s *Store) removeThumb(c string, isDir bool) {
_ = os.RemoveAll(s.thumbAbs(c, isDir))
}