From 384775dd3651ce33815ac654e8c20b3d2d5deb6c Mon Sep 17 00:00:00 2001 From: lelo Date: Sun, 20 Apr 2025 05:42:56 +0000 Subject: [PATCH] fix download for in app browsing --- app.py | 62 ++++++++++++++++++------------------------- static/audioplayer.js | 36 +++++++------------------ static/sw.js | 2 +- 3 files changed, 37 insertions(+), 63 deletions(-) diff --git a/app.py b/app.py index 689c0fc..17304b2 100755 --- a/app.py +++ b/app.py @@ -237,7 +237,7 @@ def api_browse(subpath): @app.route("/media/") @auth.require_secret def serve_file(subpath): - # ─── 1) Locate the real file on disk ─── + # 1) Locate the real file on disk root, *relative_parts = subpath.split('/') base_path = session['folders'].get(root) full_path = os.path.join(base_path or '', *relative_parts) @@ -246,7 +246,7 @@ def serve_file(subpath): app.logger.error(f"File not found: {full_path}") return "File not found", 404 - # ─── 2) Prep request info ─── + # 2) Prep request info mime, _ = mimetypes.guess_type(full_path) mime = mime or 'application/octet-stream' @@ -259,29 +259,18 @@ def serve_file(subpath): if mime == 'audio/mpeg' and request.method != 'HEAD': do_log = False - # ─── 3) Pick the right cache ─── + # 3) Pick cache if mime.startswith('audio/'): cache = cache_audio elif mime.startswith('image/'): cache = cache_image elif mime.startswith('video/'): cache = cache_video else: cache = cache_other - # ─── 4) Try to stream directly from diskcache ─── + # 4) Ensure cached on‑disk file and get its path try: - # returns a file-like whose .name is the real path on disk with cache.read(subpath) as reader: file_path = reader.name - filesize = os.path.getsize(file_path) - response = send_file( - file_path, - mimetype= mime, - conditional= True # enable Range / If-Modified / etc - ) - cached_hit = True - except KeyError: - # cache miss → generate & write back to cache - cached_hit = False - + # cache miss if mime.startswith('image/'): # ─── 4a) Image branch: thumbnail & cache the JPEG ─── try: @@ -289,51 +278,52 @@ def serve_file(subpath): img.thumbnail((1920, 1920)) if img.mode in ("RGBA", "P"): img = img.convert("RGB") - thumb_io = io.BytesIO() img.save(thumb_io, format='JPEG', quality=85) thumb_io.seek(0) - # write thumbnail into diskcache as a real file cache.set(subpath, thumb_io, read=True) - # now re-open from cache to get the on-disk path with cache.read(subpath) as reader: file_path = reader.name - filesize = os.path.getsize(file_path) - response = send_file( - file_path, - mimetype= 'image/jpeg', - conditional= True - ) - except Exception as e: app.logger.error(f"Image processing failed for {subpath}: {e}") abort(500) - else: # ─── 4b) Non-image branch: cache original file ─── try: # store the real file on diskcache cache.set(subpath, open(full_path, 'rb'), read=True) - # read back to get its path with cache.read(subpath) as reader: file_path = reader.name - filesize = os.path.getsize(file_path) - response = send_file( - file_path, - mimetype= mime, - conditional= True - ) - except Exception as e: app.logger.error(f"Failed to cache file {subpath}: {e}") abort(500) - # ─── 5) Common headers & logging ─── + filesize = os.path.getsize(file_path) + cached_hit = True # or False if you want to pass that into your logger + + # 5) Figure out download flag and filename + ask_download = request.args.get('download') == 'true' + filename = os.path.basename(full_path) + + # 6) Single send_file call with proper attachment handling + response = send_file( + file_path, + mimetype = mime, + conditional = True, + as_attachment = ask_download, + download_name = filename if ask_download else None + ) + + # Explicitly force inline if not downloading + if not ask_download: + response.headers['Content-Disposition'] = 'inline' + response.headers['Cache-Control'] = 'public, max-age=86400' + # 7) Logging if do_log: a.log_file_access( subpath, filesize, mime, diff --git a/static/audioplayer.js b/static/audioplayer.js index 5e2220e..11fff2d 100644 --- a/static/audioplayer.js +++ b/static/audioplayer.js @@ -101,37 +101,21 @@ audio.onended = function() { playerButton.innerHTML = playIcon; }; - - async function downloadAudio() { - const src = audio.currentSrc || audio.src; + const audio = document.getElementById('globalAudio'); + const src = audio.currentSrc || audio.src; if (!src) return; - // Build a fresh URL every time - const requestUrl = new URL(src, window.location.href); - requestUrl.searchParams.set('_', Date.now()); - - // Force network fetch and include same‑origin credentials - const response = await fetch(requestUrl.toString(), { - credentials: 'same-origin', - cache: 'no-store' - }); - if (!response.ok) { - throw new Error(`Download failed: ${response.status}`); - } - - // Turn the response into a blob, then download it - const blob = await response.blob(); - const blobUrl = URL.createObjectURL(blob); + // Build the URL with your download flag + cache‑buster + const downloadUrl = new URL(src, window.location.href); + downloadUrl.searchParams.set('download', 'true'); + downloadUrl.searchParams.set('_', Date.now()); + // Create a “real” link to that URL and click it const a = document.createElement('a'); - a.href = blobUrl; - // Retain original filename if possible - a.download = decodeURIComponent(src.split('/').pop() || 'audio'); + a.href = downloadUrl.toString(); + // NOTE: do NOT set a.download here – we want the server's Content-Disposition to drive it document.body.appendChild(a); a.click(); document.body.removeChild(a); - - // Cleanup - URL.revokeObjectURL(blobUrl); -} +} \ No newline at end of file diff --git a/static/sw.js b/static/sw.js index eb02061..f9b1140 100644 --- a/static/sw.js +++ b/static/sw.js @@ -1,4 +1,4 @@ -const cacheName = 'gottesdienste-v1.7'; +const cacheName = 'gottesdienste-v1.8'; const assets = [ '/', '/static/app.css',