Add bulk downloading

This commit is contained in:
gluap 2023-04-11 21:06:27 +02:00 committed by Paul Bienkowski
parent fb3e8bf701
commit a946ea53c9
2 changed files with 57 additions and 6 deletions

View file

@ -1,16 +1,17 @@
import logging import logging
import queue
import re import re
import tarfile
from json import load as jsonload from json import load as jsonload
from os.path import join, exists, isfile from os.path import join, exists, isfile
from sanic.exceptions import InvalidUsage, NotFound, Forbidden
from sanic.response import file_stream, empty
from sqlalchemy import select, func, and_ from sqlalchemy import select, func, and_
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from obs.api.db import Track, User, Comment, DuplicateTrackFileError
from obs.api.app import api, require_auth, read_api_key, json from obs.api.app import api, require_auth, read_api_key, json
from obs.api.db import Track, Comment, DuplicateTrackFileError
from sanic.response import file_stream, empty
from sanic.exceptions import InvalidUsage, NotFound, Forbidden
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -58,6 +59,39 @@ async def _return_tracks(req, extend_query, limit, offset, order_by=None):
}, },
) )
class StreamerHelper:
def __init__(self, response):
self.response = response
self.towrite = queue.Queue()
def write(self, data):
self.towrite.put(data)
async def send_all(self):
while True:
try:
tosend = self.towrite.get(block=False)
await self.response.send(tosend)
except queue.Empty:
break
async def tar_of_tracks(req, files):
response = await req.respond(content_type="application/x-gtar", headers={'Content-Disposition': 'attachment; filename="tracks.tar.bz2"'})
helper = StreamerHelper(response)
tar = tarfile.open(name=None, fileobj=helper, mode="w|bz2", bufsize=256 * 512)
for fname in files:
logging.info(f"sending {fname}")
with open(fname, "rb") as fobj:
tar.addfile(tar.gettarinfo(fname),fobj)
await helper.send_all()
tar.close()
await helper.send_all()
await response.eof()
@api.get("/tracks") @api.get("/tracks")
async def get_tracks(req): async def get_tracks(req):
@ -135,13 +169,15 @@ async def tracks_bulk_action(req):
action = body["action"] action = body["action"]
track_slugs = body["tracks"] track_slugs = body["tracks"]
if action not in ("delete", "makePublic", "makePrivate", "reprocess"): if action not in ("delete", "makePublic", "makePrivate", "reprocess", "download"):
raise InvalidUsage("invalid action") raise InvalidUsage("invalid action")
query = select(Track).where( query = select(Track).where(
and_(Track.author_id == req.ctx.user.id, Track.slug.in_(track_slugs)) and_(Track.author_id == req.ctx.user.id, Track.slug.in_(track_slugs))
) )
files = set()
for track in (await req.ctx.db.execute(query)).scalars(): for track in (await req.ctx.db.execute(query)).scalars():
if action == "delete": if action == "delete":
await req.ctx.db.delete(track) await req.ctx.db.delete(track)
@ -155,9 +191,15 @@ async def tracks_bulk_action(req):
track.public = False track.public = False
elif action == "reprocess": elif action == "reprocess":
track.queue_processing() track.queue_processing()
elif action == "download":
files.add(track.get_original_file_path(req.app.config))
await req.ctx.db.commit() await req.ctx.db.commit()
if action == "download":
await tar_of_tracks(req, files)
return
return empty() return empty()

View file

@ -27,6 +27,8 @@ import { Page, FormattedDate, Visibility } from "components";
import api from "api"; import api from "api";
import { useCallbackRef, formatDistance, formatDuration } from "utils"; import { useCallbackRef, formatDistance, formatDuration } from "utils";
import download from "downloadjs";
const COLOR_BY_STATUS: Record<ProcessingStatus, SemanticCOLORS> = { const COLOR_BY_STATUS: Record<ProcessingStatus, SemanticCOLORS> = {
error: "red", error: "red",
complete: "green", complete: "green",
@ -233,12 +235,16 @@ function TracksTable({ title }) {
}; };
const bulkAction = async (action: string) => { const bulkAction = async (action: string) => {
await api.post("/tracks/bulk", { const data = await api.post("/tracks/bulk", {
body: { body: {
action, action,
tracks: Object.keys(selectedTracks), tracks: Object.keys(selectedTracks),
}, },
returnResponse: true
}); });
if (action === "download") {
download(await data.blob(), "tracks.tar.bz2", "application/x-gtar");
}
setShowBulkDelete(false); setShowBulkDelete(false);
setSelectedTracks({}); setSelectedTracks({});
@ -263,6 +269,9 @@ function TracksTable({ title }) {
<Dropdown.Item onClick={() => bulkAction("reprocess")}> <Dropdown.Item onClick={() => bulkAction("reprocess")}>
Reprocess Reprocess
</Dropdown.Item> </Dropdown.Item>
<Dropdown.Item onClick={() => bulkAction("download")}>
Download
</Dropdown.Item>
<Dropdown.Item onClick={() => setShowBulkDelete(true)}> <Dropdown.Item onClick={() => setShowBulkDelete(true)}>
Delete Delete
</Dropdown.Item> </Dropdown.Item>