mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 03:26:37 +08:00
b0fe40142a
* Rebrand MMTL to MeBox across codebase and assets Rename the project display name, Go module path, environment variable prefix (MEBOX_*), Docker image references, and UI branding from MMTL/mmtl to MeBox/mebox. Replace logo assets with the new MeBox icon and keep legacy SQLite migration support for existing mmtl.db deployments. Co-authored-by: truewhile <truewhile@users.noreply.github.com> * Fix logo icons: use cube-only crop without truncated text Previous icon generation cropped too much of the source image, including partial MeBox wordmark text that was cut off in square icon containers. Regenerate logo-64/192/512, favicon, and SVG from cube-only region. Co-authored-by: truewhile <truewhile@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: truewhile <truewhile@users.noreply.github.com>
248 lines
7.0 KiB
Go
248 lines
7.0 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/truewhile/MeBox/internal/config"
|
|
"github.com/truewhile/MeBox/internal/model"
|
|
"github.com/truewhile/MeBox/internal/repository"
|
|
)
|
|
|
|
func newFileManagerTestServiceWithRepo(t *testing.T, root string) (*FileManagerService, *repository.Container) {
|
|
t.Helper()
|
|
db := newServiceTestDB(t, &model.Library{}, &model.Media{}, &model.Setting{})
|
|
repos := repository.New(db)
|
|
lib := model.Library{Name: "downloads", Path: root, Type: "movie", Enabled: true}
|
|
if err := repos.Library.Create(t.Context(), &lib); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg := &config.Config{}
|
|
cfg.App.DataDir = root
|
|
cfg.Cache.CacheDir = root
|
|
return NewFileManagerService(cfg, zap.NewNop(), repos), repos
|
|
}
|
|
|
|
func newFileManagerTestService(t *testing.T, root string) *FileManagerService {
|
|
t.Helper()
|
|
svc, _ := newFileManagerTestServiceWithRepo(t, root)
|
|
return svc
|
|
}
|
|
|
|
func TestFileManagerRecursiveListAndMutations(t *testing.T) {
|
|
root := t.TempDir()
|
|
nested := filepath.Join(root, "downloads", "国产剧")
|
|
if err := os.MkdirAll(nested, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mediaPath := filepath.Join(nested, "狂飙.S01E01.mkv")
|
|
if err := os.WriteFile(mediaPath, []byte("video"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
svc := newFileManagerTestService(t, root)
|
|
|
|
listing, err := svc.List(root, 100, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found := false
|
|
for _, entry := range listing.Entries {
|
|
if entry.Path == mediaPath {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("recursive listing did not include %s", mediaPath)
|
|
}
|
|
|
|
created, err := svc.CreateFolder(root, "整理目标")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
emptyListing, err := svc.List(created.Path, 100, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if emptyListing.Entries == nil || len(emptyListing.Entries) != 0 {
|
|
t.Fatalf("empty directory entries = %#v, want empty slice", emptyListing.Entries)
|
|
}
|
|
copied, err := svc.Transfer(mediaPath, created.Path, TransferCopy)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(copied.Path); err != nil {
|
|
t.Fatalf("copied file missing: %v", err)
|
|
}
|
|
renamed, err := svc.Rename(copied.Path, "狂飙 - S01E01.mkv")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(renamed.Path); err != nil {
|
|
t.Fatalf("renamed file missing: %v", err)
|
|
}
|
|
if err := svc.Delete(renamed.Path); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(renamed.Path); !os.IsNotExist(err) {
|
|
t.Fatalf("expected file deleted, stat err=%v", err)
|
|
}
|
|
}
|
|
|
|
func TestFileManagerTransferDirectoryHardlinksFiles(t *testing.T) {
|
|
root := t.TempDir()
|
|
if hardlinksUnsupported(t, root) {
|
|
t.Skip("hardlinks unsupported on this filesystem")
|
|
}
|
|
sourceDir := filepath.Join(root, "downloads", "Show")
|
|
seasonDir := filepath.Join(sourceDir, "Season 01")
|
|
targetRoot := filepath.Join(root, "media")
|
|
if err := os.MkdirAll(seasonDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.MkdirAll(targetRoot, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sourceFile := filepath.Join(seasonDir, "Show.S01E01.mkv")
|
|
if err := os.WriteFile(sourceFile, []byte("episode"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
svc := newFileManagerTestService(t, root)
|
|
|
|
res, err := svc.Transfer(sourceDir, targetRoot, TransferHardlink)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetFile := filepath.Join(res.Path, "Season 01", "Show.S01E01.mkv")
|
|
sourceInfo, err := os.Stat(sourceFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
targetInfo, err := os.Stat(targetFile)
|
|
if err != nil {
|
|
t.Fatalf("hardlinked directory file missing: %v", err)
|
|
}
|
|
if !os.SameFile(sourceInfo, targetInfo) {
|
|
t.Fatal("directory hardlink should hardlink contained files")
|
|
}
|
|
listing, err := svc.List(res.Path, 100, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found := false
|
|
for _, entry := range listing.Entries {
|
|
if entry.Path == targetFile {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("target directory listing did not include %s", targetFile)
|
|
}
|
|
}
|
|
|
|
func TestFileManagerRefusesRootMutation(t *testing.T) {
|
|
root := t.TempDir()
|
|
svc := newFileManagerTestService(t, root)
|
|
if err := svc.Delete(root); !errors.Is(err, ErrRootMutation) {
|
|
t.Fatalf("Delete(root) err = %v, want ErrRootMutation", err)
|
|
}
|
|
}
|
|
|
|
func hardlinksUnsupported(t *testing.T, root string) bool {
|
|
t.Helper()
|
|
src := filepath.Join(root, "hardlink-probe-src")
|
|
dst := filepath.Join(root, "hardlink-probe-dst")
|
|
if err := os.WriteFile(src, []byte("probe"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err := os.Link(src, dst)
|
|
_ = os.Remove(src)
|
|
_ = os.Remove(dst)
|
|
return err != nil
|
|
}
|
|
|
|
func TestFileManagerIncludesConfiguredOrganizeRoots(t *testing.T) {
|
|
root := t.TempDir()
|
|
sourceDir := filepath.Join(root, "downloads")
|
|
targetDir := filepath.Join(root, "media")
|
|
qbDir := filepath.Join(root, "qb-save")
|
|
for _, dir := range []string{sourceDir, targetDir, qbDir} {
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
svc, repos := newFileManagerTestServiceWithRepo(t, root)
|
|
if err := repos.Setting.Set(t.Context(), "organize.source_dir", sourceDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repos.Setting.Set(t.Context(), "organize.target_dir", targetDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repos.Setting.Set(t.Context(), "qbittorrent.savepath", qbDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
listing, err := svc.List("", 100)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := map[string]string{}
|
|
for _, root := range listing.Roots {
|
|
got[root.Label] = root.Path
|
|
}
|
|
for label, want := range map[string]string{
|
|
"organize-source": filepath.Clean(sourceDir),
|
|
"organize-target": filepath.Clean(targetDir),
|
|
"qb-savepath": filepath.Clean(qbDir),
|
|
} {
|
|
if got[label] != want {
|
|
t.Fatalf("root %s = %q, want %q; roots=%#v", label, got[label], want, listing.Roots)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFileManagerIncludesAllLibraryRoots(t *testing.T) {
|
|
rootA := t.TempDir()
|
|
rootB := t.TempDir()
|
|
nestedB := filepath.Join(rootB, "second-root")
|
|
if err := os.MkdirAll(nestedB, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
db := newServiceTestDB(t, &model.Library{}, &model.LibraryRoot{}, &model.Media{}, &model.Setting{})
|
|
repos := repository.New(db)
|
|
lib := &model.Library{Name: "电影", Path: rootA, Type: "movie", Enabled: true}
|
|
if err := repos.Library.CreateWithRoots(t.Context(), lib, []model.LibraryRoot{
|
|
{Name: "硬盘1", Path: rootA, Enabled: true},
|
|
{Name: "硬盘2", Path: rootB, Enabled: true, SortOrder: 1},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg := &config.Config{}
|
|
cfg.App.DataDir = t.TempDir()
|
|
cfg.Cache.CacheDir = t.TempDir()
|
|
svc := NewFileManagerService(cfg, zap.NewNop(), repos)
|
|
|
|
listing, err := svc.List("", 100)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := map[string]string{}
|
|
for _, root := range listing.Roots {
|
|
got[root.Label] = root.Path
|
|
}
|
|
if got["library:电影:硬盘1"] != filepath.Clean(rootA) {
|
|
t.Fatalf("root A missing from listing: %#v", listing.Roots)
|
|
}
|
|
if got["library:电影:硬盘2"] != filepath.Clean(rootB) {
|
|
t.Fatalf("root B missing from listing: %#v", listing.Roots)
|
|
}
|
|
if _, err := svc.List(nestedB, 100); err != nil {
|
|
t.Fatalf("list second library root: %v", err)
|
|
}
|
|
}
|