1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-09-19 19:01:03 +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", "requests>=1.0.0",
"mediafile", "mediafile",
"watchdog>=0.8.0", "watchdog>=0.8.0",
"zipstream", "zipstream-ng>=1.1.0,<2.0.0",
] ]
setup( setup(

View File

@ -22,8 +22,7 @@ from flask import current_app
from PIL import Image from PIL import Image
from pony.orm import ObjectNotFound from pony.orm import ObjectNotFound
from xml.etree import ElementTree from xml.etree import ElementTree
from zipfile import ZIP_DEFLATED from zipstream import ZipStream
from zipstream import ZipFile
from ..cache import CacheMiss from ..cache import CacheMiss
from ..db import Track, Album, Folder, now from ..db import Track, Album, Folder, now
@ -251,16 +250,20 @@ def download_media():
raise NotFound("Folder") raise NotFound("Folder")
# Stream a zip of the tracks + cover art to the client # 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: 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) cover_path = _cover_from_collection(rv, extract=False)
if cover_path: 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 = Response(z, mimetype="application/zip")
resp.headers["Content-Disposition"] = "attachment; filename={}.zip".format(rv.name) resp.headers["Content-Disposition"] = "attachment; filename={}.zip".format(rv.name)
resp.headers["Content-Length"] = len(z)
return resp return resp