diff --git a/backend/config/__init__.py b/backend/config/__init__.py index 2d9f6cbc5..7a5ddf7b7 100644 --- a/backend/config/__init__.py +++ b/backend/config/__init__.py @@ -140,6 +140,9 @@ SCAN_TIMEOUT: Final = int(os.environ.get("SCAN_TIMEOUT", 60 * 60 * 4)) # 4 hour # TASKS TASK_TIMEOUT: Final = int(os.environ.get("TASK_TIMEOUT", 60 * 5)) # 5 minutes +TASK_RESULT_TTL: Final = int( + os.environ.get("TASK_RESULT_TTL", 24 * 60 * 60) +) # 24 hours ENABLE_RESCAN_ON_FILESYSTEM_CHANGE: Final = str_to_bool( os.environ.get("ENABLE_RESCAN_ON_FILESYSTEM_CHANGE", "false") ) diff --git a/backend/endpoints/responses/__init__.py b/backend/endpoints/responses/__init__.py index c4b0b5c56..61dd1b5ea 100644 --- a/backend/endpoints/responses/__init__.py +++ b/backend/endpoints/responses/__init__.py @@ -13,7 +13,7 @@ class TaskExecutionResponse(TypedDict): class TaskStatusResponse(TaskExecutionResponse): started_at: str | None ended_at: str | None - result: str | None + result: dict[str, Any] | None meta: dict[str, Any] | None diff --git a/backend/endpoints/sockets/scan.py b/backend/endpoints/sockets/scan.py index b78372098..03588effc 100644 --- a/backend/endpoints/sockets/scan.py +++ b/backend/endpoints/sockets/scan.py @@ -8,7 +8,7 @@ import socketio # type: ignore from rq import Worker, get_current_job from rq.job import Job -from config import REDIS_URL, SCAN_TIMEOUT +from config import REDIS_URL, SCAN_TIMEOUT, TASK_RESULT_TTL from endpoints.responses.platform import PlatformSchema from endpoints.responses.rom import SimpleRomSchema from exceptions.fs_exceptions import ( @@ -629,6 +629,7 @@ async def scan_handler(_sid: str, options: dict[str, Any]): roms_ids, metadata_sources, job_timeout=SCAN_TIMEOUT, # Timeout (default of 4 hours) + result_ttl=TASK_RESULT_TTL, ) diff --git a/backend/endpoints/tasks.py b/backend/endpoints/tasks.py index 5550e8ca7..e04c9eedf 100644 --- a/backend/endpoints/tasks.py +++ b/backend/endpoints/tasks.py @@ -8,6 +8,7 @@ from rq.registry import FailedJobRegistry, FinishedJobRegistry from config import ( ENABLE_RESCAN_ON_FILESYSTEM_CHANGE, RESCAN_ON_FILESYSTEM_CHANGE_DELAY, + TASK_RESULT_TTL, TASK_TIMEOUT, ) from decorators.auth import protected_route @@ -305,7 +306,12 @@ async def run_all_tasks(request: Request) -> list[TaskExecutionResponse]: ) jobs = [ - (task_name, low_prio_queue.enqueue(task_instance.run, job_timeout=TASK_TIMEOUT)) + ( + task_name, + low_prio_queue.enqueue( + task_instance.run, job_timeout=TASK_TIMEOUT, result_ttl=TASK_RESULT_TTL + ), + ) for task_name, task_instance in runnable_tasks.items() ] @@ -346,7 +352,9 @@ async def run_single_task(request: Request, task_name: str) -> TaskExecutionResp detail=f"Task '{task_name}' cannot be run", ) - job = low_prio_queue.enqueue(task_instance.run, job_timeout=TASK_TIMEOUT) + job = low_prio_queue.enqueue( + task_instance.run, job_timeout=TASK_TIMEOUT, result_ttl=TASK_RESULT_TTL + ) return { "task_name": task_name, diff --git a/backend/tasks/manual/cleanup_orphaned_resources.py b/backend/tasks/manual/cleanup_orphaned_resources.py index dcc120fe1..1a53e0bef 100644 --- a/backend/tasks/manual/cleanup_orphaned_resources.py +++ b/backend/tasks/manual/cleanup_orphaned_resources.py @@ -19,7 +19,7 @@ class CleanupOrphanedResourcesTask(Task): ) @initialize_context() - async def run(self) -> None: + async def run(self) -> dict[str, int]: """Clean up orphaned resources.""" log.info(f"Starting {self.title} task...") @@ -28,7 +28,7 @@ class CleanupOrphanedResourcesTask(Task): roms_resources_path = os.path.join(RESOURCES_BASE_PATH, "roms") if not os.path.exists(roms_resources_path): log.info("Resources path does not exist, skipping cleanup") - return None + return {"removed_count": 0} existing_platforms = { str(platform.id) for platform in db_platform_handler.get_platforms() @@ -82,5 +82,7 @@ class CleanupOrphanedResourcesTask(Task): log.info(f"Removed {removed_count} orphaned resource directories") log.info("Cleanup of orphaned resources completed!") + return {"removed_count": removed_count} + cleanup_orphaned_resources_task = CleanupOrphanedResourcesTask() diff --git a/backend/tasks/scheduled/scan_library.py b/backend/tasks/scheduled/scan_library.py index c469412bb..2efb3483f 100644 --- a/backend/tasks/scheduled/scan_library.py +++ b/backend/tasks/scheduled/scan_library.py @@ -31,11 +31,11 @@ class ScanLibraryTask(PeriodicTask): func="tasks.scheduled.scan_library.scan_library_task.run", ) - async def run(self): + async def run(self) -> dict[str, str]: if not ENABLE_SCHEDULED_RESCAN: log.info("Scheduled library scan not enabled, unscheduling...") self.unschedule() - return None + return {"status": "skipped", "reason": "Scheduled library scan not enabled"} source_mapping: dict[str, bool] = { MetadataSource.IGDB: meta_igdb_handler.is_enabled(), @@ -54,7 +54,7 @@ class ScanLibraryTask(PeriodicTask): if not metadata_sources: log.warning("No metadata sources enabled, unscheduling library scan") self.unschedule() - return None + return {"status": "skipped", "reason": "No metadata sources enabled"} log.info("Scheduled library scan started...") await scan_platforms( @@ -62,5 +62,7 @@ class ScanLibraryTask(PeriodicTask): ) log.info("Scheduled library scan done") + return {"status": "completed", "message": "Library scan completed successfully"} + scan_library_task = ScanLibraryTask() diff --git a/backend/tasks/scheduled/sync_retroachievements_progress.py b/backend/tasks/scheduled/sync_retroachievements_progress.py index 3a3fc7f7b..1556f2361 100644 --- a/backend/tasks/scheduled/sync_retroachievements_progress.py +++ b/backend/tasks/scheduled/sync_retroachievements_progress.py @@ -24,10 +24,14 @@ class SyncRetroAchievementsProgressTask(PeriodicTask): ) @initialize_context() - async def run(self) -> None: + async def run(self) -> dict[str, str | int]: if not meta_ra_handler.is_enabled(): log.warning("RetroAchievements API is not enabled, skipping progress sync") - return None + return { + "status": "skipped", + "reason": "RetroAchievements API not enabled", + "updated_users": 0, + } log.info("Scheduled RetroAchievements progress sync started...") @@ -57,5 +61,7 @@ class SyncRetroAchievementsProgressTask(PeriodicTask): f"Scheduled RetroAchievements progress sync done. Updated users: {len(users)}" ) + return {"status": "completed", "updated_users": len(users)} + sync_retroachievements_progress_task = SyncRetroAchievementsProgressTask() diff --git a/backend/tasks/scheduled/update_launchbox_metadata.py b/backend/tasks/scheduled/update_launchbox_metadata.py index 2fdac9eb2..d3c94da0c 100644 --- a/backend/tasks/scheduled/update_launchbox_metadata.py +++ b/backend/tasks/scheduled/update_launchbox_metadata.py @@ -38,15 +38,15 @@ class UpdateLaunchboxMetadataTask(RemoteFilePullTask): ) @initialize_context() - async def run(self, force: bool = False) -> None: + async def run(self, force: bool = False) -> dict[str, Any]: if not meta_launchbox_handler.is_enabled(): log.warning("Launchbox API is not enabled, skipping metadata update") - return None + return {"status": "skipped", "reason": "Launchbox API not enabled"} content = await super().run(force) if content is None: log.warning("No content received from launchbox metadata update") - return None + return {"status": "failed", "reason": "No content received"} try: zip_file_bytes = BytesIO(content) @@ -239,9 +239,13 @@ class UpdateLaunchboxMetadataTask(RemoteFilePullTask): except zipfile.BadZipFile: log.error("Bad zip file in launchbox metadata update") - return None + return {"status": "failed", "reason": "Bad zip file"} log.info("Scheduled launchbox metadata update completed!") + return { + "status": "completed", + "message": "Launchbox metadata update completed successfully", + } update_launchbox_metadata_task = UpdateLaunchboxMetadataTask() diff --git a/backend/tasks/scheduled/update_switch_titledb.py b/backend/tasks/scheduled/update_switch_titledb.py index 8c49c4958..a8e902f78 100644 --- a/backend/tasks/scheduled/update_switch_titledb.py +++ b/backend/tasks/scheduled/update_switch_titledb.py @@ -28,10 +28,10 @@ class UpdateSwitchTitleDBTask(RemoteFilePullTask): ) @initialize_context() - async def run(self, force: bool = False) -> None: + async def run(self, force: bool = False) -> dict[str, str]: content = await super().run(force) if content is None: - return None + return {"status": "failed", "reason": "No content received"} index_json = json.loads(content) relevant_data = {k: v for k, v in index_json.items() if k and v} @@ -52,5 +52,10 @@ class UpdateSwitchTitleDBTask(RemoteFilePullTask): log.info("Scheduled switch titledb update completed!") + return { + "status": "completed", + "message": "Switch TitleDB update completed successfully", + } + update_switch_titledb_task = UpdateSwitchTitleDBTask() diff --git a/backend/tasks/tasks.py b/backend/tasks/tasks.py index e806dd840..d9a27a011 100644 --- a/backend/tasks/tasks.py +++ b/backend/tasks/tasks.py @@ -115,7 +115,7 @@ class RemoteFilePullTask(PeriodicTask, ABC): super().__init__(*args, **kwargs) self.url = url - async def run(self, force: bool = False) -> bytes | None: + async def run(self, force: bool = False) -> Any: if not self.enabled and not force: log.info(f"Scheduled {self.description} not enabled, unscheduling...") self.unschedule() diff --git a/backend/watcher.py b/backend/watcher.py index 8b6f89557..cc88df72c 100644 --- a/backend/watcher.py +++ b/backend/watcher.py @@ -15,6 +15,7 @@ from config import ( RESCAN_ON_FILESYSTEM_CHANGE_DELAY, SCAN_TIMEOUT, SENTRY_DSN, + TASK_RESULT_TTL, ) from config.config_manager import config_manager as cm from endpoints.sockets.scan import scan_platforms @@ -144,6 +145,7 @@ def process_changes(changes: Sequence[Change]) -> None: scan_type=ScanType.UNIDENTIFIED, metadata_sources=metadata_sources, timeout=SCAN_TIMEOUT, + result_ttl=TASK_RESULT_TTL, ) return @@ -167,6 +169,7 @@ def process_changes(changes: Sequence[Change]) -> None: scan_type=ScanType.QUICK, metadata_sources=metadata_sources, timeout=SCAN_TIMEOUT, + result_ttl=TASK_RESULT_TTL, ) diff --git a/env.template b/env.template index 5d6f485ff..2db8e2312 100644 --- a/env.template +++ b/env.template @@ -79,6 +79,7 @@ RESCAN_ON_FILESYSTEM_CHANGE_DELAY=5 # Tasks (optional) TASK_TIMEOUT=300 +TASK_RESULT_TTL=86400 ENABLE_SCHEDULED_RESCAN=true SCHEDULED_RESCAN_CRON=0 3 * * * ENABLE_SCHEDULED_UPDATE_SWITCH_TITLEDB=true diff --git a/frontend/src/components/Settings/Administration/RunningTaskItem.vue b/frontend/src/components/Settings/Administration/RunningTaskItem.vue index 7ce5ec166..54131f7c5 100644 --- a/frontend/src/components/Settings/Administration/RunningTaskItem.vue +++ b/frontend/src/components/Settings/Administration/RunningTaskItem.vue @@ -1,6 +1,15 @@