Fix NFO folder naming to include year

- Add Serie.ensure_folder_with_year() method to ensure folder names include year
- Update all NFO API endpoints to call ensure_folder_with_year() before operations
- Folder format is now 'Name (Year)' when year is available
- Add comprehensive tests for ensure_folder_with_year() method
- All 5 tests passing
This commit is contained in:
2026-01-18 12:28:38 +01:00
parent 03901a8c2d
commit 491daa2e50
4 changed files with 148 additions and 8 deletions

View File

@@ -130,15 +130,18 @@ All tasks completed! Recent issues have been resolved.
**Root Cause:** The `AniWorld.ApiClient.request()` function returns a native `Response` object from the Fetch API, not the parsed JSON data. The code was attempting to access properties like `response.message` and `response.content` directly without first calling `response.json()` to parse the response body.
**Solution:** Updated all NFO-related API calls in the JavaScript modules to:
1. Check if response exists (`if (!response)`)
2. Parse the JSON response (`const data = await response.json()`)
3. Access properties on the parsed data object (`data.message`, `data.content`, etc.)
**Files Modified:**
- [src/server/web/static/js/index/nfo-manager.js](../src/server/web/static/js/index/nfo-manager.js) - Fixed `createNFO()`, `refreshNFO()`, `viewNFO()`, `getSeriesWithoutNFO()`
- [src/server/web/static/js/index/nfo-config.js](../src/server/web/static/js/index/nfo-config.js) - Fixed `load()`, `testTMDBConnection()`
**Verification:**
- NFO creation now works correctly from the web UI
- Error messages are properly displayed with details from the API
- All NFO operations (create, refresh, view) function as expected

View File

@@ -1,4 +1,5 @@
import json
import logging
import os
import warnings
from pathlib import Path
@@ -6,6 +7,8 @@ from typing import Optional
from src.server.utils.filesystem import sanitize_folder_name
logger = logging.getLogger(__name__)
class Serie:
"""
@@ -290,6 +293,36 @@ class Serie:
# Fallback to key if name cannot be sanitized
return sanitize_folder_name(self._key)
def ensure_folder_with_year(self) -> str:
"""Ensure folder name includes year if available.
If the serie has a year and the current folder name doesn't include it,
updates the folder name to include the year in format "Name (Year)".
This method should be called before creating folders or NFO files to
ensure consistent naming across the application.
Returns:
str: The folder name (updated if needed)
Example:
>>> serie = Serie("perfect-blue", "Perfect Blue", ..., folder="Perfect Blue", year=1997)
>>> serie.ensure_folder_with_year()
'Perfect Blue (1997)'
>>> serie.folder # folder property is updated
'Perfect Blue (1997)'
"""
if self._year:
# Check if folder already has year format
year_pattern = f"({self._year})"
if year_pattern not in self._folder:
# Update folder to include year
self._folder = self.sanitized_folder
logger.info(
f"Updated folder name for '{self._key}' to include year: {self._folder}"
)
return self._folder
def to_dict(self):
"""Convert Serie object to dictionary for JSON serialization."""
return {

View File

@@ -133,7 +133,8 @@ async def check_nfo(
detail=f"Series not found: {serie_id}"
)
serie_folder = serie.folder
# Ensure folder name includes year if available
serie_folder = serie.ensure_folder_with_year()
# Check NFO
has_nfo = await nfo_service.check_nfo_exists(serie_folder)
@@ -201,7 +202,11 @@ async def create_nfo(
detail=f"Series not found: {serie_id}"
)
serie_folder = serie.folder
# Ensure folder name includes year if available
serie_folder = serie.ensure_folder_with_year()
# If year not provided in request but serie has year, use it
year = request.year or serie.year
# Check if NFO already exists
if not request.overwrite_existing:
@@ -217,7 +222,7 @@ async def create_nfo(
nfo_path = await nfo_service.create_tvshow_nfo(
serie_name=serie_name,
serie_folder=serie_folder,
year=request.year,
year=year,
download_poster=request.download_poster,
download_logo=request.download_logo,
download_fanart=request.download_fanart
@@ -290,7 +295,8 @@ async def update_nfo(
detail=f"Series not found: {serie_id}"
)
serie_folder = serie.folder
# Ensure folder name includes year if available
serie_folder = serie.ensure_folder_with_year()
# Check if NFO exists
has_nfo = await nfo_service.check_nfo_exists(serie_folder)
@@ -371,7 +377,8 @@ async def get_nfo_content(
detail=f"Series not found: {serie_id}"
)
serie_folder = serie.folder
# Ensure folder name includes year if available
serie_folder = serie.ensure_folder_with_year()
# Check if NFO exists
nfo_path = (
@@ -494,7 +501,8 @@ async def download_media(
detail=f"Series not found: {serie_id}"
)
serie_folder = serie.folder
# Ensure folder name includes year if available
serie_folder = serie.ensure_folder_with_year()
# Check if NFO exists (needed for TMDB ID)
has_nfo = await nfo_service.check_nfo_exists(serie_folder)
@@ -575,7 +583,8 @@ async def batch_create_nfo(
message="Series not found"
)
serie_folder = serie.folder
# Ensure folder name includes year if available
serie_folder = serie.ensure_folder_with_year()
# Check if NFO exists
if request.skip_existing:
@@ -664,7 +673,8 @@ async def get_missing_nfo(
if not serie_id:
continue
serie_folder = serie.folder
# Ensure folder name includes year if available
serie_folder = serie.ensure_folder_with_year()
has_nfo = await nfo_service.check_nfo_exists(serie_folder)
if not has_nfo:

View File

@@ -0,0 +1,94 @@
"""Tests for Serie.ensure_folder_with_year() method."""
import pytest
from src.core.entities.series import Serie
class TestSerieEnsureFolderWithYear:
"""Test suite for ensure_folder_with_year method."""
def test_ensure_folder_with_year_adds_year(self):
"""Test that ensure_folder_with_year adds year to folder name."""
serie = Serie(
key="perfect-blue",
name="Perfect Blue",
site="aniworld.to",
folder="Perfect Blue",
episodeDict={1: [1, 2, 3]},
year=1997
)
result = serie.ensure_folder_with_year()
assert result == "Perfect Blue (1997)"
assert serie.folder == "Perfect Blue (1997)"
def test_ensure_folder_with_year_already_has_year(self):
"""Test that ensure_folder_with_year doesn't duplicate year."""
serie = Serie(
key="blue-exorcist",
name="Blue Exorcist",
site="aniworld.to",
folder="Blue Exorcist (2011)",
episodeDict={1: [1, 2, 3]},
year=2011
)
result = serie.ensure_folder_with_year()
assert result == "Blue Exorcist (2011)"
assert serie.folder == "Blue Exorcist (2011)"
def test_ensure_folder_with_year_no_year_available(self):
"""Test that ensure_folder_with_year returns folder unchanged if no year."""
serie = Serie(
key="unknown-anime",
name="Unknown Anime",
site="aniworld.to",
folder="Unknown Anime",
episodeDict={1: [1, 2, 3]},
year=None
)
result = serie.ensure_folder_with_year()
assert result == "Unknown Anime"
assert serie.folder == "Unknown Anime"
def test_ensure_folder_with_year_sanitizes_name(self):
"""Test that ensure_folder_with_year uses sanitized_folder property."""
serie = Serie(
key="attack-on-titan",
name="Attack on Titan: Final Season",
site="aniworld.to",
folder="Attack on Titan Final", # Old folder without year
episodeDict={1: [1, 2, 3]},
year=2020
)
result = serie.ensure_folder_with_year()
# Should use sanitized version of name_with_year
assert "(2020)" in result
assert serie.folder == result
# Colon should be removed by sanitization
assert ":" not in result
def test_ensure_folder_with_year_updates_folder_property(self):
"""Test that folder property is updated when year is added."""
serie = Serie(
key="dororo",
name="Dororo",
site="aniworld.to",
folder="Dororo",
episodeDict={1: [1, 2, 3]},
year=2019
)
original_folder = serie.folder
result = serie.ensure_folder_with_year()
assert original_folder == "Dororo"
assert result == "Dororo (2019)"
assert serie.folder == "Dororo (2019)"
assert serie.folder != original_folder