feat(SerieScanner): add warning event for duplicate series keys
- Add on_warning event system with subscribe/unsubscribe methods - Change duplicate key handling from error to warning - Fire on_warning event when duplicate series detected - Include metadata: key, duplicate_folder, existing_folder
This commit is contained in:
@@ -99,6 +99,7 @@ class SerieScanner:
|
|||||||
|
|
||||||
self.events.on_progress = []
|
self.events.on_progress = []
|
||||||
self.events.on_error = []
|
self.events.on_error = []
|
||||||
|
self.events.on_warning = []
|
||||||
self.events.on_completion = []
|
self.events.on_completion = []
|
||||||
|
|
||||||
logger.info("Initialized SerieScanner with base path: %s", abs_path)
|
logger.info("Initialized SerieScanner with base path: %s", abs_path)
|
||||||
@@ -191,7 +192,25 @@ class SerieScanner:
|
|||||||
"""
|
"""
|
||||||
if handler in self.events.on_error:
|
if handler in self.events.on_error:
|
||||||
self.events.on_error.remove(handler)
|
self.events.on_error.remove(handler)
|
||||||
|
|
||||||
|
def subscribe_on_warning(self, handler):
|
||||||
|
"""
|
||||||
|
Subscribe a handler to an event.
|
||||||
|
Args:
|
||||||
|
handler: Callable to handle the event
|
||||||
|
"""
|
||||||
|
if handler not in self.events.on_warning:
|
||||||
|
self.events.on_warning.append(handler)
|
||||||
|
|
||||||
|
def unsubscribe_on_warning(self, handler):
|
||||||
|
"""
|
||||||
|
Unsubscribe a handler from an event.
|
||||||
|
Args:
|
||||||
|
handler: Callable to remove
|
||||||
|
"""
|
||||||
|
if handler in self.events.on_warning:
|
||||||
|
self.events.on_warning.remove(handler)
|
||||||
|
|
||||||
def subscribe_on_completion(self, handler):
|
def subscribe_on_completion(self, handler):
|
||||||
"""
|
"""
|
||||||
Subscribe a handler to an event.
|
Subscribe a handler to an event.
|
||||||
@@ -454,11 +473,27 @@ class SerieScanner:
|
|||||||
|
|
||||||
# Store by key (primary identifier), not folder
|
# Store by key (primary identifier), not folder
|
||||||
if serie.key in self.keyDict:
|
if serie.key in self.keyDict:
|
||||||
logger.error(
|
existing = self.keyDict[serie.key]
|
||||||
"Duplicate series found with key '%s' "
|
logger.warning(
|
||||||
"(folder: '%s')",
|
"Duplicate series found with key '%s': "
|
||||||
|
"folder '%s' maps to same key as existing folder '%s'. "
|
||||||
|
"Skipping duplicate folder.",
|
||||||
serie.key,
|
serie.key,
|
||||||
folder
|
folder,
|
||||||
|
existing.folder
|
||||||
|
)
|
||||||
|
self._safe_call_event(
|
||||||
|
self.events.on_warning,
|
||||||
|
{
|
||||||
|
"operation_id": self._current_operation_id,
|
||||||
|
"warning": "duplicate_key",
|
||||||
|
"message": f"Duplicate series skipped: '{folder}' maps to key '{serie.key}' already used by '{existing.folder}'",
|
||||||
|
"metadata": {
|
||||||
|
"key": serie.key,
|
||||||
|
"duplicate_folder": folder,
|
||||||
|
"existing_folder": existing.folder,
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.keyDict[serie.key] = serie
|
self.keyDict[serie.key] = serie
|
||||||
|
|||||||
@@ -115,6 +115,29 @@ def _is_series_being_downloaded(series_folder: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _cleanup_stale_files_after_rename(new_path: Path, new_name: str) -> None:
|
||||||
|
"""Remove legacy 'key' file after successful folder rename.
|
||||||
|
|
||||||
|
Also checks for orphaned folders with the same key that may have been
|
||||||
|
left behind from previous rename operations.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
new_path: The new folder path after rename.
|
||||||
|
new_name: The new folder name.
|
||||||
|
"""
|
||||||
|
key_file = new_path / "key"
|
||||||
|
if key_file.exists():
|
||||||
|
try:
|
||||||
|
key_file.unlink()
|
||||||
|
logger.info(
|
||||||
|
"Removed legacy 'key' file after rename: %s", key_file
|
||||||
|
)
|
||||||
|
except OSError as exc:
|
||||||
|
logger.warning(
|
||||||
|
"Could not remove legacy 'key' file %s: %s", key_file, exc
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _update_database_paths(
|
async def _update_database_paths(
|
||||||
old_folder: str,
|
old_folder: str,
|
||||||
new_folder: str,
|
new_folder: str,
|
||||||
@@ -315,6 +338,9 @@ async def validate_and_rename_series_folders() -> Dict[str, int]:
|
|||||||
# Update database records
|
# Update database records
|
||||||
await _update_database_paths(current_name, expected_name, anime_dir)
|
await _update_database_paths(current_name, expected_name, anime_dir)
|
||||||
|
|
||||||
|
# Clean up stale/legacy files after successful rename
|
||||||
|
_cleanup_stale_files_after_rename(expected_path, expected_name)
|
||||||
|
|
||||||
except PermissionError as exc:
|
except PermissionError as exc:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Permission denied renaming '%s' → '%s': %s",
|
"Permission denied renaming '%s' → '%s': %s",
|
||||||
|
|||||||
Reference in New Issue
Block a user