mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-04-13 14:59:54 +08:00
fix(offset): apply season_offset to folder path and update RSS rules
When user sets season_offset, the save path now reflects the adjusted season: - _gen_save_path() uses (season + season_offset) for folder name - Files saved directly to correct folder (e.g., Season 2 instead of Season 1) - update_rule() now updates qBittorrent RSS rule's savePath when offset changes - Existing torrents are moved to the new location Renamer changes: - gen_path() no longer double-applies season_offset (folder already has it) - Season number taken directly from folder name - Added path normalization for better save_path matching - Added debug logging for offset lookup Torrent name matching (title_raw) remains primary fallback for finding bangumi. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -305,15 +305,51 @@ class BangumiDatabase:
|
||||
def match_by_save_path(self, save_path: str) -> Optional[Bangumi]:
|
||||
"""Find bangumi by save_path to get offset.
|
||||
|
||||
Tries exact match first, then falls back to matching with/without trailing slashes
|
||||
and different path separators.
|
||||
|
||||
Note: When multiple subscriptions share the same save_path (e.g., different RSS
|
||||
sources for the same anime), this returns the first match. Use match_torrent()
|
||||
for more accurate matching when torrent_name is available.
|
||||
"""
|
||||
if not save_path:
|
||||
return None
|
||||
|
||||
# Try exact match first
|
||||
statement = select(Bangumi).where(
|
||||
and_(Bangumi.save_path == save_path, Bangumi.deleted == false())
|
||||
)
|
||||
result = self.session.execute(statement)
|
||||
return result.scalars().first()
|
||||
bangumi = result.scalars().first()
|
||||
if bangumi:
|
||||
return bangumi
|
||||
|
||||
# Normalize the input path and try variations
|
||||
normalized = save_path.replace("\\", "/").rstrip("/")
|
||||
variations = [
|
||||
normalized,
|
||||
normalized + "/",
|
||||
save_path.rstrip("/"),
|
||||
save_path.rstrip("\\"),
|
||||
]
|
||||
# Remove duplicates while preserving order
|
||||
seen = {save_path}
|
||||
unique_variations = []
|
||||
for v in variations:
|
||||
if v not in seen:
|
||||
seen.add(v)
|
||||
unique_variations.append(v)
|
||||
|
||||
for variant in unique_variations:
|
||||
statement = select(Bangumi).where(
|
||||
and_(Bangumi.save_path == variant, Bangumi.deleted == false())
|
||||
)
|
||||
result = self.session.execute(statement)
|
||||
bangumi = result.scalars().first()
|
||||
if bangumi:
|
||||
return bangumi
|
||||
|
||||
return None
|
||||
|
||||
def get_needs_review(self) -> list[Bangumi]:
|
||||
"""Get all bangumi that need review for offset mismatch."""
|
||||
|
||||
@@ -58,10 +58,24 @@ class TorrentPath:
|
||||
|
||||
@staticmethod
|
||||
def _gen_save_path(data: Bangumi | BangumiUpdate):
|
||||
"""Generate save path for a bangumi.
|
||||
|
||||
The save path uses the adjusted season number (season + season_offset)
|
||||
so files are saved directly to the correct season folder.
|
||||
"""
|
||||
folder = (
|
||||
f"{data.official_title} ({data.year})" if data.year else data.official_title
|
||||
)
|
||||
save_path = Path(settings.downloader.path) / folder / f"Season {data.season}"
|
||||
# Apply season_offset to get the adjusted season number for the folder
|
||||
adjusted_season = data.season + getattr(data, "season_offset", 0)
|
||||
if adjusted_season < 1:
|
||||
adjusted_season = data.season # Safety: don't go below 1
|
||||
logger.warning(
|
||||
f"[Path] Season offset would result in invalid season for {data.official_title}, using original season"
|
||||
)
|
||||
save_path = (
|
||||
Path(settings.downloader.path) / folder / f"Season {adjusted_season}"
|
||||
)
|
||||
return str(save_path)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -31,16 +31,13 @@ class Renamer(DownloadClient):
|
||||
bangumi_name: str,
|
||||
method: str,
|
||||
episode_offset: int = 0,
|
||||
season_offset: int = 0,
|
||||
season_offset: int = 0, # Kept for API compatibility, but no longer used
|
||||
) -> str:
|
||||
# Apply season offset
|
||||
adjusted_season = file_info.season + season_offset
|
||||
if adjusted_season < 1:
|
||||
adjusted_season = file_info.season # Safety: don't go below 1
|
||||
logger.warning(
|
||||
f"[Renamer] Season offset {season_offset} would result in invalid season, ignoring"
|
||||
)
|
||||
season = f"0{adjusted_season}" if adjusted_season < 10 else adjusted_season
|
||||
# Season comes from the folder name which already includes the offset
|
||||
# (folder is now "Season {season + season_offset}")
|
||||
# So we use file_info.season directly without applying offset again
|
||||
season_num = file_info.season
|
||||
season = f"0{season_num}" if season_num < 10 else season_num
|
||||
# Apply episode offset
|
||||
adjusted_episode = int(file_info.episode) + episode_offset
|
||||
if adjusted_episode < 1:
|
||||
@@ -96,16 +93,14 @@ class Renamer(DownloadClient):
|
||||
if await self.rename_torrent_file(
|
||||
_hash=_hash, old_path=media_path, new_path=new_path
|
||||
):
|
||||
# Return adjusted season and episode numbers for notification
|
||||
adjusted_season = ep.season + season_offset
|
||||
if adjusted_season < 1:
|
||||
adjusted_season = ep.season
|
||||
# Season comes from folder which already has offset applied
|
||||
# Only apply episode offset
|
||||
adjusted_episode = int(ep.episode) + episode_offset
|
||||
if adjusted_episode < 1:
|
||||
adjusted_episode = int(ep.episode)
|
||||
return Notification(
|
||||
official_title=bangumi_name,
|
||||
season=adjusted_season,
|
||||
season=ep.season,
|
||||
episode=adjusted_episode,
|
||||
)
|
||||
else:
|
||||
@@ -202,6 +197,16 @@ class Renamer(DownloadClient):
|
||||
pass
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _normalize_path(path: str) -> str:
|
||||
"""Normalize path by removing trailing slashes and standardizing separators."""
|
||||
if not path:
|
||||
return path
|
||||
# Replace backslashes with forward slashes for consistency
|
||||
normalized = path.replace("\\", "/")
|
||||
# Remove trailing slashes
|
||||
return normalized.rstrip("/")
|
||||
|
||||
def _lookup_offsets(
|
||||
self, torrent_hash: str, torrent_name: str, save_path: str, tags: str = ""
|
||||
) -> tuple[int, int]:
|
||||
@@ -229,6 +234,9 @@ class Renamer(DownloadClient):
|
||||
if torrent_record and torrent_record.bangumi_id:
|
||||
bangumi = db.bangumi.search_id(torrent_record.bangumi_id)
|
||||
if bangumi and not bangumi.deleted:
|
||||
logger.debug(
|
||||
f"[Renamer] Found offsets via qb_hash: ep={bangumi.episode_offset}, season={bangumi.season_offset}"
|
||||
)
|
||||
return bangumi.episode_offset, bangumi.season_offset
|
||||
|
||||
# Then try by bangumi_id from tags (for newly added torrents)
|
||||
@@ -236,17 +244,35 @@ class Renamer(DownloadClient):
|
||||
if bangumi_id:
|
||||
bangumi = db.bangumi.search_id(bangumi_id)
|
||||
if bangumi and not bangumi.deleted:
|
||||
logger.debug(
|
||||
f"[Renamer] Found offsets via tag ab:{bangumi_id}: ep={bangumi.episode_offset}, season={bangumi.season_offset}"
|
||||
)
|
||||
return bangumi.episode_offset, bangumi.season_offset
|
||||
|
||||
# Then try matching by torrent name
|
||||
bangumi = db.bangumi.match_torrent(torrent_name)
|
||||
if bangumi:
|
||||
logger.debug(
|
||||
f"[Renamer] Found offsets via torrent name match: ep={bangumi.episode_offset}, season={bangumi.season_offset}"
|
||||
)
|
||||
return bangumi.episode_offset, bangumi.season_offset
|
||||
|
||||
# Finally fall back to save_path matching (may fail with multiple subscriptions)
|
||||
# Finally fall back to save_path matching with normalization
|
||||
normalized_save_path = self._normalize_path(save_path)
|
||||
bangumi = db.bangumi.match_by_save_path(save_path)
|
||||
if not bangumi:
|
||||
# Try with normalized path if exact match failed
|
||||
bangumi = db.bangumi.match_by_save_path(normalized_save_path)
|
||||
if bangumi:
|
||||
logger.debug(
|
||||
f"[Renamer] Found offsets via save_path match: ep={bangumi.episode_offset}, season={bangumi.season_offset}"
|
||||
)
|
||||
return bangumi.episode_offset, bangumi.season_offset
|
||||
|
||||
logger.debug(
|
||||
f"[Renamer] No bangumi found for torrent: hash={torrent_hash[:8] if torrent_hash else 'N/A'}, "
|
||||
f"name={torrent_name[:50] if torrent_name else 'N/A'}..., path={save_path}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"[Renamer] Could not lookup offsets for {save_path}: {e}")
|
||||
return 0, 0
|
||||
|
||||
@@ -115,10 +115,46 @@ class TorrentManager(Database):
|
||||
# Move torrent
|
||||
match_list = await self.__match_torrents_list(old_data)
|
||||
async with DownloadClient() as client:
|
||||
path = client._gen_save_path(data)
|
||||
if match_list:
|
||||
await client.move_torrent(match_list, path)
|
||||
data.save_path = path
|
||||
new_path = client._gen_save_path(data)
|
||||
old_path = old_data.save_path
|
||||
|
||||
# Move existing torrents to new location if path changed
|
||||
if match_list and new_path != old_path:
|
||||
await client.move_torrent(match_list, new_path)
|
||||
logger.info(
|
||||
f"[Manager] Moved torrents from {old_path} to {new_path}"
|
||||
)
|
||||
|
||||
# Update qBittorrent RSS rule if save_path changed
|
||||
if new_path != old_path and old_data.rule_name:
|
||||
# Recreate the rule with the new save_path
|
||||
rule = {
|
||||
"enable": True,
|
||||
"mustContain": data.title_raw,
|
||||
"mustNotContain": "|".join(data.filter)
|
||||
if isinstance(data.filter, list)
|
||||
else data.filter,
|
||||
"useRegex": True,
|
||||
"episodeFilter": "",
|
||||
"smartFilter": False,
|
||||
"previouslyMatchedEpisodes": [],
|
||||
"affectedFeeds": data.rss_link
|
||||
if isinstance(data.rss_link, str)
|
||||
else ",".join(data.rss_link),
|
||||
"ignoreDays": 0,
|
||||
"lastMatch": "",
|
||||
"addPaused": False,
|
||||
"assignedCategory": "Bangumi",
|
||||
"savePath": new_path,
|
||||
}
|
||||
await client.client.rss_set_rule(
|
||||
rule_name=old_data.rule_name, rule_def=rule
|
||||
)
|
||||
logger.info(
|
||||
f"[Manager] Updated RSS rule {old_data.rule_name} with new save_path"
|
||||
)
|
||||
|
||||
data.save_path = new_path
|
||||
self.bangumi.update(data, bangumi_id)
|
||||
return ResponseModel(
|
||||
status_code=200,
|
||||
@@ -279,12 +315,16 @@ class TorrentManager(Database):
|
||||
if tmdb_info.series_status == "Ended" and not bangumi.archived:
|
||||
bangumi.archived = True
|
||||
archived_count += 1
|
||||
logger.info(f"[Manager] Auto-archived ended series: {bangumi.official_title}")
|
||||
logger.info(
|
||||
f"[Manager] Auto-archived ended series: {bangumi.official_title}"
|
||||
)
|
||||
|
||||
if archived_count > 0 or poster_count > 0:
|
||||
self.bangumi.update_all(bangumis)
|
||||
|
||||
logger.info(f"[Manager] Metadata refresh: archived {archived_count}, updated posters {poster_count}")
|
||||
logger.info(
|
||||
f"[Manager] Metadata refresh: archived {archived_count}, updated posters {poster_count}"
|
||||
)
|
||||
return ResponseModel(
|
||||
status_code=200,
|
||||
status=True,
|
||||
@@ -296,13 +336,19 @@ class TorrentManager(Database):
|
||||
"""Suggest offset based on TMDB episode counts."""
|
||||
data = self.bangumi.search_id(bangumi_id)
|
||||
if not data:
|
||||
return {"suggested_offset": 0, "reason": f"Bangumi id {bangumi_id} not found"}
|
||||
return {
|
||||
"suggested_offset": 0,
|
||||
"reason": f"Bangumi id {bangumi_id} not found",
|
||||
}
|
||||
|
||||
language = settings.rss_parser.language
|
||||
tmdb_info = await tmdb_parser(data.official_title, language)
|
||||
|
||||
if not tmdb_info or not tmdb_info.season_episode_counts:
|
||||
return {"suggested_offset": 0, "reason": "Unable to fetch TMDB episode data"}
|
||||
return {
|
||||
"suggested_offset": 0,
|
||||
"reason": "Unable to fetch TMDB episode data",
|
||||
}
|
||||
|
||||
season = data.season
|
||||
if season <= 1:
|
||||
|
||||
@@ -1,12 +1,91 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from module.conf import PLATFORM
|
||||
|
||||
|
||||
def test_path_to_bangumi():
|
||||
# Test for unix-like path
|
||||
from module.downloader.path import TorrentPath
|
||||
|
||||
path = "Downloads/Bangumi/Kono Subarashii Sekai ni Shukufuku wo!/Season 2/"
|
||||
bangumi_name, season = TorrentPath()._path_to_bangumi(path)
|
||||
assert bangumi_name == "Kono Subarashii Sekai ni Shukufuku wo!"
|
||||
assert season == 2
|
||||
|
||||
|
||||
class TestGenSavePath:
|
||||
"""Tests for TorrentPath._gen_save_path with season_offset."""
|
||||
|
||||
def test_gen_save_path_no_offset(self):
|
||||
"""Save path uses season directly when no offset."""
|
||||
from module.downloader.path import TorrentPath
|
||||
from module.models import Bangumi
|
||||
|
||||
bangumi = Bangumi(
|
||||
official_title="Test Anime",
|
||||
year="2024",
|
||||
season=1,
|
||||
season_offset=0,
|
||||
title_raw="test",
|
||||
)
|
||||
with patch("module.downloader.path.settings") as mock_settings:
|
||||
mock_settings.downloader.path = "/downloads/Bangumi"
|
||||
result = TorrentPath._gen_save_path(bangumi)
|
||||
|
||||
assert "Season 1" in result
|
||||
assert "Test Anime (2024)" in result
|
||||
|
||||
def test_gen_save_path_with_positive_offset(self):
|
||||
"""Save path uses adjusted season when offset is positive."""
|
||||
from module.downloader.path import TorrentPath
|
||||
from module.models import Bangumi
|
||||
|
||||
bangumi = Bangumi(
|
||||
official_title="Test Anime",
|
||||
year="2024",
|
||||
season=1,
|
||||
season_offset=1,
|
||||
title_raw="test",
|
||||
)
|
||||
with patch("module.downloader.path.settings") as mock_settings:
|
||||
mock_settings.downloader.path = "/downloads/Bangumi"
|
||||
result = TorrentPath._gen_save_path(bangumi)
|
||||
|
||||
assert "Season 2" in result # 1 + 1 = 2
|
||||
assert "Test Anime (2024)" in result
|
||||
|
||||
def test_gen_save_path_with_negative_offset(self):
|
||||
"""Save path uses adjusted season when offset is negative."""
|
||||
from module.downloader.path import TorrentPath
|
||||
from module.models import Bangumi
|
||||
|
||||
bangumi = Bangumi(
|
||||
official_title="Test Anime",
|
||||
year="2024",
|
||||
season=3,
|
||||
season_offset=-1,
|
||||
title_raw="test",
|
||||
)
|
||||
with patch("module.downloader.path.settings") as mock_settings:
|
||||
mock_settings.downloader.path = "/downloads/Bangumi"
|
||||
result = TorrentPath._gen_save_path(bangumi)
|
||||
|
||||
assert "Season 2" in result # 3 - 1 = 2
|
||||
|
||||
def test_gen_save_path_offset_below_one_ignored(self):
|
||||
"""Save path doesn't go below Season 1."""
|
||||
from module.downloader.path import TorrentPath
|
||||
from module.models import Bangumi
|
||||
|
||||
bangumi = Bangumi(
|
||||
official_title="Test Anime",
|
||||
year="2024",
|
||||
season=1,
|
||||
season_offset=-5,
|
||||
title_raw="test",
|
||||
)
|
||||
with patch("module.downloader.path.settings") as mock_settings:
|
||||
mock_settings.downloader.path = "/downloads/Bangumi"
|
||||
result = TorrentPath._gen_save_path(bangumi)
|
||||
|
||||
assert "Season 1" in result # Would be -4, so uses original season
|
||||
|
||||
@@ -364,7 +364,11 @@ class TestRenameSubtitles:
|
||||
|
||||
renamer.client.torrents_rename_file.assert_called_once()
|
||||
call_args = renamer.client.torrents_rename_file.call_args
|
||||
new_path = call_args[1]["new_path"] if "new_path" in (call_args[1] or {}) else call_args[0][2]
|
||||
new_path = (
|
||||
call_args[1]["new_path"]
|
||||
if "new_path" in (call_args[1] or {})
|
||||
else call_args[0][2]
|
||||
)
|
||||
assert ".zh." in new_path
|
||||
|
||||
|
||||
@@ -396,11 +400,13 @@ class TestRenameFlow:
|
||||
async def test_single_file_rename(self, renamer):
|
||||
"""Full rename flow for a single-file torrent."""
|
||||
renamer.client.torrents_info.return_value = [
|
||||
{"hash": "h1", "name": "[Sub] Anime - 01.mkv", "save_path": "/downloads/Bangumi/Anime (2024)/Season 1"}
|
||||
]
|
||||
renamer.client.torrents_files.return_value = [
|
||||
{"name": "[Sub] Anime - 01.mkv"}
|
||||
{
|
||||
"hash": "h1",
|
||||
"name": "[Sub] Anime - 01.mkv",
|
||||
"save_path": "/downloads/Bangumi/Anime (2024)/Season 1",
|
||||
}
|
||||
]
|
||||
renamer.client.torrents_files.return_value = [{"name": "[Sub] Anime - 01.mkv"}]
|
||||
renamer.client.torrents_rename_file.return_value = True
|
||||
|
||||
ep = EpisodeFile(
|
||||
@@ -424,7 +430,11 @@ class TestRenameFlow:
|
||||
async def test_collection_sets_category(self, renamer):
|
||||
"""Multi-file torrent triggers collection rename and set_category."""
|
||||
renamer.client.torrents_info.return_value = [
|
||||
{"hash": "h1", "name": "Anime Collection", "save_path": "/downloads/Bangumi/Anime (2024)/Season 1"}
|
||||
{
|
||||
"hash": "h1",
|
||||
"name": "Anime Collection",
|
||||
"save_path": "/downloads/Bangumi/Anime (2024)/Season 1",
|
||||
}
|
||||
]
|
||||
renamer.client.torrents_files.return_value = [
|
||||
{"name": "ep01.mkv"},
|
||||
@@ -456,7 +466,11 @@ class TestRenameFlow:
|
||||
async def test_no_media_files_no_crash(self, renamer):
|
||||
"""When torrent has no media files, logs warning but doesn't crash."""
|
||||
renamer.client.torrents_info.return_value = [
|
||||
{"hash": "h1", "name": "No Media", "save_path": "/downloads/Bangumi/Anime/Season 1"}
|
||||
{
|
||||
"hash": "h1",
|
||||
"name": "No Media",
|
||||
"save_path": "/downloads/Bangumi/Anime/Season 1",
|
||||
}
|
||||
]
|
||||
renamer.client.torrents_files.return_value = [
|
||||
{"name": "readme.txt"},
|
||||
@@ -564,38 +578,54 @@ class TestGenPathWithOffsets:
|
||||
assert "E05" in result # Would be -5, so offset ignored
|
||||
|
||||
def test_season_offset_positive(self):
|
||||
"""Season offset adds to season number."""
|
||||
"""Season offset is now applied to folder path, not filename.
|
||||
|
||||
The season_offset parameter is kept for API compatibility but no longer
|
||||
affects the filename. The folder path (generated by _gen_save_path)
|
||||
already includes the offset, so the season from the folder is used directly.
|
||||
"""
|
||||
# Simulate file in Season 2 folder (offset already applied to folder)
|
||||
ep = EpisodeFile(
|
||||
media_path="old.mkv", title="My Anime", season=1, episode=5, suffix=".mkv"
|
||||
media_path="old.mkv", title="My Anime", season=2, episode=5, suffix=".mkv"
|
||||
)
|
||||
result = Renamer.gen_path(ep, "Bangumi", method="pn", season_offset=1)
|
||||
assert "S02" in result # 1 + 1 = 2
|
||||
assert (
|
||||
"S02" in result
|
||||
) # Season from folder used directly, offset not re-applied
|
||||
|
||||
def test_season_offset_negative(self):
|
||||
"""Negative season offset subtracts from season number."""
|
||||
"""Season offset is now applied to folder path, not filename."""
|
||||
# Simulate file in Season 2 folder (offset already applied to folder)
|
||||
ep = EpisodeFile(
|
||||
media_path="old.mkv", title="My Anime", season=3, episode=5, suffix=".mkv"
|
||||
media_path="old.mkv", title="My Anime", season=2, episode=5, suffix=".mkv"
|
||||
)
|
||||
result = Renamer.gen_path(ep, "Bangumi", method="pn", season_offset=-1)
|
||||
assert "S02" in result # 3 - 1 = 2
|
||||
assert (
|
||||
"S02" in result
|
||||
) # Season from folder used directly, offset not re-applied
|
||||
|
||||
def test_season_offset_negative_below_one_ignored(self):
|
||||
"""Negative season offset that would go below 1 is ignored."""
|
||||
"""Season offset parameter no longer affects filename."""
|
||||
ep = EpisodeFile(
|
||||
media_path="old.mkv", title="My Anime", season=1, episode=5, suffix=".mkv"
|
||||
)
|
||||
result = Renamer.gen_path(ep, "Bangumi", method="pn", season_offset=-5)
|
||||
assert "S01" in result # Would be -4, so offset ignored
|
||||
assert "S01" in result # Season from folder used directly
|
||||
|
||||
def test_both_offsets_combined(self):
|
||||
"""Both episode and season offset applied together."""
|
||||
"""Episode offset applied to filename, season offset applied to folder path.
|
||||
|
||||
The folder path already includes season_offset (Season 2 in this case).
|
||||
Only episode_offset is applied during filename generation.
|
||||
"""
|
||||
# Simulate file in Season 2 folder (season_offset=1 applied to folder: 1+1=2)
|
||||
ep = EpisodeFile(
|
||||
media_path="old.mkv", title="My Anime", season=1, episode=13, suffix=".mkv"
|
||||
media_path="old.mkv", title="My Anime", season=2, episode=13, suffix=".mkv"
|
||||
)
|
||||
result = Renamer.gen_path(
|
||||
ep, "Bangumi", method="pn", episode_offset=-12, season_offset=1
|
||||
)
|
||||
assert "S02E01" in result # Season 1+1=2, Episode 13-12=1
|
||||
assert "S02E01" in result # Season 2 from folder, Episode 13-12=1
|
||||
|
||||
def test_offset_with_advance_method(self):
|
||||
"""Offset works with advance rename method."""
|
||||
@@ -892,3 +922,108 @@ class TestLookupOffsets:
|
||||
|
||||
assert episode_offset == 0
|
||||
assert season_offset == 0
|
||||
|
||||
def test_lookup_by_save_path_with_trailing_slash(self, renamer, db_session):
|
||||
"""Save path matching works with trailing slashes."""
|
||||
from module.database.bangumi import BangumiDatabase
|
||||
from module.database.torrent import TorrentDatabase
|
||||
from module.models import Bangumi
|
||||
|
||||
# Create bangumi with save_path WITHOUT trailing slash
|
||||
bangumi_db = BangumiDatabase(db_session)
|
||||
bangumi = Bangumi(
|
||||
official_title="Trailing Slash Test",
|
||||
year="2024",
|
||||
title_raw="unique_raw_trailing",
|
||||
season=1,
|
||||
save_path="/downloads/Bangumi/Test (2024)/Season 1",
|
||||
episode_offset=5,
|
||||
season_offset=2,
|
||||
)
|
||||
bangumi_db.add(bangumi)
|
||||
|
||||
with patch("module.manager.renamer.Database") as MockDatabase:
|
||||
mock_db = MagicMock()
|
||||
mock_db.__enter__ = MagicMock(return_value=mock_db)
|
||||
mock_db.__exit__ = MagicMock(return_value=False)
|
||||
mock_db.torrent = TorrentDatabase(db_session)
|
||||
mock_db.bangumi = BangumiDatabase(db_session)
|
||||
MockDatabase.return_value = mock_db
|
||||
|
||||
# Query WITH trailing slash - should still match
|
||||
episode_offset, season_offset = renamer._lookup_offsets(
|
||||
torrent_hash="nonexistent",
|
||||
torrent_name="no_match",
|
||||
save_path="/downloads/Bangumi/Test (2024)/Season 1/",
|
||||
tags="",
|
||||
)
|
||||
|
||||
assert episode_offset == 5
|
||||
assert season_offset == 2
|
||||
|
||||
def test_lookup_by_save_path_with_backslashes(self, renamer, db_session):
|
||||
"""Save path matching works with Windows-style backslashes."""
|
||||
from module.database.bangumi import BangumiDatabase
|
||||
from module.database.torrent import TorrentDatabase
|
||||
from module.models import Bangumi
|
||||
|
||||
# Create bangumi with forward slashes
|
||||
bangumi_db = BangumiDatabase(db_session)
|
||||
bangumi = Bangumi(
|
||||
official_title="Backslash Test",
|
||||
year="2024",
|
||||
title_raw="unique_raw_backslash",
|
||||
season=1,
|
||||
save_path="/downloads/Bangumi/Test (2024)/Season 1",
|
||||
episode_offset=3,
|
||||
season_offset=1,
|
||||
)
|
||||
bangumi_db.add(bangumi)
|
||||
|
||||
with patch("module.manager.renamer.Database") as MockDatabase:
|
||||
mock_db = MagicMock()
|
||||
mock_db.__enter__ = MagicMock(return_value=mock_db)
|
||||
mock_db.__exit__ = MagicMock(return_value=False)
|
||||
mock_db.torrent = TorrentDatabase(db_session)
|
||||
mock_db.bangumi = BangumiDatabase(db_session)
|
||||
MockDatabase.return_value = mock_db
|
||||
|
||||
# Query with backslashes - should still match after normalization
|
||||
episode_offset, season_offset = renamer._lookup_offsets(
|
||||
torrent_hash="nonexistent",
|
||||
torrent_name="no_match",
|
||||
save_path="\\downloads\\Bangumi\\Test (2024)\\Season 1",
|
||||
tags="",
|
||||
)
|
||||
|
||||
assert episode_offset == 3
|
||||
assert season_offset == 1
|
||||
|
||||
|
||||
class TestNormalizePath:
|
||||
"""Tests for Renamer._normalize_path static method."""
|
||||
|
||||
def test_empty_path(self):
|
||||
from module.manager.renamer import Renamer
|
||||
|
||||
assert Renamer._normalize_path("") == ""
|
||||
|
||||
def test_removes_trailing_slash(self):
|
||||
from module.manager.renamer import Renamer
|
||||
|
||||
assert Renamer._normalize_path("/path/to/dir/") == "/path/to/dir"
|
||||
|
||||
def test_removes_trailing_backslash(self):
|
||||
from module.manager.renamer import Renamer
|
||||
|
||||
assert Renamer._normalize_path("C:\\path\\to\\dir\\") == "C:/path/to/dir"
|
||||
|
||||
def test_converts_backslashes(self):
|
||||
from module.manager.renamer import Renamer
|
||||
|
||||
assert Renamer._normalize_path("C:\\path\\to\\dir") == "C:/path/to/dir"
|
||||
|
||||
def test_preserves_forward_slashes(self):
|
||||
from module.manager.renamer import Renamer
|
||||
|
||||
assert Renamer._normalize_path("/path/to/dir") == "/path/to/dir"
|
||||
|
||||
Reference in New Issue
Block a user