mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-08 14:26:37 +08:00
split organizer reclassify helpers
This commit is contained in:
@@ -8,8 +8,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/ShukeBta/MediaStationGo/internal/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type organizeExistingReclassifyRequest struct {
|
type organizeExistingReclassifyRequest struct {
|
||||||
@@ -216,166 +214,3 @@ func (o *OrganizerService) nextReclassifyConflictTarget(ctx context.Context, tar
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func safeToRemoveReclassifiedDuplicate(path, target string) bool {
|
|
||||||
info, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
targetInfo, err := os.Stat(target)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if os.SameFile(info, targetInfo) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return info.Size() > 0 && info.Size() == targetInfo.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func organizeFileExists(path string) bool {
|
|
||||||
_, err := os.Stat(path)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func moveSidecarNFO(oldMedia, newMedia string) error {
|
|
||||||
oldNFO := nfoPath(oldMedia)
|
|
||||||
newNFO := nfoPath(newMedia)
|
|
||||||
if oldNFO == newNFO || !organizeFileExists(oldNFO) || organizeFileExists(newNFO) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(filepath.Dir(newNFO), 0o755); err != nil { // #nosec G301 -- sidecar media directories must remain readable by NAS/player users.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return moveFile(oldNFO, newNFO)
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeMediaAndNFO(path string) error {
|
|
||||||
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if nfo := nfoPath(path); nfo != "" {
|
|
||||||
if err := os.Remove(nfo); err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OrganizerService) updateReclassifiedMediaRow(ctx context.Context, oldPath, newPath string, req organizeExistingReclassifyRequest) error {
|
|
||||||
if o == nil || o.repo == nil || o.repo.DB == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
updates := map[string]any{
|
|
||||||
"path": newPath,
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(req.TargetLibraryID) != "" {
|
|
||||||
updates["library_id"] = strings.TrimSpace(req.TargetLibraryID)
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(req.Title) != "" {
|
|
||||||
updates["title"] = strings.TrimSpace(req.Title)
|
|
||||||
}
|
|
||||||
if req.Year > 0 {
|
|
||||||
updates["year"] = req.Year
|
|
||||||
}
|
|
||||||
if normalizeOrganizeMediaType(req.MediaType) == "movie" {
|
|
||||||
updates["season_num"] = 0
|
|
||||||
updates["episode_num"] = 0
|
|
||||||
} else if req.Season > 0 {
|
|
||||||
updates["season_num"] = req.Season
|
|
||||||
if req.Episode > 0 {
|
|
||||||
updates["episode_num"] = req.Episode
|
|
||||||
}
|
|
||||||
} else if req.Episode > 0 {
|
|
||||||
updates["episode_num"] = req.Episode
|
|
||||||
}
|
|
||||||
applyReclassifyMatchUpdates(updates, req.MetadataMatch)
|
|
||||||
return o.repo.DB.WithContext(ctx).Model(&model.Media{}).Where("path = ?", oldPath).Updates(updates).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyReclassifyMatchUpdates(updates map[string]any, match *Match) {
|
|
||||||
if updates == nil || match == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if value := strings.TrimSpace(match.Title); value != "" {
|
|
||||||
updates["title"] = value
|
|
||||||
}
|
|
||||||
if value := strings.TrimSpace(match.OriginalName); value != "" {
|
|
||||||
updates["original_name"] = value
|
|
||||||
}
|
|
||||||
if value := strings.TrimSpace(match.Overview); value != "" {
|
|
||||||
updates["overview"] = value
|
|
||||||
}
|
|
||||||
if value := strings.TrimSpace(match.PosterURL); value != "" {
|
|
||||||
updates["poster_url"] = value
|
|
||||||
}
|
|
||||||
if value := strings.TrimSpace(match.BackdropURL); value != "" {
|
|
||||||
updates["backdrop_url"] = value
|
|
||||||
}
|
|
||||||
if match.Rating > 0 {
|
|
||||||
updates["rating"] = match.Rating
|
|
||||||
}
|
|
||||||
if match.Year > 0 {
|
|
||||||
updates["year"] = match.Year
|
|
||||||
}
|
|
||||||
if match.TMDbID > 0 {
|
|
||||||
updates["tm_db_id"] = match.TMDbID
|
|
||||||
}
|
|
||||||
if match.BangumiID > 0 {
|
|
||||||
updates["bangumi_id"] = match.BangumiID
|
|
||||||
}
|
|
||||||
if value := strings.TrimSpace(match.DoubanID); value != "" {
|
|
||||||
updates["douban_id"] = value
|
|
||||||
}
|
|
||||||
if value := strings.TrimSpace(match.TheTVDBID); value != "" {
|
|
||||||
updates["thetvdb_id"] = value
|
|
||||||
}
|
|
||||||
if len(match.Genres) > 0 {
|
|
||||||
updates["genres"] = strings.Join(match.Genres, ",")
|
|
||||||
}
|
|
||||||
if len(match.Countries) > 0 {
|
|
||||||
updates["countries"] = strings.Join(match.Countries, ",")
|
|
||||||
}
|
|
||||||
if len(match.Languages) > 0 {
|
|
||||||
updates["languages"] = strings.Join(match.Languages, ",")
|
|
||||||
}
|
|
||||||
if match.NSFW {
|
|
||||||
updates["nsfw"] = true
|
|
||||||
}
|
|
||||||
updates["scrape_status"] = "matched"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OrganizerService) deleteMediaRowForPath(ctx context.Context, path string) {
|
|
||||||
if o == nil || o.repo == nil || o.repo.DB == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = o.repo.DB.WithContext(ctx).Where("path = ?", path).Delete(&model.Media{}).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OrganizerService) mediaPathExists(ctx context.Context, path string) bool {
|
|
||||||
if o == nil || o.repo == nil || o.repo.DB == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
var count int64
|
|
||||||
if err := o.repo.DB.WithContext(ctx).Unscoped().Model(&model.Media{}).Where("path = ?", path).Count(&count).Error; err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return count > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupEmptyMediaDirs(startDir, stopRoot string) {
|
|
||||||
dir := filepath.Clean(strings.TrimSpace(startDir))
|
|
||||||
stopRoot = filepath.Clean(strings.TrimSpace(stopRoot))
|
|
||||||
for dir != "" && dir != "." {
|
|
||||||
if stopRoot != "" && stopRoot != "." && (!pathWithin(dir, stopRoot) || strings.EqualFold(dir, stopRoot)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := os.Remove(dir); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parent := filepath.Dir(dir)
|
|
||||||
if parent == dir {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dir = parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func safeToRemoveReclassifiedDuplicate(path, target string) bool {
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
targetInfo, err := os.Stat(target)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if os.SameFile(info, targetInfo) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return info.Size() > 0 && info.Size() == targetInfo.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
func organizeFileExists(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveSidecarNFO(oldMedia, newMedia string) error {
|
||||||
|
oldNFO := nfoPath(oldMedia)
|
||||||
|
newNFO := nfoPath(newMedia)
|
||||||
|
if oldNFO == newNFO || !organizeFileExists(oldNFO) || organizeFileExists(newNFO) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(filepath.Dir(newNFO), 0o755); err != nil { // #nosec G301 -- sidecar media directories must remain readable by NAS/player users.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return moveFile(oldNFO, newNFO)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeMediaAndNFO(path string) error {
|
||||||
|
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if nfo := nfoPath(path); nfo != "" {
|
||||||
|
if err := os.Remove(nfo); err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupEmptyMediaDirs(startDir, stopRoot string) {
|
||||||
|
dir := filepath.Clean(strings.TrimSpace(startDir))
|
||||||
|
stopRoot = filepath.Clean(strings.TrimSpace(stopRoot))
|
||||||
|
for dir != "" && dir != "." {
|
||||||
|
if stopRoot != "" && stopRoot != "." && (!pathWithin(dir, stopRoot) || strings.EqualFold(dir, stopRoot)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := os.Remove(dir); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
parent := filepath.Dir(dir)
|
||||||
|
if parent == dir {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dir = parent
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ShukeBta/MediaStationGo/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (o *OrganizerService) updateReclassifiedMediaRow(ctx context.Context, oldPath, newPath string, req organizeExistingReclassifyRequest) error {
|
||||||
|
if o == nil || o.repo == nil || o.repo.DB == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
updates := map[string]any{
|
||||||
|
"path": newPath,
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(req.TargetLibraryID) != "" {
|
||||||
|
updates["library_id"] = strings.TrimSpace(req.TargetLibraryID)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(req.Title) != "" {
|
||||||
|
updates["title"] = strings.TrimSpace(req.Title)
|
||||||
|
}
|
||||||
|
if req.Year > 0 {
|
||||||
|
updates["year"] = req.Year
|
||||||
|
}
|
||||||
|
if normalizeOrganizeMediaType(req.MediaType) == "movie" {
|
||||||
|
updates["season_num"] = 0
|
||||||
|
updates["episode_num"] = 0
|
||||||
|
} else if req.Season > 0 {
|
||||||
|
updates["season_num"] = req.Season
|
||||||
|
if req.Episode > 0 {
|
||||||
|
updates["episode_num"] = req.Episode
|
||||||
|
}
|
||||||
|
} else if req.Episode > 0 {
|
||||||
|
updates["episode_num"] = req.Episode
|
||||||
|
}
|
||||||
|
applyReclassifyMatchUpdates(updates, req.MetadataMatch)
|
||||||
|
return o.repo.DB.WithContext(ctx).Model(&model.Media{}).Where("path = ?", oldPath).Updates(updates).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyReclassifyMatchUpdates(updates map[string]any, match *Match) {
|
||||||
|
if updates == nil || match == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if value := strings.TrimSpace(match.Title); value != "" {
|
||||||
|
updates["title"] = value
|
||||||
|
}
|
||||||
|
if value := strings.TrimSpace(match.OriginalName); value != "" {
|
||||||
|
updates["original_name"] = value
|
||||||
|
}
|
||||||
|
if value := strings.TrimSpace(match.Overview); value != "" {
|
||||||
|
updates["overview"] = value
|
||||||
|
}
|
||||||
|
if value := strings.TrimSpace(match.PosterURL); value != "" {
|
||||||
|
updates["poster_url"] = value
|
||||||
|
}
|
||||||
|
if value := strings.TrimSpace(match.BackdropURL); value != "" {
|
||||||
|
updates["backdrop_url"] = value
|
||||||
|
}
|
||||||
|
if match.Rating > 0 {
|
||||||
|
updates["rating"] = match.Rating
|
||||||
|
}
|
||||||
|
if match.Year > 0 {
|
||||||
|
updates["year"] = match.Year
|
||||||
|
}
|
||||||
|
if match.TMDbID > 0 {
|
||||||
|
updates["tm_db_id"] = match.TMDbID
|
||||||
|
}
|
||||||
|
if match.BangumiID > 0 {
|
||||||
|
updates["bangumi_id"] = match.BangumiID
|
||||||
|
}
|
||||||
|
if value := strings.TrimSpace(match.DoubanID); value != "" {
|
||||||
|
updates["douban_id"] = value
|
||||||
|
}
|
||||||
|
if value := strings.TrimSpace(match.TheTVDBID); value != "" {
|
||||||
|
updates["thetvdb_id"] = value
|
||||||
|
}
|
||||||
|
if len(match.Genres) > 0 {
|
||||||
|
updates["genres"] = strings.Join(match.Genres, ",")
|
||||||
|
}
|
||||||
|
if len(match.Countries) > 0 {
|
||||||
|
updates["countries"] = strings.Join(match.Countries, ",")
|
||||||
|
}
|
||||||
|
if len(match.Languages) > 0 {
|
||||||
|
updates["languages"] = strings.Join(match.Languages, ",")
|
||||||
|
}
|
||||||
|
if match.NSFW {
|
||||||
|
updates["nsfw"] = true
|
||||||
|
}
|
||||||
|
updates["scrape_status"] = "matched"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OrganizerService) deleteMediaRowForPath(ctx context.Context, path string) {
|
||||||
|
if o == nil || o.repo == nil || o.repo.DB == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = o.repo.DB.WithContext(ctx).Where("path = ?", path).Delete(&model.Media{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OrganizerService) mediaPathExists(ctx context.Context, path string) bool {
|
||||||
|
if o == nil || o.repo == nil || o.repo.DB == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var count int64
|
||||||
|
if err := o.repo.DB.WithContext(ctx).Unscoped().Model(&model.Media{}).Where("path = ?", path).Count(&count).Error; err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return count > 0
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user