fix: attempt to fix cross device move
Some checks are pending
build / build (push) Waiting to run

This commit is contained in:
Viktor Varland 2025-10-02 18:03:23 +02:00
parent 09134c46c4
commit a639ac073e
Signed by: varl
GPG key ID: 7459F0B410115EE8
4 changed files with 114 additions and 29 deletions

View file

@ -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()
}

View file

@ -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,
}) })
} }

View file

@ -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"

View file

@ -57,6 +57,7 @@ func run(cfg config.Config) {
continue continue
} }
// download the channel
dl.Youtube(dl.Download{ dl.Youtube(dl.Download{
Url: feed.Author.Uri, Url: feed.Author.Uri,
OutDir: paths.ChannelsDir, OutDir: paths.ChannelsDir,