From dd2e99572005fcf516521ad30edb17080f2b0688 Mon Sep 17 00:00:00 2001 From: Paul Bienkowski Date: Sat, 13 May 2023 20:14:35 +0200 Subject: [PATCH] generate proper filenames for bulk download, and use that as base folder inside tar --- api/obs/api/cors.py | 1 + api/obs/api/routes/tracks.py | 8 +++++++- api/obs/api/utils.py | 12 +++++++----- frontend/src/pages/MyTracksPage.tsx | 11 +++++++++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/api/obs/api/cors.py b/api/obs/api/cors.py index e46d6f1..3ab27e1 100644 --- a/api/obs/api/cors.py +++ b/api/obs/api/cors.py @@ -21,6 +21,7 @@ def _add_cors_headers(request, response, methods: Iterable[str]) -> None: "origin, content-type, accept, " "authorization, x-xsrf-token, x-request-id" ), + "Access-Control-Expose-Headers": "content-disposition", } response.headers.extend(headers) diff --git a/api/obs/api/routes/tracks.py b/api/obs/api/routes/tracks.py index 1353d03..9868fb5 100644 --- a/api/obs/api/routes/tracks.py +++ b/api/obs/api/routes/tracks.py @@ -1,10 +1,12 @@ import logging import re +from datetime import date from json import load as jsonload from os.path import join, exists, isfile from sanic.exceptions import InvalidUsage, NotFound, Forbidden from sanic.response import file_stream, empty +from slugify import slugify from sqlalchemy import select, func, and_ from sqlalchemy.orm import joinedload @@ -163,7 +165,11 @@ async def tracks_bulk_action(req): await req.ctx.db.commit() if action == "download": - await tar_of_tracks(req, files) + username_slug = slugify(req.ctx.user.username, separator="-") + date_str = date.today().isoformat() + file_basename = f"tracks_{username_slug}_{date_str}" + + await tar_of_tracks(req, files, file_basename) return return empty() diff --git a/api/obs/api/utils.py b/api/obs/api/utils.py index 0e822b9..0ddd0f2 100644 --- a/api/obs/api/utils.py +++ b/api/obs/api/utils.py @@ -1,8 +1,8 @@ from datetime import datetime import logging -import os import queue import tarfile +from os.path import commonpath, relpath, join import dateutil.parser @@ -86,22 +86,24 @@ class chunk: break -async def tar_of_tracks(req, files): +async def tar_of_tracks(req, files, file_basename="tracks"): response = await req.respond( content_type="application/x-gtar", - headers={"Content-Disposition": 'attachment; filename="tracks.tar.bz2"'}, + headers={ + "content-disposition": f'attachment; filename="{file_basename}.tar.bz2"' + }, ) helper = StreamerHelper(response) tar = tarfile.open(name=None, fileobj=helper, mode="w|bz2", bufsize=256 * 512) - root = os.path.commonpath(list(files)) + root = commonpath(list(files)) for fname in files: log.info("Write file to tar: %s", fname) with open(fname, "rb") as fobj: tarinfo = tar.gettarinfo(fname) - tarinfo.name = os.path.relpath(fname, root) + tarinfo.name = join(file_basename, relpath(fname, root)) tar.addfile(tarinfo, fobj) await helper.send_all() tar.close() diff --git a/frontend/src/pages/MyTracksPage.tsx b/frontend/src/pages/MyTracksPage.tsx index cf31702..38f0d4c 100644 --- a/frontend/src/pages/MyTracksPage.tsx +++ b/frontend/src/pages/MyTracksPage.tsx @@ -240,10 +240,17 @@ function TracksTable({ title }) { action, tracks: Object.keys(selectedTracks), }, - returnResponse: true + returnResponse: true, }); if (action === "download") { - download(await response.blob(), "tracks.tar.bz2", "application/x-gtar"); + const contentType = + response.headers.get("content-type") ?? "application/x-gtar"; + + const filename = + response.headers + .get("content-disposition") + ?.match(/filename="([^"]+)"/)?.[1] ?? "tracks.tar.bz2"; + download(await response.blob(), filename, contentType); } setShowBulkDelete(false);