fix: tolerate delayed OpenList metadata

This commit is contained in:
jxxghp
2026-05-20 15:53:56 +08:00
parent 5fe5523d13
commit 72ead2970c
4 changed files with 116 additions and 8 deletions
+12 -1
View File
@@ -331,7 +331,18 @@ class Alist(StorageBase, metaclass=WeakSingleton):
)
return None
return self._delay_get_item(path, refresh=True)
return self._delay_get_item(
path, refresh=True
) or self.__build_transfer_item(
schemas.FileItem(
storage=self.schema.value,
type="dir",
path=fileitem.path,
name=name,
basename=Path(name).stem,
),
path,
)
def get_folder(self, path: Path) -> Optional[schemas.FileItem]:
"""
+32 -7
View File
@@ -546,6 +546,7 @@ class TransHandler:
result=result,
)
if not new_item:
err_msg = err_msg or f"{fileitem.path} 整理后未获取到目标文件信息"
logger.error(f"文件 {fileitem.path} 整理失败:{err_msg}")
self.__update_result(
result=result,
@@ -608,6 +609,34 @@ class TransHandler:
modify_time=_path.stat().st_mtime,
)
def __build_remote_targetitem(_source_item: FileItem, _path: Path) -> FileItem:
"""
根据已确认的目标路径构造网盘文件信息,用于兼容元数据延迟可见的存储。
"""
return FileItem(
storage=target_storage,
path=_path.as_posix(),
name=_path.name,
basename=_path.stem,
type=_source_item.type or "file",
size=_source_item.size,
extension=_path.suffix.lstrip("."),
modify_time=_source_item.modify_time,
thumbnail=_source_item.thumbnail,
)
def __get_remote_targetitem(_source_item: FileItem, _path: Path) -> FileItem:
"""
获取网盘目标文件信息,目标存储索引未刷新时使用目标路径兜底。
"""
target_item = target_oper.get_item(_path)
if target_item:
return target_item
logger.warn(
f"目标文件【{target_storage}{_path} 元数据暂不可见,使用目标路径构造整理结果"
)
return __build_remote_targetitem(_source_item, _path)
if (
fileitem.storage != target_storage
and fileitem.storage != "local"
@@ -719,9 +748,7 @@ class TransHandler:
elif source_oper.copy(
fileitem, Path(target_fileitem.path), target_file.name
):
new_item = target_oper.get_item(target_file)
if new_item:
return new_item, ""
return __get_remote_targetitem(fileitem, target_file), ""
return None, f"{target_storage}{fileitem.path} 复制文件失败"
else:
return (
@@ -742,9 +769,7 @@ class TransHandler:
elif source_oper.move(
fileitem, Path(target_fileitem.path), target_file.name
):
new_item = target_oper.get_item(target_file)
if new_item:
return new_item, ""
return __get_remote_targetitem(fileitem, target_file), ""
return None, f"{target_storage}{fileitem.path} 移动文件失败"
else:
return (
@@ -753,7 +778,7 @@ class TransHandler:
)
elif transfer_type == "link":
if source_oper.link(fileitem, target_file):
return target_oper.get_item(target_file), ""
return __get_remote_targetitem(fileitem, target_file), ""
else:
return None, f"{target_storage}{fileitem.path} 创建硬链接失败"
else:
+23
View File
@@ -200,6 +200,29 @@ class AlistStorageTest(unittest.TestCase):
self.assertEqual(50, len(items))
self.assertEqual(1, request_utils.post_res.call_count)
def test_create_folder_returns_target_when_openlist_metadata_is_delayed(self):
"""
OpenList 创建目录成功但元数据延迟可见时,应返回可用的目标目录项。
"""
request_utils = MagicMock()
request_utils.post_res.return_value = _FakeResponse(
{"code": 200, "message": "success", "data": None}
)
with patch.object(Alist, "get_conf", return_value={"url": "http://openlist.test", "token": "token"}):
with patch.object(self.storage, "_Alist__get_header_with_token", return_value={}):
with patch.object(alist_module, "RequestUtils", return_value=request_utils):
with patch.object(self.storage, "_delay_get_item", return_value=None):
folder = self.storage.create_folder(
self._dir_item("/library/Test Show (2026)"),
"Season 1",
)
self.assertIsNotNone(folder)
self.assertEqual("/library/Test Show (2026)/Season 1", folder.path)
self.assertEqual("alist", folder.storage)
self.assertEqual("dir", folder.type)
def test_move_item_returns_target_when_openlist_metadata_is_delayed(self):
"""
OpenList 操作成功但目标元数据延迟可见时,应返回可用的目标文件项。
+49
View File
@@ -5,6 +5,7 @@ from unittest.mock import patch, MagicMock
from app.core.config import settings
from app.chain.transfer import JobManager, TransferChain
from app.modules.filemanager.transhandler import TransHandler
from app.schemas import EpisodeFormat, FileItem, TransferInfo, TransferTask
from app.schemas.types import EventType, MediaType
@@ -127,6 +128,54 @@ def migrate_to_media_job(jobview: JobManager, task: TransferTask):
class TransferJobManagerTest(unittest.TestCase):
def test_same_storage_success_uses_target_path_when_metadata_is_delayed(self):
"""
网盘操作已成功但目标元数据暂不可见时,整理结果应按成功路径落库。
"""
source_item = FileItem(
storage="alist",
path="/downloads/Test.Show.S01E01.mkv",
type="file",
name="Test.Show.S01E01.mkv",
basename="Test.Show.S01E01",
extension="mkv",
size=1024,
modify_time=1715939275.0,
)
target_path = Path(
"/library/Test Show (2026)/Season 1/Test.Show.S01E01.mkv"
)
target_folder = FileItem(
storage="alist",
path=target_path.parent.as_posix(),
type="dir",
name=target_path.parent.name,
)
source_oper = SimpleNamespace(
is_support_transtype=lambda transfer_type: True,
move=lambda fileitem, path, name: True,
)
target_oper = SimpleNamespace(
get_folder=lambda path: target_folder,
get_item=lambda path: None,
)
new_item, errmsg = TransHandler._TransHandler__transfer_command(
fileitem=source_item,
target_storage="alist",
source_oper=source_oper,
target_oper=target_oper,
target_file=target_path,
transfer_type="move",
)
self.assertEqual("", errmsg)
self.assertIsNotNone(new_item)
self.assertEqual(target_path.as_posix(), new_item.path)
self.assertEqual("alist", new_item.storage)
self.assertEqual("file", new_item.type)
self.assertEqual(1024, new_item.size)
def test_manual_episode_offset_applies_once(self):
chain = make_transfer_chain()
source_fileitem = make_fileitem("/downloads/Test.Show.2026.S01E14.mkv")