fix: update to not add empty dirs
This commit is contained in:
@@ -688,6 +688,9 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
|
|||||||
movie_skipped += 1
|
movie_skipped += 1
|
||||||
elif result == "processed":
|
elif result == "processed":
|
||||||
movie_processed += 1
|
movie_processed += 1
|
||||||
|
elif result == "no_video_files":
|
||||||
|
print(f"INFO: Skipped empty directory: {item.name}")
|
||||||
|
movie_skipped += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: Failed processing movie {item}: {e}")
|
print(f"ERROR: Failed processing movie {item}: {e}")
|
||||||
movie_total += 1
|
movie_total += 1
|
||||||
@@ -1132,6 +1135,34 @@ async def delete_series_episodes(imdb_id: str, dependencies: dict):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_movie(imdb_id: str, dependencies: dict):
|
||||||
|
"""Delete a specific movie from the database"""
|
||||||
|
db = dependencies["db"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
deleted = db.delete_movie(imdb_id)
|
||||||
|
|
||||||
|
if deleted:
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"Deleted movie {imdb_id} from database",
|
||||||
|
"imdb_id": imdb_id
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": f"Movie {imdb_id} not found in database",
|
||||||
|
"imdb_id": imdb_id
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"imdb_id": imdb_id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def cleanup_orphaned_episodes(dependencies: dict):
|
async def cleanup_orphaned_episodes(dependencies: dict):
|
||||||
"""Find and delete episodes that don't have corresponding video files"""
|
"""Find and delete episodes that don't have corresponding video files"""
|
||||||
db = dependencies["db"]
|
db = dependencies["db"]
|
||||||
@@ -1154,6 +1185,28 @@ async def cleanup_orphaned_episodes(dependencies: dict):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def cleanup_orphaned_movies(dependencies: dict):
|
||||||
|
"""Find and delete movies that don't have corresponding video files"""
|
||||||
|
db = dependencies["db"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
deleted_movies = db.delete_orphaned_movies()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"Cleaned up {len(deleted_movies)} orphaned movies",
|
||||||
|
"deleted_count": len(deleted_movies),
|
||||||
|
"deleted_movies": deleted_movies
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"message": "Failed to cleanup orphaned movies"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
# Route Registration
|
# Route Registration
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
@@ -1212,10 +1265,18 @@ def register_routes(app, dependencies: dict):
|
|||||||
async def _delete_series_episodes(imdb_id: str):
|
async def _delete_series_episodes(imdb_id: str):
|
||||||
return await delete_series_episodes(imdb_id, dependencies)
|
return await delete_series_episodes(imdb_id, dependencies)
|
||||||
|
|
||||||
|
@app.delete("/database/movie/{imdb_id}")
|
||||||
|
async def _delete_movie(imdb_id: str):
|
||||||
|
return await delete_movie(imdb_id, dependencies)
|
||||||
|
|
||||||
@app.post("/database/cleanup/orphaned-episodes")
|
@app.post("/database/cleanup/orphaned-episodes")
|
||||||
async def _cleanup_orphaned_episodes():
|
async def _cleanup_orphaned_episodes():
|
||||||
return await cleanup_orphaned_episodes(dependencies)
|
return await cleanup_orphaned_episodes(dependencies)
|
||||||
|
|
||||||
|
@app.post("/database/cleanup/orphaned-movies")
|
||||||
|
async def _cleanup_orphaned_movies():
|
||||||
|
return await cleanup_orphaned_movies(dependencies)
|
||||||
|
|
||||||
@app.post("/manual/scan")
|
@app.post("/manual/scan")
|
||||||
async def _manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = None, scan_type: str = "both", scan_mode: str = "smart"):
|
async def _manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = None, scan_type: str = "both", scan_mode: str = "smart"):
|
||||||
return await manual_scan(background_tasks, path, scan_type, scan_mode, dependencies)
|
return await manual_scan(background_tasks, path, scan_type, scan_mode, dependencies)
|
||||||
|
|||||||
+91
-1
@@ -205,7 +205,7 @@ class NFOGuardDatabase:
|
|||||||
# Debug: Check what was actually saved
|
# Debug: Check what was actually saved
|
||||||
cursor.execute("SELECT dateadded, source FROM movies WHERE imdb_id = %s", (imdb_id,))
|
cursor.execute("SELECT dateadded, source FROM movies WHERE imdb_id = %s", (imdb_id,))
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
print(f"🔍 DATABASE VERIFY: After upsert, found dateadded={result[0] if result else 'NOT_FOUND'}, source={result[1] if result else 'NOT_FOUND'}")
|
print(f"🔍 DATABASE VERIFY: After upsert, found dateadded={result['dateadded'] if result else 'NOT_FOUND'}, source={result['source'] if result else 'NOT_FOUND'}")
|
||||||
|
|
||||||
def get_series_episodes(self, imdb_id: str, has_video_file_only: bool = False) -> List[Dict]:
|
def get_series_episodes(self, imdb_id: str, has_video_file_only: bool = False) -> List[Dict]:
|
||||||
"""Get all episodes for a series"""
|
"""Get all episodes for a series"""
|
||||||
@@ -411,6 +411,96 @@ class NFOGuardDatabase:
|
|||||||
|
|
||||||
return deleted_episodes
|
return deleted_episodes
|
||||||
|
|
||||||
|
def delete_movie(self, imdb_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
Delete a specific movie from the database
|
||||||
|
|
||||||
|
Args:
|
||||||
|
imdb_id: Movie IMDb ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if movie was deleted, False if not found
|
||||||
|
"""
|
||||||
|
with self.get_connection() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
DELETE FROM movies
|
||||||
|
WHERE imdb_id = %s
|
||||||
|
""", (imdb_id,))
|
||||||
|
|
||||||
|
deleted_count = cursor.rowcount
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
return deleted_count > 0
|
||||||
|
|
||||||
|
def delete_orphaned_movies(self) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Find and delete movies that don't have corresponding video files on disk
|
||||||
|
This requires checking filesystem for each movie, so use carefully
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of deleted movies with their details
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
deleted_movies = []
|
||||||
|
|
||||||
|
with self.get_connection() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Get all movies with their paths
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT imdb_id, path, dateadded, source
|
||||||
|
FROM movies
|
||||||
|
""")
|
||||||
|
|
||||||
|
movies_list = cursor.fetchall()
|
||||||
|
|
||||||
|
for movie in movies_list:
|
||||||
|
imdb_id = movie['imdb_id']
|
||||||
|
movie_path = Path(movie['path'])
|
||||||
|
|
||||||
|
if not movie_path.exists():
|
||||||
|
# Movie directory doesn't exist - delete it
|
||||||
|
cursor.execute("""
|
||||||
|
DELETE FROM movies
|
||||||
|
WHERE imdb_id = %s
|
||||||
|
""", (imdb_id,))
|
||||||
|
|
||||||
|
deleted_movies.append({
|
||||||
|
'imdb_id': imdb_id,
|
||||||
|
'reason': 'directory_not_found',
|
||||||
|
'path': str(movie_path),
|
||||||
|
'dateadded': movie['dateadded'],
|
||||||
|
'source': movie['source']
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check for video files
|
||||||
|
video_exts = (".mkv", ".mp4", ".avi", ".mov", ".m4v")
|
||||||
|
has_video = any(f.is_file() and f.suffix.lower() in video_exts
|
||||||
|
for f in movie_path.iterdir() if f.is_file())
|
||||||
|
|
||||||
|
if not has_video:
|
||||||
|
# No video files found - delete this movie
|
||||||
|
cursor.execute("""
|
||||||
|
DELETE FROM movies
|
||||||
|
WHERE imdb_id = %s
|
||||||
|
""", (imdb_id,))
|
||||||
|
|
||||||
|
deleted_movies.append({
|
||||||
|
'imdb_id': imdb_id,
|
||||||
|
'reason': 'no_video_files',
|
||||||
|
'path': str(movie_path),
|
||||||
|
'dateadded': movie['dateadded'],
|
||||||
|
'source': movie['source']
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
return deleted_movies
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Close all database connections"""
|
"""Close all database connections"""
|
||||||
if hasattr(self._local, 'connection'):
|
if hasattr(self._local, 'connection'):
|
||||||
|
|||||||
@@ -188,9 +188,8 @@ class MovieProcessor:
|
|||||||
has_video = any(f.is_file() and f.suffix.lower() in video_exts for f in movie_path.iterdir())
|
has_video = any(f.is_file() and f.suffix.lower() in video_exts for f in movie_path.iterdir())
|
||||||
|
|
||||||
if not has_video:
|
if not has_video:
|
||||||
_log("WARNING", f"No video files found in: {movie_path}")
|
_log("WARNING", f"No video files found in: {movie_path} - skipping database entry")
|
||||||
self.db.upsert_movie_dates(imdb_id, None, None, None, False)
|
return "no_video_files"
|
||||||
return "processed"
|
|
||||||
|
|
||||||
# TIER 1: Check database first (fastest - local lookup)
|
# TIER 1: Check database first (fastest - local lookup)
|
||||||
existing = self.db.get_movie_dates(imdb_id)
|
existing = self.db.get_movie_dates(imdb_id)
|
||||||
|
|||||||
@@ -289,6 +289,9 @@ function updateMoviesTable(data) {
|
|||||||
<button class="btn btn-sm btn-secondary" onclick="debugMovie('${movie.imdb_id}')" title="Debug Data">
|
<button class="btn btn-sm btn-secondary" onclick="debugMovie('${movie.imdb_id}')" title="Debug Data">
|
||||||
<i class="fas fa-bug"></i>
|
<i class="fas fa-bug"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-sm btn-danger" onclick="deleteMovie('${movie.imdb_id}')" style="margin-left: 5px;" title="Delete Movie">
|
||||||
|
<i class="fas fa-trash"></i> Delete
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
@@ -1315,6 +1318,40 @@ async function deleteEpisode(imdbId, season, episode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Movie deletion functionality
|
||||||
|
async function deleteMovie(imdbId) {
|
||||||
|
// Confirmation dialog
|
||||||
|
if (!confirm(`⚠️ Delete Movie?\n\nThis will permanently remove the movie (${imdbId}) from the database.\n\nAre you sure you want to continue?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/database/movie/${imdbId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result.success) {
|
||||||
|
showToast(`✅ Movie deleted successfully`, 'success');
|
||||||
|
|
||||||
|
// Refresh the movies table
|
||||||
|
loadMovies(currentMoviesPage);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const errorMsg = result.message || result.error || 'Unknown error';
|
||||||
|
showToast(`❌ Failed to delete movie: ${errorMsg}`, 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Delete movie failed:', error);
|
||||||
|
showToast(`❌ Delete failed: ${error.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update episode counts in modal after deletion
|
// Update episode counts in modal after deletion
|
||||||
function updateEpisodeModalCounts() {
|
function updateEpisodeModalCounts() {
|
||||||
const remainingRows = document.querySelectorAll('#episodes-table-body tr');
|
const remainingRows = document.querySelectorAll('#episodes-table-body tr');
|
||||||
|
|||||||
Reference in New Issue
Block a user