Addressing feature request for Content-Disposition
All checks were successful
Build and Publish Docker Image / build (push) Successful in 41s
All checks were successful
Build and Publish Docker Image / build (push) Successful in 41s
This commit is contained in:
parent
75e301b0b2
commit
bdcc6c531b
4 changed files with 67 additions and 8 deletions
48
app.py
48
app.py
|
@ -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)
|
||||
|
|
17
storage.py
17
storage.py
|
@ -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
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue