diff --git a/app/library/downloads/queue_manager.py b/app/library/downloads/queue_manager.py index 95fca5dc..3bd26576 100644 --- a/app/library/downloads/queue_manager.py +++ b/app/library/downloads/queue_manager.py @@ -500,16 +500,15 @@ class DownloadQueue(metaclass=Singleton): if not ids: return {"deleted": 0} - items = await self.done.get_many_by_ids(ids) + items: list[tuple[str, Download]] = await self.done.get_many_by_ids(ids) if not items: return {"deleted": 0} - if self.config.remove_files is not True: - remove_file = False + remove_file = False if self.config.remove_files is not True else remove_file removed_files = 0 deleted_ids: list[str] = [] - deleted_titles: list[str] = [] + item_summaries: list[dict] = [] for item_id, item in items: filename: str = "" @@ -524,6 +523,8 @@ class DownloadQueue(metaclass=Singleton): "title": item.info.title, "status": item.info.status, "remove_file": remove_file, + "preset": item.info.preset, + "path": str(p) if (p := item.info.get_file()) else None, } }, ) @@ -555,7 +556,9 @@ class DownloadQueue(metaclass=Singleton): "download_id": item_id, "item_id": item.info.id, "title": item.info.title, + "preset": item.info.preset, "filename": file_ref.name, + "path": str(file_ref), } }, ) @@ -571,6 +574,8 @@ class DownloadQueue(metaclass=Singleton): "item_id": item.info.id, "title": item.info.title, "filename": rf.name, + "preset": item.info.preset, + "path": str(rf), } }, ) @@ -587,6 +592,8 @@ class DownloadQueue(metaclass=Singleton): "item_id": item.info.id, "title": item.info.title, "filename": filename, + "preset": item.info.preset, + "path": str(rf), } }, ) @@ -601,6 +608,7 @@ class DownloadQueue(metaclass=Singleton): "item_id": item.info.id, "title": item.info.title, "filename": filename, + "preset": item.info.preset, "remove_file": remove_file, "exception_type": type(e).__name__, } @@ -608,61 +616,78 @@ class DownloadQueue(metaclass=Singleton): ) deleted_ids.append(item_id) - deleted_titles.append(item.info.title or item.info.id or item_id) + item_summaries.append( + { + "id": item_id, + "title": item.info.title, + "url": item.info.url, + "preset": item.info.preset, + "path": str(p) if (p := item.info.get_file()) else None, + } + ) - deleted_count = await self.done.bulk_delete(deleted_ids) + deleted_count: int = await self.done.bulk_delete(deleted_ids) if deleted_count < 1: return {"deleted": 0} - title = "History Removed" if removed_files > 0 else "History Cleared" - message = f"Removed {deleted_count} item{'s' if deleted_count != 1 else ''} from history." + message: list[str] = [f"Cleared {deleted_count} item{'s' if deleted_count != 1 else ''} from history."] if removed_files > 0: - message += f" Also removed {removed_files} local file{'s' if removed_files != 1 else ''}." + message.append(f"Also removed {removed_files} local file{'s' if removed_files != 1 else ''}.") self._notify.emit( Events.ITEM_BULK_DELETED, - data={"ids": deleted_ids, "count": deleted_count}, - title=title, - message=message, + data={"count": deleted_count, "removed_files": removed_files, "items": item_summaries}, + title="History Cleared", + message=" ".join(message), ) - summary = ", ".join(deleted_titles[:5]) - if deleted_count > 5: - summary += ", ..." LOG.info( - "Cleared %s history item(s), including %s.", + "Cleared %d history item(s).", deleted_count, - summary, - extra={"deleted_count": deleted_count, "removed_files": removed_files, "titles": deleted_titles[:5]}, + extra={"deleted_count": deleted_count, "removed_files": removed_files, "items": item_summaries}, ) return {"deleted": deleted_count} async def clear_by_status(self, status_filter: str, remove_file: bool = False) -> dict[str, int | str]: - if self.config.remove_files is not True: - remove_file = False + if not (items := await self.done.get_many_by_status(status_filter)): + return {"deleted": 0} - if not remove_file: - deleted_count = await self.done.bulk_delete_by_status(status_filter) - if deleted_count < 1: - return {"deleted": 0} + remove_file = False if self.config.remove_files is not True else remove_file - self._notify.emit( - Events.ITEM_BULK_DELETED, - data={"count": deleted_count, "status": status_filter}, - title="History Cleared", - message=f"Cleared {deleted_count} item{'s' if deleted_count != 1 else ''} from history.", - ) - LOG.info( - "Cleared %s history item(s) with status '%s'.", - deleted_count, - status_filter, - extra={"deleted_count": deleted_count, "status_filter": status_filter}, - ) - return {"deleted": deleted_count} + if remove_file: + return await self.clear_bulk([item_id for item_id, _ in items], remove_file=remove_file) - items = await self.done.get_many_by_status(status_filter) - return await self.clear_bulk([item_id for item_id, _ in items], remove_file=remove_file) + deleted_count: int = await self.done.bulk_delete_by_status(status_filter) + item_summaries: list[dict[str, str | None]] = [ + { + "id": item_id, + "title": dto.info.title, + "url": dto.info.url, + "preset": dto.info.preset, + "path": str(p) if (p := dto.info.get_file()) else None, + } + for item_id, dto in items + ] + + self._notify.emit( + Events.ITEM_BULK_DELETED, + data={"count": deleted_count, "status": status_filter, "items": item_summaries}, + title="History Cleared", + message=f"Cleared {deleted_count} item(s) from history.", + ) + + LOG.info( + "Cleared %d history item(s) with status '%s'.", + deleted_count, + status_filter, + extra={ + "deleted_count": deleted_count, + "status_filter": status_filter, + "items": item_summaries, + }, + ) + return {"deleted": deleted_count} async def get(self, mode: str = "all") -> dict[str, list[dict[str, ItemDTO]]]: """ @@ -708,7 +733,7 @@ class DownloadQueue(metaclass=Singleton): **kwargs: The key-value pair to search for. Supported keys are 'id', 'url'. Returns: - (StoreType, Download) | None: The requested item if found, otherwise None. + tuple[StoreType, Download] | tuple[None, None]: The requested item if found, otherwise None. """ from app.library.DataStore import StoreType diff --git a/app/tests/test_download.py b/app/tests/test_download.py index f5440d0e..a4a60c8d 100644 --- a/app/tests/test_download.py +++ b/app/tests/test_download.py @@ -1501,7 +1501,11 @@ class TestQueueManager: done_store.bulk_delete.assert_awaited_once_with(["done-id-1", "done-id-2"]) queue_manager._notify.emit.assert_called_once() assert queue_manager._notify.emit.call_args.args[0] == Events.ITEM_BULK_DELETED - assert queue_manager._notify.emit.call_args.kwargs["data"] == {"ids": ["done-id-1", "done-id-2"], "count": 2} + assert queue_manager._notify.emit.call_args.kwargs["data"]["count"] == 2 + assert queue_manager._notify.emit.call_args.kwargs["data"]["removed_files"] == 0 + assert len(queue_manager._notify.emit.call_args.kwargs["data"]["items"]) == 2 + assert queue_manager._notify.emit.call_args.kwargs["data"]["items"][0]["id"] == "done-id-1" + assert queue_manager._notify.emit.call_args.kwargs["data"]["items"][1]["id"] == "done-id-2" @pytest.mark.asyncio async def test_clear_status_fetches(self) -> None: @@ -1509,19 +1513,25 @@ class TestQueueManager: queue_manager.config = Mock(remove_files=False, download_path="/tmp") queue_manager._notify = Mock() + item = Mock() + item.info = make_item(id="done-id", title="Cleared clip") + done_store = Mock() done_store.bulk_delete_by_status = AsyncMock(return_value=1) - done_store.get_many_by_status = AsyncMock() + done_store.get_many_by_status = AsyncMock(return_value=[("done-id", item)]) queue_manager.done = done_store result = await DownloadQueue.clear_by_status(queue_manager, "finished", remove_file=False) assert result == {"deleted": 1} + done_store.get_many_by_status.assert_awaited_once_with("finished") done_store.bulk_delete_by_status.assert_awaited_once_with("finished") - done_store.get_many_by_status.assert_not_called() queue_manager._notify.emit.assert_called_once() assert queue_manager._notify.emit.call_args.args[0] == Events.ITEM_BULK_DELETED - assert queue_manager._notify.emit.call_args.kwargs["data"] == {"count": 1, "status": "finished"} + assert queue_manager._notify.emit.call_args.kwargs["data"]["count"] == 1 + assert queue_manager._notify.emit.call_args.kwargs["data"]["status"] == "finished" + assert len(queue_manager._notify.emit.call_args.kwargs["data"]["items"]) == 1 + assert queue_manager._notify.emit.call_args.kwargs["data"]["items"][0]["id"] == "done-id" @pytest.mark.asyncio async def test_clear_status_files_fetch(self) -> None: