This commit is contained in:
parent
09134c46c4
commit
a639ac073e
|
|
@ -3,15 +3,19 @@ package fsops
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
DryRun bool
|
DryRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var renameFn = os.Rename
|
||||||
|
|
||||||
func (m Manager) EnsureDir(path string) error {
|
func (m Manager) EnsureDir(path string) error {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return fmt.Errorf("ensure dir: empty path")
|
return fmt.Errorf("ensure dir: empty path")
|
||||||
|
|
@ -43,7 +47,16 @@ func (m Manager) Move(src, dst string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Rename(src, dst); err != nil {
|
if err := renameFn(src, dst); err != nil {
|
||||||
|
if errors.Is(err, syscall.EXDEV) {
|
||||||
|
if copyErr := copyFile(src, dst); copyErr != nil {
|
||||||
|
return fmt.Errorf("copy %s -> %s: %w", src, dst, copyErr)
|
||||||
|
}
|
||||||
|
if rmErr := os.Remove(src); rmErr != nil {
|
||||||
|
return fmt.Errorf("remove %s after copy: %w", src, rmErr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return fmt.Errorf("move %s -> %s: %w", src, dst, err)
|
return fmt.Errorf("move %s -> %s: %w", src, dst, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -131,3 +144,33 @@ func (m Manager) RemoveEmptyDirs(root string) error {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
info, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return fmt.Errorf("copy directory not supported: %s", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = out.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := io.Copy(out, in); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.Close()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package metadata
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -76,29 +75,6 @@ func globSidecars(base string) ([]string, error) {
|
||||||
return sidecars, nil
|
return sidecars, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func directoryImages(dir string) ([]string, error) {
|
|
||||||
entries, err := os.ReadDir(dir)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("readdir %s: %w", dir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var images []string
|
|
||||||
for _, entry := range entries {
|
|
||||||
if entry.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := entry.Name()
|
|
||||||
switch strings.ToLower(filepath.Ext(name)) {
|
|
||||||
case ".jpg", ".jpeg", ".png", ".webp":
|
|
||||||
images = append(images, filepath.Join(dir, name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return images, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveMediaPath(base string, episode model.Episode, sidecars []string) (string, []string) {
|
func resolveMediaPath(base string, episode model.Episode, sidecars []string) (string, []string) {
|
||||||
if episode.Ext != "" {
|
if episode.Ext != "" {
|
||||||
candidate := base + "." + episode.Ext
|
candidate := base + "." + episode.Ext
|
||||||
|
|
@ -140,17 +116,27 @@ func ScanShows(root string) ([]ShowAsset, error) {
|
||||||
for _, infoPath := range infoFiles {
|
for _, infoPath := range infoFiles {
|
||||||
show := model.LoadShow(infoPath)
|
show := model.LoadShow(infoPath)
|
||||||
dir := filepath.Dir(infoPath)
|
dir := filepath.Dir(infoPath)
|
||||||
|
base := trimInfoSuffix(infoPath)
|
||||||
|
|
||||||
imgs, imgErr := directoryImages(dir)
|
sidecars, sidecarErr := globSidecars(base)
|
||||||
if imgErr != nil {
|
if sidecarErr != nil {
|
||||||
log.Printf("scan shows: %v\n", imgErr)
|
return nil, sidecarErr
|
||||||
|
}
|
||||||
|
|
||||||
|
var images []string
|
||||||
|
for _, path := range sidecars {
|
||||||
|
ext := strings.ToLower(filepath.Ext(path))
|
||||||
|
switch ext {
|
||||||
|
case ".jpg", ".jpeg", ".png", ".webp":
|
||||||
|
images = append(images, path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assets = append(assets, ShowAsset{
|
assets = append(assets, ShowAsset{
|
||||||
Show: show,
|
Show: show,
|
||||||
InfoPath: infoPath,
|
InfoPath: infoPath,
|
||||||
Dir: dir,
|
Dir: dir,
|
||||||
Images: imgs,
|
Images: images,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,10 @@ func TestScanShows(t *testing.T) {
|
||||||
t.Fatalf("write poster: %v", err)
|
t.Fatalf("write poster: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(filepath.Join(showDir, "other-channel.jpg"), []byte("other"), 0o644); err != nil {
|
||||||
|
t.Fatalf("write extra image: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
assets, err := ScanShows(root)
|
assets, err := ScanShows(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ScanShows error: %v", err)
|
t.Fatalf("ScanShows error: %v", err)
|
||||||
|
|
@ -82,6 +86,57 @@ func TestScanShows(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestScanShowsFiltersImagesPerChannel(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
showDir := filepath.Join(root, "sNA")
|
||||||
|
if err := os.MkdirAll(showDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("mkdir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
primaryBase := filepath.Join(showDir, "channel-one")
|
||||||
|
secondaryBase := filepath.Join(showDir, "channel-two")
|
||||||
|
|
||||||
|
primaryInfo := primaryBase + ".info.json"
|
||||||
|
secondaryInfo := secondaryBase + ".info.json"
|
||||||
|
|
||||||
|
if err := os.WriteFile(primaryInfo, []byte(`{"channel":"One","channel_id":"one","description":"","thumbnails":[]}`), 0o644); err != nil {
|
||||||
|
t.Fatalf("write primary info: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(secondaryInfo, []byte(`{"channel":"Two","channel_id":"two","description":"","thumbnails":[]}`), 0o644); err != nil {
|
||||||
|
t.Fatalf("write secondary info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(primaryBase+".jpg", []byte("one"), 0o644); err != nil {
|
||||||
|
t.Fatalf("write primary image: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(secondaryBase+".jpg", []byte("two"), 0o644); err != nil {
|
||||||
|
t.Fatalf("write secondary image: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assets, err := ScanShows(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("scan shows: %v", err)
|
||||||
|
}
|
||||||
|
if len(assets) != 2 {
|
||||||
|
t.Fatalf("expected 2 show assets, got %d", len(assets))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, asset := range assets {
|
||||||
|
switch asset.Show.Title {
|
||||||
|
case "One":
|
||||||
|
if len(asset.Images) != 1 || asset.Images[0] != primaryBase+".jpg" {
|
||||||
|
t.Fatalf("primary images mismatch: %#v", asset.Images)
|
||||||
|
}
|
||||||
|
case "Two":
|
||||||
|
if len(asset.Images) != 1 || asset.Images[0] != secondaryBase+".jpg" {
|
||||||
|
t.Fatalf("secondary images mismatch: %#v", asset.Images)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected show asset: %s", asset.Show.Title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNormalizeEpisodeThumbnail(t *testing.T) {
|
func TestNormalizeEpisodeThumbnail(t *testing.T) {
|
||||||
got := NormalizeEpisodeThumbnail("file.jpg")
|
got := NormalizeEpisodeThumbnail("file.jpg")
|
||||||
expected := "file-thumb.jpg"
|
expected := "file-thumb.jpg"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue