diff --git a/docs/instructions.md b/docs/instructions.md index 7d106b3..d8f131d 100644 --- a/docs/instructions.md +++ b/docs/instructions.md @@ -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 diff --git a/src/core/entities/series.py b/src/core/entities/series.py index 068dc11..7d18c8b 100644 --- a/src/core/entities/series.py +++ b/src/core/entities/series.py @@ -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 { diff --git a/src/server/api/nfo.py b/src/server/api/nfo.py index 20114c0..90a1d95 100644 --- a/src/server/api/nfo.py +++ b/src/server/api/nfo.py @@ -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: diff --git a/tests/unit/test_serie_folder_with_year.py b/tests/unit/test_serie_folder_with_year.py new file mode 100644 index 0000000..6a69fb8 --- /dev/null +++ b/tests/unit/test_serie_folder_with_year.py @@ -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