Addressing feature request for Content-Disposition
All checks were successful
Build and Publish Docker Image / build (push) Successful in 41s

This commit is contained in:
Timothy Rogers 2025-05-26 14:38:13 -04:00
parent 75e301b0b2
commit bdcc6c531b
4 changed files with 67 additions and 8 deletions

48
app.py
View file

@ -1,6 +1,7 @@
import os
import uuid
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify
import mimetypes
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, send_from_directory, send_file, Response, stream_with_context
from werkzeug.utils import secure_filename
from config import Config
from flask_migrate import Migrate
@ -263,5 +264,50 @@ def delete_asset_file(id):
flash('Failed to delete file: ' + str(e), 'error')
return redirect(url_for('asset_detail', id=asset_id))
@app.route('/download/<int:file_id>')
def download_file(file_id):
"""Download a file with its original filename"""
try:
asset_file = AssetFile.query.get_or_404(file_id)
filename = asset_file.filename
download_name = asset_file.original_filename or filename
# Guess the mime type
mime_type, _ = mimetypes.guess_type(download_name)
if mime_type is None:
mime_type = 'application/octet-stream'
app.logger.debug(f"Starting download of {filename} as {download_name} with type {mime_type}")
try:
file_stream = app.storage.get_file_stream(filename)
def generate():
try:
while True:
chunk = file_stream.read(8192) # Read in 8KB chunks
if not chunk:
break
yield chunk
finally:
file_stream.close()
response = Response(
stream_with_context(generate()),
mimetype=mime_type
)
response.headers['Content-Disposition'] = f'attachment; filename="{download_name}"'
return response
except Exception as e:
app.logger.error(f"Error streaming file {filename}: {str(e)}", exc_info=True)
flash('Error downloading file. Please try again.', 'error')
return redirect(url_for('asset_detail', id=asset_file.asset_id))
except Exception as e:
app.logger.error(f"Error in download_file: {str(e)}", exc_info=True)
flash('File not found or error occurred.', 'error')
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5432, debug=True)

View file

@ -172,4 +172,19 @@ class StorageBackend:
full_path = self._get_full_path(filename)
if self.protocol == 's3':
return self.fs.exists(f"{self.bucket}/{full_path}")
return self.fs.exists(full_path)
return self.fs.exists(full_path)
def get_file_stream(self, filename: str):
"""Get a file stream from storage"""
try:
if self.protocol == 's3':
s3_path = f"{self.bucket}/{self._get_full_path(filename)}"
self.logger.debug(f"Opening S3 file stream: {s3_path}")
return self.fs.open(s3_path, 'rb')
else:
full_path = self._get_full_path(filename)
self.logger.debug(f"Opening local file stream: {full_path}")
return open(full_path, 'rb')
except Exception as e:
self.logger.error(f"Failed to get file stream for {filename}: {str(e)}", exc_info=True)
raise

View file

@ -64,15 +64,14 @@
</div>
{% endif %}
<div class="attached-files content-box">
<h2>Attached Files</h2>
<div class="files-section">
<h2>Files</h2>
{% if asset.files %}
<ul class="files-list">
{% for file in asset.files %}
<li class="file-item">
<a
href="{{ file.file_url }}"
target="_blank"
href="{{ url_for('download_file', file_id=file.id) }}"
class="file-link"
>
<i class="fas fa-file"></i>

View file

@ -109,8 +109,7 @@
{% for file in asset.files %}
<li class="file-item">
<a
href="{{ file.file_url }}"
target="_blank"
href="{{ url_for('download_file', file_id=file.id) }}"
class="file-link"
>
<i class="fas fa-file"></i>