fix download for in app browsing

This commit is contained in:
lelo 2025-04-20 05:42:56 +00:00
parent 97694cdd50
commit 384775dd36
3 changed files with 37 additions and 63 deletions

62
app.py
View File

@ -237,7 +237,7 @@ def api_browse(subpath):
@app.route("/media/<path:subpath>") @app.route("/media/<path:subpath>")
@auth.require_secret @auth.require_secret
def serve_file(subpath): def serve_file(subpath):
# ─── 1) Locate the real file on disk ─── # 1) Locate the real file on disk
root, *relative_parts = subpath.split('/') root, *relative_parts = subpath.split('/')
base_path = session['folders'].get(root) base_path = session['folders'].get(root)
full_path = os.path.join(base_path or '', *relative_parts) 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}") app.logger.error(f"File not found: {full_path}")
return "File not found", 404 return "File not found", 404
# ─── 2) Prep request info ─── # 2) Prep request info
mime, _ = mimetypes.guess_type(full_path) mime, _ = mimetypes.guess_type(full_path)
mime = mime or 'application/octet-stream' mime = mime or 'application/octet-stream'
@ -259,29 +259,18 @@ def serve_file(subpath):
if mime == 'audio/mpeg' and request.method != 'HEAD': if mime == 'audio/mpeg' and request.method != 'HEAD':
do_log = False do_log = False
# ─── 3) Pick the right cache ─── # 3) Pick cache
if mime.startswith('audio/'): cache = cache_audio if mime.startswith('audio/'): cache = cache_audio
elif mime.startswith('image/'): cache = cache_image elif mime.startswith('image/'): cache = cache_image
elif mime.startswith('video/'): cache = cache_video elif mime.startswith('video/'): cache = cache_video
else: cache = cache_other else: cache = cache_other
# ─── 4) Try to stream directly from diskcache ─── # 4) Ensure cached ondisk file and get its path
try: try:
# returns a file-like whose .name is the real path on disk
with cache.read(subpath) as reader: with cache.read(subpath) as reader:
file_path = reader.name 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: except KeyError:
# cache miss → generate & write back to cache # cache miss
cached_hit = False
if mime.startswith('image/'): if mime.startswith('image/'):
# ─── 4a) Image branch: thumbnail & cache the JPEG ─── # ─── 4a) Image branch: thumbnail & cache the JPEG ───
try: try:
@ -289,51 +278,52 @@ def serve_file(subpath):
img.thumbnail((1920, 1920)) img.thumbnail((1920, 1920))
if img.mode in ("RGBA", "P"): if img.mode in ("RGBA", "P"):
img = img.convert("RGB") img = img.convert("RGB")
thumb_io = io.BytesIO() thumb_io = io.BytesIO()
img.save(thumb_io, format='JPEG', quality=85) img.save(thumb_io, format='JPEG', quality=85)
thumb_io.seek(0) thumb_io.seek(0)
# write thumbnail into diskcache as a real file # write thumbnail into diskcache as a real file
cache.set(subpath, thumb_io, read=True) cache.set(subpath, thumb_io, read=True)
# now re-open from cache to get the on-disk path # now re-open from cache to get the on-disk path
with cache.read(subpath) as reader: with cache.read(subpath) as reader:
file_path = reader.name file_path = reader.name
filesize = os.path.getsize(file_path)
response = send_file(
file_path,
mimetype= 'image/jpeg',
conditional= True
)
except Exception as e: except Exception as e:
app.logger.error(f"Image processing failed for {subpath}: {e}") app.logger.error(f"Image processing failed for {subpath}: {e}")
abort(500) abort(500)
else: else:
# ─── 4b) Non-image branch: cache original file ─── # ─── 4b) Non-image branch: cache original file ───
try: try:
# store the real file on diskcache # store the real file on diskcache
cache.set(subpath, open(full_path, 'rb'), read=True) cache.set(subpath, open(full_path, 'rb'), read=True)
# read back to get its path # read back to get its path
with cache.read(subpath) as reader: with cache.read(subpath) as reader:
file_path = reader.name file_path = reader.name
filesize = os.path.getsize(file_path)
response = send_file(
file_path,
mimetype= mime,
conditional= True
)
except Exception as e: except Exception as e:
app.logger.error(f"Failed to cache file {subpath}: {e}") app.logger.error(f"Failed to cache file {subpath}: {e}")
abort(500) 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' response.headers['Cache-Control'] = 'public, max-age=86400'
# 7) Logging
if do_log: if do_log:
a.log_file_access( a.log_file_access(
subpath, filesize, mime, subpath, filesize, mime,

View File

@ -101,37 +101,21 @@ audio.onended = function() {
playerButton.innerHTML = playIcon; playerButton.innerHTML = playIcon;
}; };
async function downloadAudio() { async function downloadAudio() {
const src = audio.currentSrc || audio.src; const audio = document.getElementById('globalAudio');
const src = audio.currentSrc || audio.src;
if (!src) return; if (!src) return;
// Build a fresh URL every time // Build the URL with your download flag + cachebuster
const requestUrl = new URL(src, window.location.href); const downloadUrl = new URL(src, window.location.href);
requestUrl.searchParams.set('_', Date.now()); downloadUrl.searchParams.set('download', 'true');
downloadUrl.searchParams.set('_', Date.now());
// Force network fetch and include sameorigin 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);
// Create a “real” link to that URL and click it
const a = document.createElement('a'); const a = document.createElement('a');
a.href = blobUrl; a.href = downloadUrl.toString();
// Retain original filename if possible // NOTE: do NOT set a.download here we want the server's Content-Disposition to drive it
a.download = decodeURIComponent(src.split('/').pop() || 'audio');
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
}
// Cleanup
URL.revokeObjectURL(blobUrl);
}

View File

@ -1,4 +1,4 @@
const cacheName = 'gottesdienste-v1.7'; const cacheName = 'gottesdienste-v1.8';
const assets = [ const assets = [
'/', '/',
'/static/app.css', '/static/app.css',