mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 03:56:38 +08:00
fix: keep existing library files in place
This commit is contained in:
@@ -84,7 +84,7 @@ func importSTRMHandler(svc *service.Container) gin.HandlerFunc {
|
||||
STRMURL: req.URL,
|
||||
Container: "strm",
|
||||
}
|
||||
if err := svc.Repo.DB.WithContext(c.Request.Context()).Create(m).Error; err != nil {
|
||||
if err := svc.Repo.Media.Upsert(c.Request.Context(), m); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -276,6 +276,9 @@ func (r *MediaRepository) Upsert(ctx context.Context, m *model.Media) error {
|
||||
if m.EpisodeNum > 0 && existing.EpisodeNum != m.EpisodeNum {
|
||||
updates["episode_num"] = m.EpisodeNum
|
||||
}
|
||||
if m.STRMURL != "" {
|
||||
updates["strm_url"] = m.STRMURL
|
||||
}
|
||||
|
||||
if err := r.db.WithContext(ctx).Unscoped().Model(&model.Media{}).
|
||||
Where("id = ?", existing.ID).Updates(updates).Error; err != nil {
|
||||
|
||||
@@ -150,6 +150,10 @@ func (o *OrganizerService) OrganizeMedia(ctx context.Context, mediaID string) (s
|
||||
// OrganizeLibrary organizes every media row in a library whose file is
|
||||
// not already in the expected path structure.
|
||||
func (o *OrganizerService) OrganizeLibrary(ctx context.Context, libraryID string) (*OrganizeResult, error) {
|
||||
lib, err := o.repo.Library.FindByID(ctx, libraryID)
|
||||
if err != nil || lib == nil {
|
||||
return nil, errors.New("library not found")
|
||||
}
|
||||
var rows []model.Media
|
||||
if err := o.repo.DB.WithContext(ctx).
|
||||
Where("library_id = ? AND deleted_at IS NULL", libraryID).
|
||||
@@ -158,6 +162,10 @@ func (o *OrganizerService) OrganizeLibrary(ctx context.Context, libraryID string
|
||||
}
|
||||
res := &OrganizeResult{}
|
||||
for i := range rows {
|
||||
if pathWithin(rows[i].Path, lib.Path) {
|
||||
res.Skipped++
|
||||
continue
|
||||
}
|
||||
dst, err := o.OrganizeMedia(ctx, rows[i].ID)
|
||||
if err != nil {
|
||||
res.Errors = append(res.Errors, fmt.Sprintf("%s: %s", rows[i].Title, err.Error()))
|
||||
@@ -290,6 +298,19 @@ func categoryRoot(root, category string) string {
|
||||
return filepath.Join(root, category)
|
||||
}
|
||||
|
||||
func pathWithin(path, root string) bool {
|
||||
cleanPath := filepath.Clean(path)
|
||||
cleanRoot := filepath.Clean(root)
|
||||
if strings.EqualFold(cleanPath, cleanRoot) {
|
||||
return true
|
||||
}
|
||||
rel, err := filepath.Rel(cleanRoot, cleanPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator))
|
||||
}
|
||||
|
||||
func mediaTypeRootDir(mediaType string) string {
|
||||
switch normalizeMediaType(mediaType, "", "") {
|
||||
case "movie":
|
||||
|
||||
@@ -232,3 +232,64 @@ func TestOrganizeMediaDoesNotRepeatCategoryWhenLibraryIsCategoryRoot(t *testing.
|
||||
t.Fatalf("repeated category path exists or stat failed unexpectedly: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrganizeLibrarySkipsFilesAlreadyInsideLibrary(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
libraryRoot := filepath.Join(root, "media", "电视剧", "国产剧")
|
||||
sourceDir := filepath.Join(libraryRoot, "Existing Show")
|
||||
if err := os.MkdirAll(sourceDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
source := filepath.Join(sourceDir, "Existing Show S01E02.mkv")
|
||||
if err := os.WriteFile(source, []byte("episode"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.Library{}, &model.Media{}, &model.Setting{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repos := repository.New(db)
|
||||
if err := repos.Setting.Set(t.Context(), "organizer.smart_classify", "true"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lib := model.Library{Name: "国产剧", Path: libraryRoot, Type: "tv", Enabled: true}
|
||||
if err := repos.Library.Create(t.Context(), &lib); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
media := model.Media{
|
||||
LibraryID: lib.ID,
|
||||
Title: "Existing Show",
|
||||
Path: source,
|
||||
Container: "mkv",
|
||||
Countries: "CN",
|
||||
SeasonNum: 1,
|
||||
EpisodeNum: 2,
|
||||
ScrapeStatus: "matched",
|
||||
}
|
||||
if err := repos.Media.Upsert(t.Context(), &media); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
organizer := NewOrganizerService(&config.Config{}, zap.NewNop(), repos)
|
||||
result, err := organizer.OrganizeLibrary(t.Context(), lib.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Organized != 0 || result.Skipped != 1 {
|
||||
t.Fatalf("result = %+v, want organized=0 skipped=1", result)
|
||||
}
|
||||
if _, err := os.Stat(source); err != nil {
|
||||
t.Fatalf("source should remain untouched: %v", err)
|
||||
}
|
||||
var refreshed model.Media
|
||||
if err := db.First(&refreshed, "id = ?", media.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if refreshed.Path != source {
|
||||
t.Fatalf("media path = %q, want unchanged %q", refreshed.Path, source)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user