From c6b6b0ef64b1b23568ad83a8fa991c574d6d31a3 Mon Sep 17 00:00:00 2001 From: SBCrumb Date: Sun, 26 Oct 2025 15:23:35 -0400 Subject: [PATCH] web: table update and nfo scan update --- api/routes.py | 61 ++++++++++++-------- nfoguard-web/static/css/styles.css | 91 ++++++++++++++++++++++++++++++ nfoguard-web/static/index.html | 4 +- nfoguard-web/static/js/app.js | 54 ++++++++++++------ 4 files changed, 168 insertions(+), 42 deletions(-) diff --git a/api/routes.py b/api/routes.py index e5c25c9..c5435c7 100644 --- a/api/routes.py +++ b/api/routes.py @@ -2391,7 +2391,7 @@ def register_routes(app, dependencies: dict): imdb_id = "unknown" try: - # Parse the NFO file we already know exists (since grep found it missing dateadded) + # Try to parse the NFO file we already know exists (since grep found it missing dateadded) tree = ET.parse(nfo_file) root = tree.getroot() @@ -2418,26 +2418,43 @@ def register_routes(app, dependencies: dict): imdb_text = content_match.group(1) print(f"✅ Found IMDb {imdb_text} in NFO content: {os.path.basename(nfo_file)}") return imdb_text - - # Method 4: Fallback - check folder structure for episodes - if is_episode: - series_folder = os.path.dirname(os.path.dirname(nfo_file)) - folder_name = os.path.basename(series_folder) - folder_match = re.search(r'\[imdb-(tt\d+)\]', folder_name, re.IGNORECASE) - if folder_match: - print(f"✅ Found IMDb {folder_match.group(1)} in folder: {folder_name}") - return folder_match.group(1) - else: - # For movies, check movie folder - movie_folder = os.path.dirname(nfo_file) - folder_name = os.path.basename(movie_folder) - folder_match = re.search(r'\[imdb-(tt\d+)\]', folder_name, re.IGNORECASE) - if folder_match: - print(f"✅ Found IMDb {folder_match.group(1)} in folder: {folder_name}") - return folder_match.group(1) + except ET.ParseError as e: + # Handle malformed XML by trying text-based search + print(f"⚠️ NFO XML parse error in {os.path.basename(nfo_file)}: {e}") + try: + # Fallback: Read as text and search for IMDb patterns + with open(nfo_file, 'r', encoding='utf-8', errors='ignore') as f: + nfo_text = f.read() + + # Search for IMDb patterns in raw text + text_match = re.search(r'(tt\d{7,})', nfo_text) + if text_match: + imdb_text = text_match.group(1) + print(f"✅ Found IMDb {imdb_text} in NFO text content: {os.path.basename(nfo_file)}") + return imdb_text + except Exception as text_error: + print(f"⚠️ Error reading NFO as text {nfo_file}: {text_error}") + except Exception as e: - print(f"⚠️ Error parsing NFO for IMDb in {nfo_file}: {e}") + print(f"⚠️ Unexpected error parsing NFO {nfo_file}: {e}") + + # Method 4: Fallback - check folder structure + if is_episode: + series_folder = os.path.dirname(os.path.dirname(nfo_file)) + folder_name = os.path.basename(series_folder) + folder_match = re.search(r'\[imdb-(tt\d+)\]', folder_name, re.IGNORECASE) + if folder_match: + print(f"✅ Found IMDb {folder_match.group(1)} in folder: {folder_name}") + return folder_match.group(1) + else: + # For movies, check movie folder + movie_folder = os.path.dirname(nfo_file) + folder_name = os.path.basename(movie_folder) + folder_match = re.search(r'\[imdb-(tt\d+)\]', folder_name, re.IGNORECASE) + if folder_match: + print(f"✅ Found IMDb {folder_match.group(1)} in folder: {folder_name}") + return folder_match.group(1) print(f"❌ No IMDb ID found in {os.path.basename(nfo_file)}") return "unknown" @@ -2568,7 +2585,7 @@ def register_routes(app, dependencies: dict): with db.get_connection() as conn: cursor = conn.cursor() cursor.execute(""" - SELECT dateadded, source, added_by + SELECT dateadded, source FROM episodes WHERE imdb_id = %s AND season = %s AND episode = %s LIMIT 1 @@ -2578,7 +2595,6 @@ def register_routes(app, dependencies: dict): if result: episode["dateadded"] = result["dateadded"] episode["source"] = result.get("source", "database") - episode["added_by"] = result.get("added_by", "unknown") else: print(f"⚠️ No database entry for {episode['imdb_id']} S{episode['season']:02d}E{episode['episode']:02d}") except Exception as e: @@ -2591,7 +2607,7 @@ def register_routes(app, dependencies: dict): with db.get_connection() as conn: cursor = conn.cursor() cursor.execute(""" - SELECT dateadded, source, added_by + SELECT dateadded, source FROM movies WHERE imdb_id = %s LIMIT 1 @@ -2601,7 +2617,6 @@ def register_routes(app, dependencies: dict): if result: movie["dateadded"] = result["dateadded"] movie["source"] = result.get("source", "database") - movie["added_by"] = result.get("added_by", "unknown") else: print(f"⚠️ No database entry for movie {movie['imdb_id']}") except Exception as e: diff --git a/nfoguard-web/static/css/styles.css b/nfoguard-web/static/css/styles.css index 0c06736..42e54f1 100644 --- a/nfoguard-web/static/css/styles.css +++ b/nfoguard-web/static/css/styles.css @@ -958,6 +958,97 @@ body { border-radius: 0.375rem; } +/* NFO Repair Table Styling */ +.nfo-repair-table-container { + background: white; + border-radius: 0.5rem; + box-shadow: var(--shadow); + overflow-x: auto; + overflow-y: auto; + max-height: 600px; + margin-bottom: 1rem; + border: 1px solid var(--border-color); +} + +.nfo-repair-table { + width: 100%; + min-width: 800px; + border-collapse: collapse; + font-size: 0.875rem; + background: white; +} + +.nfo-repair-table th { + background-color: var(--dark-color); + color: white; + padding: 0.75rem 0.5rem; + text-align: left; + font-weight: 600; + position: sticky; + top: 0; + z-index: 10; +} + +.nfo-repair-table td { + padding: 0.75rem 0.5rem; + border-bottom: 1px solid var(--border-color); + vertical-align: top; +} + +.nfo-repair-table tr:hover { + background-color: #f8f9fa; +} + +.media-type-badge { + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; +} + +.media-type-badge.episode { + background-color: #e3f2fd; + color: #1976d2; +} + +.media-type-badge.movie { + background-color: #f3e5f5; + color: #7b1fa2; +} + +.status-ready { + color: #2e7d32; + font-weight: 600; + font-size: 0.75rem; +} + +.status-missing { + color: #d32f2f; + font-weight: 600; + font-size: 0.75rem; +} + +.text-center { + text-align: center; +} + +.text-muted { + color: #6c757d; +} + +.nfo-fix-actions { + margin-top: 1rem; + padding: 1rem; + background-color: #f8f9fa; + border-radius: 0.5rem; + text-align: center; +} + +.nfo-fix-actions .btn { + margin-bottom: 0.5rem; +} + .admin-tools { display: flex; flex-direction: column; diff --git a/nfoguard-web/static/index.html b/nfoguard-web/static/index.html index d4860b9..fbbbcd6 100644 --- a/nfoguard-web/static/index.html +++ b/nfoguard-web/static/index.html @@ -4,7 +4,7 @@ NFOGuard - Database Management - + @@ -533,6 +533,6 @@
- + \ No newline at end of file diff --git a/nfoguard-web/static/js/app.js b/nfoguard-web/static/js/app.js index 1eb8e5a..7daf249 100644 --- a/nfoguard-web/static/js/app.js +++ b/nfoguard-web/static/js/app.js @@ -1847,17 +1847,18 @@ async function checkMissingNFODates() { html += `
Items with missing dateadded (showing first 50):
-
- +
+
- - - - - - - + + + + + + + + @@ -1865,20 +1866,39 @@ async function checkMissingNFODates() { (response.items || response.episodes || []).forEach(item => { const displayDate = item.dateadded ? new Date(item.dateadded).toLocaleString() : 'None'; + const seriesName = item.series_name || item.title || 'Unknown'; + const nfoFileName = item.nfo_path ? item.nfo_path.split('/').pop() : 'Unknown'; + html += ` - - - - - - - + + + + + + + + `; }); - html += '
TypeIMDb IDSeasonEpisodeDatabase DateSourceNFO PathTypeIMDb IDSeasonEpisodeDatabase DateSourceSeries/MovieActions
${item.media_type || 'episode'}${item.imdb_id}${item.season || 'N/A'}${item.episode || 'N/A'}${displayDate}${item.source || 'unknown'}${item.nfo_path}${item.media_type || 'episode'}${item.imdb_id}${item.season || 'N/A'}${item.episode || 'N/A'}${displayDate}${item.source || 'unknown'}${seriesName}
${nfoFileName}
+ ${item.dateadded ? + '✅ Ready to fix' : + '❌ No DB date' + } +
'; + html += ` + + + +
+ +

This will automatically add dateadded elements to NFO files using database values.

+
+ `; } else { html += '
✅ All NFO files have dateadded elements!
'; }