mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 11:36:36 +08:00
fix docker media path normalization
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -352,6 +353,44 @@ func TestListMediaVisibleUsesSpecificCloudChildLibraryAsDisplayTarget(t *testing
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMediaUsesMappedLocalCategoryLibraryAsDisplayTarget(t *testing.T) {
|
||||
containerRoot := filepath.Join(t.TempDir(), "media")
|
||||
t.Setenv("MEDIASTATION_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
|
||||
db := newServiceTestDB(t, &model.Library{}, &model.Media{})
|
||||
repos := repository.New(db)
|
||||
parent := model.Library{Name: "电视剧", Path: containerRoot, Type: "tv", Enabled: true}
|
||||
child := model.Library{Name: "国产剧", Path: filepath.Join("media", "电视剧", "国产剧"), Type: "tv", Enabled: true}
|
||||
for _, lib := range []*model.Library{&parent, &child} {
|
||||
if err := repos.Library.Create(t.Context(), lib); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
mediaPath := filepath.Join(containerRoot, "电视剧", "国产剧", "剧集", "Season 01", "剧集 - S01E01.mkv")
|
||||
if err := repos.DB.Create(&model.Media{
|
||||
Base: model.Base{ID: "media-1"},
|
||||
LibraryID: parent.ID,
|
||||
Title: "剧集",
|
||||
Path: mediaPath,
|
||||
SeasonNum: 1,
|
||||
EpisodeNum: 1,
|
||||
}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
svc := NewMediaService(&config.Config{}, zap.NewNop(), repos)
|
||||
|
||||
got, err := svc.GetMedia(t.Context(), "media-1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.DisplayLibraryID != child.ID {
|
||||
t.Fatalf("display library = %q, want mapped child library %q", got.DisplayLibraryID, child.ID)
|
||||
}
|
||||
if got.DisplayLibraryPath != filepath.Clean(filepath.Join(containerRoot, "电视剧", "国产剧")) {
|
||||
t.Fatalf("display library path = %q, want mapped child path", got.DisplayLibraryPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartAllCloudLibraryScansIncludesMergedCloudMounts(t *testing.T) {
|
||||
db := newServiceTestDB(t, &model.Library{}, &model.Media{})
|
||||
repos := repository.New(db)
|
||||
|
||||
@@ -92,11 +92,11 @@ func (s *FileManagerService) allowedRoots() (map[string]string, error) {
|
||||
if label == "" {
|
||||
label = fmt.Sprintf("路径%d", i+1)
|
||||
}
|
||||
add("library:"+l.Name+":"+label, root.Path)
|
||||
add("library:"+l.Name+":"+label, resolveMappedDestinationPath(root.Path))
|
||||
}
|
||||
continue
|
||||
}
|
||||
add("library:"+l.Name, l.Path)
|
||||
add("library:"+l.Name, resolveMappedDestinationPath(l.Path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ func (s *MediaService) attachLibraryMetadata(ctx context.Context, items []model.
|
||||
return
|
||||
}
|
||||
byID := make(map[string]model.Library, len(libs))
|
||||
for _, lib := range libs {
|
||||
for i := range libs {
|
||||
libs[i] = normalizeLocalLibraryPathForDisplay(libs[i])
|
||||
lib := libs[i]
|
||||
byID[lib.ID] = lib
|
||||
}
|
||||
resolver := newMediaDisplayLibraryResolver(ctx, s.repo, libs)
|
||||
@@ -58,7 +60,8 @@ func newMediaDisplayLibraryResolver(ctx context.Context, repo *repository.Contai
|
||||
displayLibraries: displayLibraries,
|
||||
}
|
||||
for _, lib := range libs {
|
||||
resolver.byID[lib.ID] = lib
|
||||
normalized := normalizeLocalLibraryPathForDisplay(lib)
|
||||
resolver.byID[normalized.ID] = normalized
|
||||
}
|
||||
for _, lib := range displayLibraries {
|
||||
resolver.displayByID[lib.ID] = lib
|
||||
@@ -146,13 +149,16 @@ func (r mediaDisplayLibraryResolver) bestPathDisplayLibrary(media model.Media) (
|
||||
}
|
||||
|
||||
mediaPath := cleanPathForVolumeMapping(media.Path)
|
||||
if isRelativeVolumeMarkerPath(media.Path) {
|
||||
mediaPath = cleanPathForVolumeMapping(resolveMappedDestinationPath(media.Path))
|
||||
}
|
||||
var best model.Library
|
||||
bestLen := 0
|
||||
for _, lib := range r.displayLibraries {
|
||||
if _, ok := ParseCloudLibraryMount(lib.Path); ok || !lib.Enabled {
|
||||
continue
|
||||
}
|
||||
libPath := cleanPathForVolumeMapping(lib.Path)
|
||||
libPath := cleanPathForVolumeMapping(resolveMappedDestinationPath(lib.Path))
|
||||
if libPath == "" || libPath == "." {
|
||||
continue
|
||||
}
|
||||
@@ -169,3 +175,17 @@ func (r mediaDisplayLibraryResolver) bestPathDisplayLibrary(media model.Media) (
|
||||
}
|
||||
return model.Library{}, false
|
||||
}
|
||||
|
||||
func normalizeLocalLibraryPathForDisplay(lib model.Library) model.Library {
|
||||
if _, ok := ParseCloudLibraryMount(lib.Path); ok {
|
||||
return lib
|
||||
}
|
||||
lib.Path = resolveMappedDestinationPath(lib.Path)
|
||||
for i := range lib.Roots {
|
||||
if _, ok := ParseCloudLibraryMount(lib.Roots[i].Path); ok {
|
||||
continue
|
||||
}
|
||||
lib.Roots[i].Path = resolveMappedDestinationPath(lib.Roots[i].Path)
|
||||
}
|
||||
return lib
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func resolveMappedDestinationPath(path string) string {
|
||||
return ""
|
||||
}
|
||||
clean := filepath.Clean(path)
|
||||
if _, err := os.Stat(clean); err == nil {
|
||||
if _, err := os.Stat(clean); err == nil && !isRelativeVolumeMarkerPath(clean) {
|
||||
return clean
|
||||
}
|
||||
for _, candidate := range mappedPathCandidates(clean) {
|
||||
@@ -97,6 +97,11 @@ func mappedPathCandidates(input string) []string {
|
||||
candidates = append(candidates, candidate)
|
||||
}
|
||||
clean := filepath.Clean(input)
|
||||
if isRelativeVolumeMarkerPath(clean) {
|
||||
for _, candidate := range dockerVolumePathCandidates(clean) {
|
||||
add(candidate)
|
||||
}
|
||||
}
|
||||
add(clean)
|
||||
for _, candidate := range dockerVolumePathCandidates(input) {
|
||||
add(candidate)
|
||||
@@ -141,6 +146,22 @@ func dockerVolumePathCandidates(path string) []string {
|
||||
{env: "MEDIASTATION_MEDIA_DIR", container: envOrDefault("MEDIASTATION_MEDIA_CONTAINER_DIR", "/media")},
|
||||
{env: "MEDIASTATION_DOWNLOAD_DIR", container: envOrDefault("MEDIASTATION_DOWNLOAD_CONTAINER_DIR", "/downloads")},
|
||||
} {
|
||||
container := cleanPathForVolumeMapping(mapping.container)
|
||||
if container == "." || container == "" || strings.HasPrefix(container, ".") {
|
||||
continue
|
||||
}
|
||||
relativeMarker := strings.TrimPrefix(normalized, "./")
|
||||
if !strings.HasPrefix(relativeMarker, "/") {
|
||||
containerBase := strings.TrimSpace(filepath.Base(container))
|
||||
if relativeMarker == containerBase {
|
||||
addCandidate(container)
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(relativeMarker, containerBase+"/") {
|
||||
addCandidate(container + strings.TrimPrefix(relativeMarker, containerBase))
|
||||
continue
|
||||
}
|
||||
}
|
||||
host := cleanPathForVolumeMapping(os.Getenv(mapping.env))
|
||||
if host == "." || host == "" || strings.HasPrefix(host, ".") {
|
||||
continue
|
||||
@@ -152,10 +173,6 @@ func dockerVolumePathCandidates(path string) []string {
|
||||
if strings.HasPrefix(normalized, host+"/") {
|
||||
addCandidate(mapping.container + strings.TrimPrefix(normalized, host))
|
||||
}
|
||||
container := cleanPathForVolumeMapping(mapping.container)
|
||||
if container == "." || container == "" || strings.HasPrefix(container, ".") {
|
||||
continue
|
||||
}
|
||||
if normalized == container {
|
||||
addCandidate(host)
|
||||
continue
|
||||
@@ -187,6 +204,20 @@ func dockerVolumePathCandidates(path string) []string {
|
||||
return candidates
|
||||
}
|
||||
|
||||
func isRelativeVolumeMarkerPath(path string) bool {
|
||||
normalized := cleanPathForVolumeMapping(path)
|
||||
normalized = strings.TrimPrefix(normalized, "./")
|
||||
if normalized == "" || strings.HasPrefix(normalized, "/") || filepath.IsAbs(path) {
|
||||
return false
|
||||
}
|
||||
for _, marker := range []string{"media", "downloads"} {
|
||||
if normalized == marker || strings.HasPrefix(normalized, marker+"/") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cleanPathForVolumeMapping(path string) string {
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" {
|
||||
|
||||
@@ -88,6 +88,24 @@ func TestResolveAccessibleLibraryPathKeepsAccessibleContainerPath(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveAccessibleLibraryPathMapsRelativeDockerMediaMarker(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
containerRoot := filepath.Join(root, "container", "media")
|
||||
containerLibrary := filepath.Join(containerRoot, "电视剧", "国产剧")
|
||||
if err := os.MkdirAll(containerLibrary, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MEDIASTATION_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
|
||||
got, err := resolveAccessibleLibraryPath(filepath.Join("media", "电视剧", "国产剧"))
|
||||
if err != nil {
|
||||
t.Fatalf("resolveAccessibleLibraryPath() error = %v", err)
|
||||
}
|
||||
if got != filepath.Clean(containerLibrary) {
|
||||
t.Fatalf("resolveAccessibleLibraryPath() = %q, want %q", got, filepath.Clean(containerLibrary))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferLibraryKindFromCategoryPathOverridesMovieDefault(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
@@ -142,6 +160,26 @@ func TestResolveMappedDestinationPathPrefersConfiguredContainerMapping(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveMappedDestinationPathPrefersContainerForRelativeMediaMarker(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
workDir := filepath.Join(root, "work")
|
||||
containerMedia := filepath.Join(root, "container", "media")
|
||||
if err := os.MkdirAll(filepath.Join(workDir, "media", "电视剧", "国产剧"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(containerMedia, "电视剧", "国产剧"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Chdir(workDir)
|
||||
t.Setenv("MEDIASTATION_MEDIA_CONTAINER_DIR", containerMedia)
|
||||
|
||||
got := resolveMappedDestinationPath(filepath.Join("media", "电视剧", "国产剧"))
|
||||
want := filepath.Join(containerMedia, "电视剧", "国产剧")
|
||||
if got != filepath.Clean(want) {
|
||||
t.Fatalf("resolveMappedDestinationPath() = %q, want %q", got, filepath.Clean(want))
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveAccessibleMappedPathMapsWindowsDownloadVariants(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
containerDownloads := filepath.Join(root, "container", "downloads")
|
||||
|
||||
@@ -130,7 +130,7 @@ func (o *OrganizerService) OrganizeLibraryWithOptions(ctx context.Context, libra
|
||||
return nil, errors.New("organize source must be a local directory; cloud libraries should be managed from external storage scan/mount")
|
||||
}
|
||||
// 目的地目录:已位于该根下的文件视为已整理;受 dest_path 覆盖与设置影响。
|
||||
baseRoot := normalizeOrganizeDestinationRoot(o.resolveBaseRoot(ctx, lib, opts.DestPath))
|
||||
baseRoot := normalizeMappedOrganizeDestinationRoot(o.resolveBaseRoot(ctx, lib, opts.DestPath))
|
||||
if _, ok := ParseCloudLibraryMount(baseRoot); ok {
|
||||
return nil, errors.New("organize destination must be a local writable media directory; enable cloud transfer in external storage when writing to cloud")
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func (o *OrganizerService) organizeLibraryForLayout(ctx context.Context, destRoo
|
||||
}
|
||||
return model.Library{}, false
|
||||
}
|
||||
destRoot = filepath.Clean(strings.TrimSpace(destRoot))
|
||||
destRoot = normalizeMappedOrganizeDestinationRoot(destRoot)
|
||||
mediaType = normalizeOrganizeMediaType(mediaType)
|
||||
aliases := o.organizeCategoryAliases(mediaType, category)
|
||||
|
||||
@@ -48,6 +48,7 @@ func (o *OrganizerService) organizeLibraryForLayout(ctx context.Context, destRoo
|
||||
// "手动整理"等暂存库不作为入库目标,避免把媒体留在暂存目录里。
|
||||
continue
|
||||
}
|
||||
lib.Path = resolveMappedDestinationPath(lib.Path)
|
||||
scopeScore, inScope := o.organizeLibraryTargetScopeScore(lib.Path, destRoot, mediaType, category)
|
||||
if !inScope {
|
||||
continue
|
||||
@@ -183,7 +184,7 @@ func (o *OrganizerService) ensureOrganizeLibraryForRoot(ctx context.Context, roo
|
||||
if o == nil || o.repo == nil || o.repo.Library == nil {
|
||||
return model.Library{}, false
|
||||
}
|
||||
root = filepath.Clean(strings.TrimSpace(root))
|
||||
root = resolveMappedDestinationPath(root)
|
||||
if root == "" || root == "." {
|
||||
return model.Library{}, false
|
||||
}
|
||||
@@ -206,7 +207,7 @@ func (o *OrganizerService) ensureOrganizeLibraryForRoot(ctx context.Context, roo
|
||||
if _, ok := ParseCloudLibraryMount(lib.Path); ok {
|
||||
continue
|
||||
}
|
||||
lib.Path = filepath.Clean(lib.Path)
|
||||
lib.Path = resolveMappedDestinationPath(lib.Path)
|
||||
if strings.EqualFold(lib.Path, root) {
|
||||
return lib, true
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func (o *OrganizerService) resolveOrganizeMediaRequest(ctx context.Context, medi
|
||||
}
|
||||
requestedBaseRoot := o.resolveBaseRoot(ctx, lib, opts.DestPath)
|
||||
mediaType, mediaCategory := o.effectiveOrganizeOverrides(opts, requestedBaseRoot)
|
||||
baseRoot := normalizeOrganizeDestinationRoot(requestedBaseRoot)
|
||||
baseRoot := normalizeMappedOrganizeDestinationRoot(requestedBaseRoot)
|
||||
if _, ok := ParseCloudLibraryMount(baseRoot); ok {
|
||||
return organizeMediaRequest{}, errors.New("organize destination must be a local writable media directory; enable cloud transfer in external storage when writing to cloud")
|
||||
}
|
||||
|
||||
@@ -235,6 +235,10 @@ func normalizeOrganizeDestinationRoot(root string) string {
|
||||
return cleaned
|
||||
}
|
||||
|
||||
func normalizeMappedOrganizeDestinationRoot(root string) string {
|
||||
return normalizeOrganizeDestinationRoot(resolveMappedDestinationPath(root))
|
||||
}
|
||||
|
||||
func pathAlreadyEndsWith(path, suffix string) bool {
|
||||
base := strings.TrimSpace(filepath.Base(filepath.Clean(path)))
|
||||
return strings.EqualFold(base, suffix)
|
||||
|
||||
@@ -167,7 +167,7 @@ func (o *OrganizerService) reclassifyScannedMedia(ctx context.Context, media mod
|
||||
return false, nil
|
||||
}
|
||||
|
||||
baseRoot := normalizeOrganizeDestinationRoot(requestedBaseRoot)
|
||||
baseRoot := normalizeMappedOrganizeDestinationRoot(requestedBaseRoot)
|
||||
targetLibrary, matched := o.organizeLibraryForLayout(ctx, baseRoot, mediaType, category)
|
||||
if !matched || strings.TrimSpace(targetLibrary.ID) == "" || strings.TrimSpace(targetLibrary.Path) == "" {
|
||||
targetRoot := categoryRoot(o.organizeRoot(baseRoot, mediaType, category), category)
|
||||
|
||||
@@ -88,6 +88,12 @@ func New(cfg *config.Config, log *zap.Logger, repos *repository.Container) *Cont
|
||||
// Boot 启动后台工作进程(watcher, downloads poller, subscription scheduler)。
|
||||
// 在 AutoMigrate 后调用一次。
|
||||
func (c *Container) Boot() {
|
||||
if err := c.NormalizeLocalLibraryPaths(c.stopCtx); err != nil {
|
||||
c.Log.Warn("normalize local library paths failed", zap.Error(err))
|
||||
}
|
||||
if err := c.NormalizeCloudLibraryTypes(c.stopCtx); err != nil {
|
||||
c.Log.Warn("normalize cloud library types failed", zap.Error(err))
|
||||
}
|
||||
if err := c.Watcher.Start(c.stopCtx); err != nil {
|
||||
c.Log.Warn("watcher start failed", zap.Error(err))
|
||||
}
|
||||
@@ -96,9 +102,6 @@ func (c *Container) Boot() {
|
||||
if err := c.APIConfig.SeedDefaults(c.stopCtx); err != nil {
|
||||
c.Log.Warn("api config seed failed", zap.Error(err))
|
||||
}
|
||||
if err := c.NormalizeCloudLibraryTypes(c.stopCtx); err != nil {
|
||||
c.Log.Warn("normalize cloud library types failed", zap.Error(err))
|
||||
}
|
||||
go c.warmMediaSearchIndex(c.stopCtx)
|
||||
|
||||
// 加载所有已配置的下载客户端
|
||||
|
||||
@@ -186,12 +186,12 @@ func (b *serviceContainerBuilder) libraryRoots() []string {
|
||||
if !root.Enabled || strings.TrimSpace(root.Path) == "" {
|
||||
continue
|
||||
}
|
||||
roots = append(roots, root.Path)
|
||||
roots = append(roots, resolveMappedDestinationPath(root.Path))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(l.Path) != "" {
|
||||
roots = append(roots, l.Path)
|
||||
roots = append(roots, resolveMappedDestinationPath(l.Path))
|
||||
}
|
||||
}
|
||||
return roots
|
||||
|
||||
@@ -2,10 +2,82 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ShukeBta/MediaStationGo/internal/model"
|
||||
)
|
||||
|
||||
func (c *Container) NormalizeLocalLibraryPaths(ctx context.Context) error {
|
||||
if c == nil || c.Repo == nil || c.Repo.Library == nil || c.Repo.DB == nil {
|
||||
return nil
|
||||
}
|
||||
libs, err := c.Repo.Library.List(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, lib := range libs {
|
||||
if _, ok := ParseCloudLibraryMount(lib.Path); ok {
|
||||
continue
|
||||
}
|
||||
if len(lib.Roots) == 0 {
|
||||
normalized := normalizePersistedLocalLibraryPath(lib.Path)
|
||||
if sameLibraryPath(normalized, lib.Path) {
|
||||
continue
|
||||
}
|
||||
if err := c.Repo.DB.WithContext(ctx).
|
||||
Model(&model.Library{}).
|
||||
Where("id = ?", lib.ID).
|
||||
Update("path", normalized).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
primaryPath := ""
|
||||
for i, root := range lib.Roots {
|
||||
if _, ok := ParseCloudLibraryMount(root.Path); ok {
|
||||
if i == 0 {
|
||||
primaryPath = root.Path
|
||||
}
|
||||
continue
|
||||
}
|
||||
normalized := normalizePersistedLocalLibraryPath(root.Path)
|
||||
if i == 0 {
|
||||
primaryPath = normalized
|
||||
}
|
||||
if sameLibraryPath(normalized, root.Path) || strings.TrimSpace(root.ID) == "" {
|
||||
continue
|
||||
}
|
||||
if err := c.Repo.DB.WithContext(ctx).
|
||||
Model(&model.LibraryRoot{}).
|
||||
Where("id = ?", root.ID).
|
||||
Update("path", normalized).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(primaryPath) != "" && !sameLibraryPath(primaryPath, lib.Path) {
|
||||
if err := c.Repo.DB.WithContext(ctx).
|
||||
Model(&model.Library{}).
|
||||
Where("id = ?", lib.ID).
|
||||
Update("path", primaryPath).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizePersistedLocalLibraryPath(pathValue string) string {
|
||||
pathValue = strings.TrimSpace(pathValue)
|
||||
if pathValue == "" {
|
||||
return ""
|
||||
}
|
||||
if !isRelativeVolumeMarkerPath(pathValue) {
|
||||
return filepath.Clean(pathValue)
|
||||
}
|
||||
return resolveMappedDestinationPath(pathValue)
|
||||
}
|
||||
|
||||
func (c *Container) NormalizeCloudLibraryTypes(ctx context.Context) error {
|
||||
if c == nil || c.Repo == nil || c.Repo.Library == nil || c.Repo.DB == nil {
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ShukeBta/MediaStationGo/internal/model"
|
||||
"github.com/ShukeBta/MediaStationGo/internal/repository"
|
||||
)
|
||||
|
||||
func TestNormalizeLocalLibraryPathsRewritesRelativeDockerMediaRoot(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
containerRoot := filepath.Join(root, "container", "media")
|
||||
containerLibrary := filepath.Join(containerRoot, "电视剧", "国产剧")
|
||||
if err := os.MkdirAll(containerLibrary, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MEDIASTATION_MEDIA_CONTAINER_DIR", containerRoot)
|
||||
|
||||
db := newServiceTestDB(t, &model.Library{}, &model.LibraryRoot{}, &model.Media{})
|
||||
repos := repository.New(db)
|
||||
lib := model.Library{Name: "国产剧", Path: filepath.Join("media", "电视剧", "国产剧"), Type: "tv", Enabled: true}
|
||||
if err := repos.Library.CreateWithRoots(t.Context(), &lib, []model.LibraryRoot{{
|
||||
Name: "国产剧",
|
||||
Path: filepath.Join("media", "电视剧", "国产剧"),
|
||||
Enabled: true,
|
||||
}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
svc := &Container{Repo: repos}
|
||||
if err := svc.NormalizeLocalLibraryPaths(t.Context()); err != nil {
|
||||
t.Fatalf("NormalizeLocalLibraryPaths() error = %v", err)
|
||||
}
|
||||
|
||||
got, err := repos.Library.FindByID(t.Context(), lib.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := filepath.Clean(containerLibrary)
|
||||
if got.Path != want {
|
||||
t.Fatalf("library path = %q, want %q", got.Path, want)
|
||||
}
|
||||
if len(got.Roots) != 1 || got.Roots[0].Path != want {
|
||||
t.Fatalf("library roots = %#v, want path %q", got.Roots, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePersistedLocalLibraryPathKeepsEmptyPathEmpty(t *testing.T) {
|
||||
if got := normalizePersistedLocalLibraryPath(""); got != "" {
|
||||
t.Fatalf("normalizePersistedLocalLibraryPath(empty) = %q, want empty", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user