Add bulk downloading
This commit is contained in:
parent
fb3e8bf701
commit
a946ea53c9
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue