mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
fix #4371 递归判断蓝光目录
This commit is contained in:
+16
-14
@@ -866,19 +866,22 @@ class TransferChain(ChainBase, metaclass=Singleton):
|
|||||||
logger.info("所有下载器中下载完成的文件已整理完成")
|
logger.info("所有下载器中下载完成的文件已整理完成")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __get_trans_fileitems(self, fileitem: FileItem) -> List[Tuple[FileItem, bool]]:
|
def __get_trans_fileitems(
|
||||||
|
self, fileitem: FileItem, depth: int = 1
|
||||||
|
) -> List[Tuple[FileItem, bool]]:
|
||||||
"""
|
"""
|
||||||
获取整理目录或文件列表
|
获取整理目录或文件列表
|
||||||
|
|
||||||
:param fileitem: 文件项
|
:param fileitem: 文件项
|
||||||
|
:param depth: 递归深度,默认为1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __is_bluray_dir(_fileitem: FileItem) -> bool:
|
def __contains_bluray_sub(_fileitems: List[FileItem]) -> bool:
|
||||||
"""
|
"""
|
||||||
判断是不是蓝光目录
|
判断是否包含蓝光子目录
|
||||||
"""
|
"""
|
||||||
subs = self.storagechain.list_files(_fileitem)
|
if _fileitems:
|
||||||
if subs:
|
for sub in _fileitems:
|
||||||
for sub in subs:
|
|
||||||
if sub.type == "dir" and sub.name in ["BDMV", "CERTIFICATE"]:
|
if sub.type == "dir" and sub.name in ["BDMV", "CERTIFICATE"]:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -913,25 +916,22 @@ class TransferChain(ChainBase, metaclass=Singleton):
|
|||||||
return [(fileitem, False)]
|
return [(fileitem, False)]
|
||||||
|
|
||||||
# 蓝光原盘根目录
|
# 蓝光原盘根目录
|
||||||
if __is_bluray_dir(fileitem):
|
sub_items = self.storagechain.list_files(fileitem) or []
|
||||||
|
if __contains_bluray_sub(sub_items):
|
||||||
return [(fileitem, True)]
|
return [(fileitem, True)]
|
||||||
|
|
||||||
# 需要整理的文件项列表
|
# 需要整理的文件项列表
|
||||||
trans_items = []
|
trans_items = []
|
||||||
# 先检查当前目录的下级目录,以支持合集的情况
|
# 先检查当前目录的下级目录,以支持合集的情况
|
||||||
for sub_dir in self.storagechain.list_files(fileitem):
|
for sub_dir in sub_items if depth >= 1 else []:
|
||||||
if sub_dir.type == "dir":
|
if sub_dir.type == "dir":
|
||||||
if __is_bluray_dir(sub_dir):
|
trans_items.extend(self.__get_trans_fileitems(sub_dir, depth=depth - 1))
|
||||||
trans_items.append((sub_dir, True))
|
|
||||||
else:
|
|
||||||
trans_items.append((sub_dir, False))
|
|
||||||
|
|
||||||
if not trans_items:
|
if not trans_items:
|
||||||
# 没有有效子目录,直接整理当前目录
|
# 没有有效子目录,直接整理当前目录
|
||||||
trans_items.append((fileitem, False))
|
trans_items.append((fileitem, False))
|
||||||
else:
|
else:
|
||||||
# 有子目录时,把当前目录的文件添加到整理任务中
|
# 有子目录时,把当前目录的文件添加到整理任务中
|
||||||
sub_items = self.storagechain.list_files(fileitem)
|
|
||||||
if sub_items:
|
if sub_items:
|
||||||
trans_items.extend([(f, False) for f in sub_items if f.type == "file"])
|
trans_items.extend([(f, False) for f in sub_items if f.type == "file"])
|
||||||
|
|
||||||
@@ -997,7 +997,9 @@ class TransferChain(ChainBase, metaclass=Singleton):
|
|||||||
# 汇总错误信息
|
# 汇总错误信息
|
||||||
err_msgs: List[str] = []
|
err_msgs: List[str] = []
|
||||||
# 待整理目录或文件项
|
# 待整理目录或文件项
|
||||||
trans_items = self.__get_trans_fileitems(fileitem)
|
trans_items = self.__get_trans_fileitems(
|
||||||
|
fileitem, depth=2 # 为解决 issue#4371 深度至少需要>=2
|
||||||
|
)
|
||||||
# 待整理的文件列表
|
# 待整理的文件列表
|
||||||
file_items: List[Tuple[FileItem, bool]] = []
|
file_items: List[Tuple[FileItem, bool]] = []
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from tests.test_bluray import BluRayTest
|
||||||
from tests.test_metainfo import MetaInfoTest
|
from tests.test_metainfo import MetaInfoTest
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -9,6 +10,9 @@ if __name__ == '__main__':
|
|||||||
suite.addTest(MetaInfoTest('test_metainfo'))
|
suite.addTest(MetaInfoTest('test_metainfo'))
|
||||||
suite.addTest(MetaInfoTest('test_emby_format_ids'))
|
suite.addTest(MetaInfoTest('test_emby_format_ids'))
|
||||||
|
|
||||||
|
# 测试蓝光目录识别
|
||||||
|
suite.addTest(BluRayTest())
|
||||||
|
|
||||||
# 运行测试
|
# 运行测试
|
||||||
runner = unittest.TextTestRunner()
|
runner = unittest.TextTestRunner()
|
||||||
runner.run(suite)
|
runner.run(suite)
|
||||||
|
|||||||
@@ -0,0 +1,336 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from app import schemas
|
||||||
|
from app.chain.storage import StorageChain
|
||||||
|
from app.chain.transfer import TransferChain
|
||||||
|
from app.db.models.transferhistory import TransferHistory
|
||||||
|
from app.db.systemconfig_oper import SystemConfigOper
|
||||||
|
from app.db.transferhistory_oper import TransferHistoryOper
|
||||||
|
|
||||||
|
|
||||||
|
class MockTransferHistoryOper(TransferHistoryOper):
|
||||||
|
def __init__(self):
|
||||||
|
# pylint: disable=super-init-not-called
|
||||||
|
self.history = []
|
||||||
|
|
||||||
|
def get_by_src(self, src, storage=None):
|
||||||
|
self.history.append(src)
|
||||||
|
return TransferHistory()
|
||||||
|
|
||||||
|
|
||||||
|
class MockStorage(StorageChain):
|
||||||
|
def __init__(self, files: list):
|
||||||
|
# pylint: disable=super-init-not-called
|
||||||
|
self.__root = schemas.FileItem(
|
||||||
|
path="/", name="", type="dir", extension="", size=0
|
||||||
|
)
|
||||||
|
self.__all = {self.__root.path: self.__root}
|
||||||
|
|
||||||
|
def __build_child(parent: schemas.FileItem, files: list[dict]):
|
||||||
|
parent.children = []
|
||||||
|
for item in files:
|
||||||
|
children = item.get("children")
|
||||||
|
sep = "" if parent.path.endswith("/") else "/"
|
||||||
|
name: str = item["name"]
|
||||||
|
file_item = schemas.FileItem(
|
||||||
|
path=f"{parent.path}{sep}{name}",
|
||||||
|
name=name,
|
||||||
|
extension=Path(name).suffix[1:],
|
||||||
|
basename=Path(name).stem,
|
||||||
|
type="file" if children is None else "dir",
|
||||||
|
size=item.get("size", 0),
|
||||||
|
)
|
||||||
|
parent.children.append(file_item)
|
||||||
|
self.__all[file_item.path] = file_item
|
||||||
|
if children is not None:
|
||||||
|
__build_child(file_item, children)
|
||||||
|
|
||||||
|
__build_child(self.__root, files)
|
||||||
|
|
||||||
|
def list_files(
|
||||||
|
self, fileitem: schemas.FileItem, recursion: bool = False
|
||||||
|
) -> Optional[List[schemas.FileItem]]:
|
||||||
|
if fileitem.type != "dir":
|
||||||
|
return None
|
||||||
|
if recursion:
|
||||||
|
result = []
|
||||||
|
file_path = f"{fileitem.path}/"
|
||||||
|
for path, item in self.__all.items():
|
||||||
|
if path.startswith(file_path):
|
||||||
|
result.append(item)
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return fileitem.children
|
||||||
|
|
||||||
|
def get_file_item(self, storage: str, path: Path) -> Optional[schemas.FileItem]:
|
||||||
|
"""
|
||||||
|
根据路径获取文件项
|
||||||
|
"""
|
||||||
|
path_posix = path.as_posix()
|
||||||
|
return self.__all.get(path_posix)
|
||||||
|
|
||||||
|
|
||||||
|
class MockTransferChain(TransferChain):
|
||||||
|
def __init__(self, storage: MockStorage):
|
||||||
|
# pylint: disable=super-init-not-called
|
||||||
|
|
||||||
|
self.transferhis = MockTransferHistoryOper()
|
||||||
|
self.systemconfig = SystemConfigOper()
|
||||||
|
self.storagechain = storage
|
||||||
|
|
||||||
|
def test(self, path: str):
|
||||||
|
self.transferhis.history.clear()
|
||||||
|
self.do_transfer(
|
||||||
|
force=False,
|
||||||
|
background=False,
|
||||||
|
fileitem=self.storagechain.get_file_item(None, Path(path)),
|
||||||
|
)
|
||||||
|
return self.transferhis.history
|
||||||
|
|
||||||
|
|
||||||
|
class BluRayTest(TestCase):
|
||||||
|
def __init__(self, methodName="test"):
|
||||||
|
super().__init__(methodName)
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
files = [
|
||||||
|
{
|
||||||
|
"name": "FOLDER",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "Digimon",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "Digimon (2055)",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "BDMV",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "STREAM",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "00000.m2ts",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "00001.m2ts",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CERTIFICATE",
|
||||||
|
"children": [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Digimon (2099)",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "BDMV",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "STREAM",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "00000.m2ts",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "00001.m2ts",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "00002.m2ts.!qB",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CERTIFICATE",
|
||||||
|
"children": [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Digimon (2199)",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "Digimon.2199.mp4",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pokemon (2016)",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "BDMV",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "STREAM",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "00000.m2ts",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "00001.m2ts",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CERTIFICATE",
|
||||||
|
"children": [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pokemon (2021)",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "BDMV",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "STREAM",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "00000.m2ts",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "00001.m2ts",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CERTIFICATE",
|
||||||
|
"children": [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pokemon (2028)",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "Pokemon.2028.mkv",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pokemon.2028.hdr.mkv.!qB",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pokemon.2029.mp4",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pokemon (2030)",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "S",
|
||||||
|
"size": 104857600,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
transfer = MockTransferChain(MockStorage(files))
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
"/FOLDER/Digimon/Digimon (2055)",
|
||||||
|
"/FOLDER/Digimon/Digimon (2099)",
|
||||||
|
"/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4",
|
||||||
|
],
|
||||||
|
transfer.test("/FOLDER/Digimon"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
"/FOLDER/Digimon/Digimon (2055)",
|
||||||
|
],
|
||||||
|
transfer.test("/FOLDER/Digimon/Digimon (2055)"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
"/FOLDER/Digimon/Digimon (2055)",
|
||||||
|
],
|
||||||
|
transfer.test("/FOLDER/Digimon/Digimon (2055)/BDMV"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
"/FOLDER/Digimon/Digimon (2055)",
|
||||||
|
],
|
||||||
|
transfer.test("/FOLDER/Digimon/Digimon (2055)/BDMV/STREAM"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
"/FOLDER/Digimon/Digimon (2055)",
|
||||||
|
],
|
||||||
|
transfer.test("/FOLDER/Digimon/Digimon (2055)/BDMV/STREAM/00001.m2ts"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
"/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4",
|
||||||
|
],
|
||||||
|
transfer.test("/FOLDER/Digimon/Digimon (2199)"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
"/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4",
|
||||||
|
],
|
||||||
|
transfer.test("/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
"/FOLDER/Pokemon.2029.mp4",
|
||||||
|
],
|
||||||
|
transfer.test("/FOLDER/Pokemon.2029.mp4"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
"/FOLDER/Digimon/Digimon (2055)",
|
||||||
|
"/FOLDER/Digimon/Digimon (2099)",
|
||||||
|
"/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4",
|
||||||
|
"/FOLDER/Pokemon (2016)",
|
||||||
|
"/FOLDER/Pokemon (2021)",
|
||||||
|
"/FOLDER/Pokemon (2028)/Pokemon.2028.mkv",
|
||||||
|
"/FOLDER/Pokemon.2029.mp4",
|
||||||
|
],
|
||||||
|
transfer.test("/FOLDER"),
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user