refactor: switch api/history to be fully async.

This commit is contained in:
arabcoders 2026-01-25 21:34:26 +03:00
parent 7d6dc7ae76
commit fe6fe42a65
3 changed files with 924 additions and 364 deletions

1200
API.md

File diff suppressed because it is too large Load diff

View file

@ -385,34 +385,67 @@ async def items_add(request: Request, queue: DownloadQueue, encoder: Encoder) ->
Returns:
Response: The response object.
Query Parameters:
sync (bool): If true, wait for all items to be processed synchronously. Default: false
"""
data = await request.json()
if isinstance(data, dict):
data = [data]
items = []
items: list[Item] = []
for item in data:
try:
items.append(Item.format(item))
except ValueError as e:
return web.json_response(data={"error": str(e), "data": item}, status=web.HTTPBadRequest.status_code)
status: list[dict] = await asyncio.wait_for(
fut=asyncio.gather(*[queue.add(item=item) for item in items]),
timeout=None,
if "true" == request.query.get("sync", "false").lower():
status: list[dict] = await asyncio.wait_for(
fut=asyncio.gather(*[queue.add(item=item) for item in items]),
timeout=None,
)
response: list[dict[str, Any]] = []
for i, item in enumerate(items):
it = {"item": item, "status": "ok" == status[i].get("status"), "msg": status[i].get("msg")}
if status[i].get("hidden"):
it["hidden"] = True
response.append(it)
return web.json_response(data=response, status=web.HTTPOk.status_code, dumps=encoder.encode)
from app.library.downloads.utils import handle_task_exception
batch_id: str = f"batch_{asyncio.get_running_loop().time():.0f}"
for idx, item in enumerate(items):
if not item.extras:
item.extras = {}
item.extras["batch_id"] = batch_id
item.extras["batch_index"] = idx
item.extras["batch_total"] = len(items)
task = asyncio.create_task(
queue.add(item=item),
name=f"bulk_add_{batch_id}_{idx}",
)
task.add_done_callback(lambda t: handle_task_exception(t, LOG))
return web.json_response(
data={
"status": "accepted",
"message": f"Accepted {len(items)} item(s) for processing",
"batch_id": batch_id,
"count": len(items),
},
status=web.HTTPAccepted.status_code,
dumps=encoder.encode,
)
response: list[dict[str, Any]] = []
for i, item in enumerate(items):
it = {"item": item, "status": "ok" == status[i].get("status"), "msg": status[i].get("msg")}
if status[i].get("hidden"):
it["hidden"] = True
response.append(it)
return web.json_response(data=response, status=web.HTTPOk.status_code, dumps=encoder.encode)
@route("POST", "api/history/start", "items_start")
async def items_start(request: Request, queue: DownloadQueue, encoder: Encoder) -> Response:

View file

@ -309,7 +309,6 @@ const form = useStorage<item_request>('local_config_v1', {
extras: {},
}) as Ref<item_request>
const is_valid_dl_field = (dl_field: string): boolean => {
if (dlFieldsExtra.includes(dl_field)) {
return true
@ -471,19 +470,25 @@ const addDownload = async () => {
let had_errors = false
data.forEach((item: Record<string, any>) => {
if (false !== item.status) {
return
}
if (200 === response.status) {
data.forEach((item: Record<string, any>) => {
if (false !== item.status) {
return
}
had_errors = true
had_errors = true
if (item?.hidden) {
return
}
if (item?.hidden) {
return
}
toast.error(`Error: ${item.msg || 'Failed to add download.'}`)
})
toast.error(`Error: ${item.msg || 'Failed to add download.'}`)
})
}
if (202 === response.status) {
toast.success(data.message,{ timeout: 2000 })
}
if (false === had_errors) {
form.value.url = ''