1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-11-09 11:42:16 +00:00

Switch to using zipstream-ng to generate and stream zip files

- Fixes zip downloads failing when zipping enough data that Zip64
   extensions are required by automatically enabling them if needed.
 - Fixes zip downloads failing when a file has a datestamp that zipfiles
   cannot store (pre-1980 or post-2108) by clamping them within the
   supported range.
 - Massively speeds up zip downloads by disabling compression (audio
   files generally don't compress well anyway)
 - Computes the total size of a generated zip file before streaming it
   and sets the `Content-Length` header. This allows clients to show a
   final size and progress bar while downloading, as well as detect if
   the download fails.
 - Adds a check to prevent sending an empty zip file to the client if
   there was no content to download (will error out instead).
This commit is contained in:
Carey Metcalfe 2021-10-07 09:44:48 -04:00
parent 5490189484
commit 387a5e3de3
2 changed files with 9 additions and 6 deletions

View File

@ -18,7 +18,7 @@ reqs = [
"requests>=1.0.0",
"mediafile",
"watchdog>=0.8.0",
"zipstream",
"zipstream-ng>=1.1.0,<2.0.0",
]
setup(

View File

@ -22,8 +22,7 @@ from flask import current_app
from PIL import Image
from pony.orm import ObjectNotFound
from xml.etree import ElementTree
from zipfile import ZIP_DEFLATED
from zipstream import ZipFile
from zipstream import ZipStream
from ..cache import CacheMiss
from ..db import Track, Album, Folder, now
@ -251,16 +250,20 @@ def download_media():
raise NotFound("Folder")
# Stream a zip of the tracks + cover art to the client
z = ZipFile(compression=ZIP_DEFLATED)
z = ZipStream(sized=True)
for track in rv.tracks:
z.write(track.path, os.path.basename(track.path))
z.add_path(track.path)
cover_path = _cover_from_collection(rv, extract=False)
if cover_path:
z.write(cover_path, os.path.basename(cover_path))
z.add_path(cover_path)
if not z:
raise GenericError("Nothing to download")
resp = Response(z, mimetype="application/zip")
resp.headers["Content-Disposition"] = "attachment; filename={}.zip".format(rv.name)
resp.headers["Content-Length"] = len(z)
return resp