diff --git a/setup.py b/setup.py index 3b46027..60b6584 100755 --- a/setup.py +++ b/setup.py @@ -21,7 +21,8 @@ reqs = [ 'pony>=0.7.6', 'Pillow', 'requests>=1.0.0', - 'mutagen>=1.33' + 'mutagen>=1.33', + 'zipstream' ] extras = { 'watcher': [ 'watchdog>=0.8.0' ] diff --git a/supysonic/api/media.py b/supysonic/api/media.py index a7307f6..e1ab163 100644 --- a/supysonic/api/media.py +++ b/supysonic/api/media.py @@ -14,11 +14,15 @@ import os.path import requests import shlex import subprocess +import uuid from flask import request, Response, send_file 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 .. import scanner from ..db import Track, Album, Artist, Folder, User, ClientPrefs, now @@ -129,8 +133,29 @@ def stream_media(): @api.route('/download.view', methods = [ 'GET', 'POST' ]) def download_media(): - res = get_entity(Track) - return send_file(res.path, mimetype = res.content_type, conditional=True) + id = request.values['id'] + uid = uuid.UUID(id) + + try: # Track -> direct download + rv = Track[uid] + return send_file(rv.path, mimetype = rv.content_type, conditional=True) + except ObjectNotFound: + pass + + try: # Folder -> stream zipped tracks, non recursive + rv = Folder[uid] + except ObjectNotFound: + try: # Album -> stream zipped tracks + rv = Album[uid] + except ObjectNotFound: + raise NotFound('Track, Folder or Album') + + z = ZipFile(compression = ZIP_DEFLATED) + for track in rv.tracks: + z.write(track.path, os.path.basename(track.path)) + resp = Response(z, mimetype = 'application/zip') + resp.headers['Content-Disposition'] = 'attachment; filename={}.zip'.format(rv.name) + return resp @api.route('/getCoverArt.view', methods = [ 'GET', 'POST' ]) def cover_art(): diff --git a/tests/api/test_media.py b/tests/api/test_media.py index 802f9b8..dab5380 100644 --- a/tests/api/test_media.py +++ b/tests/api/test_media.py @@ -89,8 +89,8 @@ class MediaTestCase(ApiTestBase): self._make_request('download', error = 10) self._make_request('download', { 'id': 'string' }, error = 0) self._make_request('download', { 'id': str(uuid.uuid4()) }, error = 70) - self._make_request('download', { 'id': str(self.folderid) }, error = 70) + # download single file rv = self.client.get('/rest/download.view', query_string = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests', 'id': str(self.trackid) }) self.assertEqual(rv.status_code, 200) self.assertEqual(rv.mimetype, 'audio/mpeg') @@ -98,6 +98,11 @@ class MediaTestCase(ApiTestBase): with db_session: self.assertEqual(Track[self.trackid].play_count, 0) + # dowload folder + rv = self.client.get('/rest/download.view', query_string = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests', 'id': str(self.folderid) }) + self.assertEqual(rv.status_code, 200) + self.assertEqual(rv.mimetype, 'application/zip') + def test_get_cover_art(self): self._make_request('getCoverArt', error = 10) self._make_request('getCoverArt', { 'id': 'string' }, error = 0)