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 os
|
||||||
import uuid
|
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 werkzeug.utils import secure_filename
|
||||||
from config import Config
|
from config import Config
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
|
@ -263,5 +264,50 @@ def delete_asset_file(id):
|
||||||
flash('Failed to delete file: ' + str(e), 'error')
|
flash('Failed to delete file: ' + str(e), 'error')
|
||||||
return redirect(url_for('asset_detail', id=asset_id))
|
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__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=5432, debug=True)
|
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)
|
full_path = self._get_full_path(filename)
|
||||||
if self.protocol == 's3':
|
if self.protocol == 's3':
|
||||||
return self.fs.exists(f"{self.bucket}/{full_path}")
|
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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="attached-files content-box">
|
<div class="files-section">
|
||||||
<h2>Attached Files</h2>
|
<h2>Files</h2>
|
||||||
{% if asset.files %}
|
{% if asset.files %}
|
||||||
<ul class="files-list">
|
<ul class="files-list">
|
||||||
{% for file in asset.files %}
|
{% for file in asset.files %}
|
||||||
<li class="file-item">
|
<li class="file-item">
|
||||||
<a
|
<a
|
||||||
href="{{ file.file_url }}"
|
href="{{ url_for('download_file', file_id=file.id) }}"
|
||||||
target="_blank"
|
|
||||||
class="file-link"
|
class="file-link"
|
||||||
>
|
>
|
||||||
<i class="fas fa-file"></i>
|
<i class="fas fa-file"></i>
|
||||||
|
|
|
@ -109,8 +109,7 @@
|
||||||
{% for file in asset.files %}
|
{% for file in asset.files %}
|
||||||
<li class="file-item">
|
<li class="file-item">
|
||||||
<a
|
<a
|
||||||
href="{{ file.file_url }}"
|
href="{{ url_for('download_file', file_id=file.id) }}"
|
||||||
target="_blank"
|
|
||||||
class="file-link"
|
class="file-link"
|
||||||
>
|
>
|
||||||
<i class="fas fa-file"></i>
|
<i class="fas fa-file"></i>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue