mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
247 lines
7.8 KiB
Go
247 lines
7.8 KiB
Go
package service
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// sanitizeFilename removes characters not safe for filesystem names.
|
|
func sanitizeFilename(s string) string {
|
|
r := strings.NewReplacer(
|
|
"/", " ", "\\", " ", ":", " ", "*", "", "?", "",
|
|
"\"", "", "<", "", ">", "", "|", "",
|
|
)
|
|
return strings.TrimSpace(r.Replace(s))
|
|
}
|
|
|
|
func (o *OrganizerService) organizeRoot(libraryPath, mediaType, category string) string {
|
|
typeDir := o.mediaTypeRootDirForCategory(mediaType, category)
|
|
if typeDir == "" || pathAlreadyEndsWith(libraryPath, typeDir) {
|
|
return libraryPath
|
|
}
|
|
if isGenericMediaRoot(libraryPath) {
|
|
return filepath.Join(libraryPath, typeDir)
|
|
}
|
|
return libraryPath
|
|
}
|
|
|
|
func (o *OrganizerService) mediaTypeRootDirForCategory(mediaType, category string) string {
|
|
if root := o.categoryPhysicalRootDirForType(mediaType, category); root != "" {
|
|
return root
|
|
}
|
|
if root := o.categoryPhysicalRootDir(category); root != "" {
|
|
return root
|
|
}
|
|
return mediaTypeRootDir(mediaType)
|
|
}
|
|
|
|
func (o *OrganizerService) categoryPhysicalRootDirForType(mediaType, category string) string {
|
|
key := normalizeOrganizeCategoryKey(category)
|
|
if key == "" {
|
|
return ""
|
|
}
|
|
categories := o.categoryMap()
|
|
match := func(values ...string) bool {
|
|
for _, value := range values {
|
|
if key == normalizeOrganizeCategoryKey(value) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
switch normalizeMediaType(mediaType, "", "") {
|
|
case "movie":
|
|
if match(
|
|
categoryName(categories, "concert_movie", "演唱会"),
|
|
categoryName(categories, "documentary_movie", "纪录片"),
|
|
categoryName(categories, "animation_movie", "动画电影"),
|
|
categoryName(categories, "chinese_movie", "华语电影"),
|
|
categoryName(categories, "euus_movie", "欧美电影"),
|
|
categoryName(categories, "jk_movie", "日韩电影"),
|
|
"演唱会", "音乐会", "纪录片", "纪录", "动画电影", "动漫电影", "华语电影", "国产电影", "外语电影", "外国电影", "欧美电影", "日韩电影",
|
|
) {
|
|
return "电影"
|
|
}
|
|
case "tv", "variety":
|
|
if match(
|
|
categoryName(categories, "domestic_tv", "国产剧"),
|
|
categoryName(categories, "euus_tv", "欧美剧"),
|
|
categoryName(categories, "jk_tv", "日韩剧"),
|
|
categoryName(categories, "variety", "综艺"),
|
|
categoryName(categories, "documentary", "纪录片"),
|
|
categoryName(categories, "children", "儿童"),
|
|
"国产剧", "欧美剧", "日韩剧", "日剧", "韩剧", "综艺", "真人秀", "纪录片", "纪录", "儿童", "少儿", "未分类",
|
|
) {
|
|
return "电视剧"
|
|
}
|
|
case "anime":
|
|
if match(
|
|
categoryName(categories, "cn_anime", "国漫"),
|
|
categoryName(categories, "jp_anime", "日番"),
|
|
categoryName(categories, "kr_anime", "韩漫"),
|
|
categoryName(categories, "us_anime", "美漫"),
|
|
categoryName(categories, "other_anime", "其他"),
|
|
"国漫", "国产动漫", "日番", "番剧", "日漫", "日本动漫", "日本动画", "韩漫", "韩国动漫", "美漫", "欧美动漫", "欧美动画", "西方动画", "其他", "其他动漫", "其它动漫",
|
|
) {
|
|
return "动漫"
|
|
}
|
|
case "adult":
|
|
if match(categoryName(categories, "adult", "成人"), "成人", "9kg", "番号", "jav") {
|
|
return "成人"
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (o *OrganizerService) categoryPhysicalRootDir(category string) string {
|
|
key := normalizeOrganizeCategoryKey(category)
|
|
if key == "" {
|
|
return ""
|
|
}
|
|
categories := o.categoryMap()
|
|
match := func(values ...string) bool {
|
|
for _, value := range values {
|
|
if key == normalizeOrganizeCategoryKey(value) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
switch {
|
|
case match(
|
|
categoryName(categories, "cn_anime", "国漫"),
|
|
categoryName(categories, "jp_anime", "日番"),
|
|
categoryName(categories, "kr_anime", "韩漫"),
|
|
categoryName(categories, "us_anime", "美漫"),
|
|
categoryName(categories, "other_anime", "其他"),
|
|
"国漫", "国产动漫", "日番", "番剧", "日漫", "日本动漫", "日本动画", "韩漫", "韩国动漫", "美漫", "欧美动漫", "欧美动画", "西方动画", "其他", "其他动漫", "其它动漫",
|
|
):
|
|
return "动漫"
|
|
case match(
|
|
categoryName(categories, "domestic_tv", "国产剧"),
|
|
categoryName(categories, "euus_tv", "欧美剧"),
|
|
categoryName(categories, "jk_tv", "日韩剧"),
|
|
categoryName(categories, "variety", "综艺"),
|
|
categoryName(categories, "documentary", "纪录片"),
|
|
categoryName(categories, "children", "儿童"),
|
|
"国产剧", "欧美剧", "日韩剧", "日剧", "韩剧", "综艺", "真人秀", "纪录片", "纪录", "儿童", "少儿", "未分类",
|
|
):
|
|
return "电视剧"
|
|
case match(
|
|
categoryName(categories, "concert_movie", "演唱会"),
|
|
categoryName(categories, "documentary_movie", "纪录片"),
|
|
categoryName(categories, "animation_movie", "动画电影"),
|
|
categoryName(categories, "chinese_movie", "华语电影"),
|
|
categoryName(categories, "euus_movie", "欧美电影"),
|
|
categoryName(categories, "jk_movie", "日韩电影"),
|
|
"演唱会", "音乐会", "动画电影", "动漫电影", "华语电影", "国产电影", "外语电影", "外国电影", "欧美电影", "日韩电影",
|
|
):
|
|
return "电影"
|
|
case match(categoryName(categories, "adult", "成人"), "成人", "9kg", "番号", "jav"):
|
|
return "成人"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func categoryRoot(root, category string) string {
|
|
if strings.TrimSpace(category) == "" || pathAlreadyEndsWith(root, category) {
|
|
return root
|
|
}
|
|
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":
|
|
return "电影"
|
|
case "anime":
|
|
return "动漫"
|
|
case "tv", "variety":
|
|
return "电视剧"
|
|
case "adult":
|
|
return "成人"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func isGenericMediaRoot(path string) bool {
|
|
base := strings.ToLower(strings.TrimSpace(filepath.Base(filepath.Clean(path))))
|
|
switch base {
|
|
case "media", "medias", "library", "libraries", "organized", "整理":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// organizeStagingFolderNames lists manual-organize style staging folder names.
|
|
// These workspaces should not remain as first-level category folders after an
|
|
// organize operation; redirectOrganizeStagingRoot lifts them to the media root.
|
|
func organizeStagingFolderNames() map[string]struct{} {
|
|
return map[string]struct{}{
|
|
"手动整理": {}, "手动整理入库": {}, "待整理": {}, "待分类": {},
|
|
"manual": {}, "manual_organize": {}, "manualorganize": {}, "staging": {}, "inbox": {},
|
|
}
|
|
}
|
|
|
|
func isOrganizeStagingDir(path string) bool {
|
|
base := strings.ToLower(strings.TrimSpace(filepath.Base(filepath.Clean(path))))
|
|
if base == "" {
|
|
return false
|
|
}
|
|
_, ok := organizeStagingFolderNames()[base]
|
|
return ok
|
|
}
|
|
|
|
func redirectOrganizeStagingRoot(root string) string {
|
|
cleaned := filepath.Clean(strings.TrimSpace(root))
|
|
if cleaned == "" || cleaned == "." {
|
|
return root
|
|
}
|
|
for isOrganizeStagingDir(cleaned) {
|
|
parent := filepath.Dir(cleaned)
|
|
if parent == cleaned || parent == "." || parent == string(filepath.Separator) {
|
|
break
|
|
}
|
|
cleaned = parent
|
|
}
|
|
return cleaned
|
|
}
|
|
|
|
func normalizeOrganizeDestinationRoot(root string) string {
|
|
cleaned := redirectOrganizeStagingRoot(root)
|
|
if collectionRoot := organizeMediaCollectionRoot(cleaned); collectionRoot != "" {
|
|
return collectionRoot
|
|
}
|
|
return cleaned
|
|
}
|
|
|
|
func pathAlreadyEndsWith(path, suffix string) bool {
|
|
base := strings.TrimSpace(filepath.Base(filepath.Clean(path)))
|
|
return strings.EqualFold(base, suffix)
|
|
}
|
|
|
|
func isSeriesLibraryType(mediaType string) bool {
|
|
switch normalizeMediaType(mediaType, "", "") {
|
|
case "tv", "anime", "variety":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|