qsl designer
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package qslcard
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TemplateDir returns the asset folder of a stored template:
|
||||
// <baseDir>/templates/<id>. baseDir is <dataDir>/qsl.
|
||||
func TemplateDir(baseDir string, id int64) string {
|
||||
return filepath.Join(baseDir, "templates", fmt.Sprintf("%d", id))
|
||||
}
|
||||
|
||||
// IsDraftPhoto reports whether a photo reference still points at the user's
|
||||
// original file (absolute path) rather than a name inside the template dir.
|
||||
func IsDraftPhoto(ref string) bool { return filepath.IsAbs(ref) }
|
||||
|
||||
// ImportPhotos copies every absolute-path photo the template references into
|
||||
// dir and rewrites the references to the copied names (img_<hash8>.<ext>).
|
||||
// Users move files around — a saved template must never depend on the
|
||||
// original location. Already-relative references are left untouched.
|
||||
func ImportPhotos(t *Template, dir string) error {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return fmt.Errorf("create template dir: %w", err)
|
||||
}
|
||||
imported := map[string]string{} // original path → copied name
|
||||
importOne := func(ref string) (string, error) {
|
||||
if !IsDraftPhoto(ref) {
|
||||
return ref, nil
|
||||
}
|
||||
if name, ok := imported[ref]; ok {
|
||||
return name, nil
|
||||
}
|
||||
name, err := copyPhoto(ref, dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
imported[ref] = name
|
||||
return name, nil
|
||||
}
|
||||
name, err := importOne(t.Hero.Photo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("import hero photo: %w", err)
|
||||
}
|
||||
t.Hero.Photo = name
|
||||
for i := range t.Elements {
|
||||
if t.Elements[i].Type != ElemInsert {
|
||||
continue
|
||||
}
|
||||
name, err := importOne(t.Elements[i].Photo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("import insert photo: %w", err)
|
||||
}
|
||||
t.Elements[i].Photo = name
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyPhoto copies src into dir under a content-derived name so identical
|
||||
// photos dedupe and re-saving is idempotent.
|
||||
func copyPhoto(src, dir string) (string, error) {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("open photo %s: %w", src, err)
|
||||
}
|
||||
defer in.Close()
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, in); err != nil {
|
||||
return "", fmt.Errorf("hash photo %s: %w", src, err)
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(src))
|
||||
if ext != ".jpg" && ext != ".jpeg" && ext != ".png" {
|
||||
return "", fmt.Errorf("unsupported photo type %q (jpeg/png only)", ext)
|
||||
}
|
||||
name := "img_" + hex.EncodeToString(h.Sum(nil))[:8] + ext
|
||||
dst := filepath.Join(dir, name)
|
||||
if _, err := os.Stat(dst); err == nil {
|
||||
return name, nil // identical content already imported
|
||||
}
|
||||
if _, err := in.Seek(0, io.SeekStart); err != nil {
|
||||
return "", fmt.Errorf("rewind photo %s: %w", src, err)
|
||||
}
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create %s: %w", dst, err)
|
||||
}
|
||||
defer out.Close()
|
||||
if _, err := io.Copy(out, in); err != nil {
|
||||
return "", fmt.Errorf("copy photo to %s: %w", dst, err)
|
||||
}
|
||||
if err := out.Close(); err != nil {
|
||||
return "", fmt.Errorf("flush %s: %w", dst, err)
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// PhotoExistsIn returns a Validate callback that checks photo references
|
||||
// against a template asset dir (relative names) or the filesystem (drafts).
|
||||
func PhotoExistsIn(dir string) func(string) bool {
|
||||
return func(ref string) bool {
|
||||
p := ref
|
||||
if !IsDraftPhoto(ref) {
|
||||
p = filepath.Join(dir, ref)
|
||||
}
|
||||
_, err := os.Stat(p)
|
||||
return err == nil
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveTemplateDir deletes a template's asset folder (photos + preview).
|
||||
func RemoveTemplateDir(baseDir string, id int64) error {
|
||||
dir := TemplateDir(baseDir, id)
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
return fmt.Errorf("remove template dir %s: %w", dir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user