feature(File Storage): Local File Storage

Added:
- Ability to do direct download
- Local File Storage (Save in django media root)
This commit is contained in:
Devoalda 2023-10-26 13:39:36 +08:00
parent 314c84403e
commit 8bd5984a94
3 changed files with 116 additions and 73 deletions

View File

@ -12,37 +12,42 @@ function DownloadFile() {
const handleDownloadFile = () => { const handleDownloadFile = () => {
if (passcode) { if (passcode) {
axios.get(`http://127.0.0.1:8000/api/files/${passcode}/`) axios.get(`http://127.0.0.1:8000/api/files/${passcode}/`, {responseType: 'blob'})
.then(response => { .then(response => {
// print json data from response let filename = 'downloaded_file'; // Default filename
console.log(response.data);
const fileData = response.data.file; // Check if the Content-Disposition header exists
if (fileData) { if (response.headers['content-disposition']) {
// Convert the data to a Blob const contentDisposition = response.headers['content-disposition'];
const blob = new Blob([fileData], { type: 'application/octet-stream' }); const filenameMatch = contentDisposition.match(/filename="(.+)"/);
if (filenameMatch) {
filename = filenameMatch[1];
}
}
const blob = new Blob([response.data], {type: 'application/octet-stream'});
// Create a temporary URL for the Blob
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
// Create a dynamically generated anchor element
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.download = response.data.filename; a.download = filename;
// Trigger a click event on the anchor element to simulate a download a.style.display = 'none';
document.body.appendChild(a);
a.click(); a.click();
// Clean up the temporary URL // Clean up the temporary URL and remove the dynamically created anchor element
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
} document.body.removeChild(a);
}) })
.catch(error => { .catch(error => {
// print error if any
console.log(error); console.log(error);
}); });
} }
}; };
return ( return (
<div className="h-screen flex flex-col items-center justify-center bg-gray-100"> <div className="h-screen flex flex-col items-center justify-center bg-gray-100">
<div className="border p-6 rounded-lg bg-white shadow-md w-96 relative"> <div className="border p-6 rounded-lg bg-white shadow-md w-96 relative">
@ -61,7 +66,8 @@ function DownloadFile() {
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300" className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300"
/> />
</div> </div>
<button onClick={handleDownloadFile} className="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded-lg w-full"> <button onClick={handleDownloadFile}
className="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded-lg w-full">
Download File Download File
</button> </button>
</div> </div>

View File

@ -95,6 +95,10 @@ CORS_ALLOWED_ORIGINS = [
'http://localhost:3000', 'http://localhost:3000',
] ]
CORS_EXPOSE_HEADERS = [
'Content-Disposition',
]
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
@ -177,6 +181,12 @@ AUTH_PASSWORD_VALIDATORS = [
}, },
] ]
# Media Storage
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
if not os.path.exists(MEDIA_ROOT):
os.makedirs(MEDIA_ROOT)
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/ # https://docs.djangoproject.com/en/4.2/topics/i18n/

View File

@ -1,13 +1,17 @@
import threading import threading
import uuid import uuid
import os
from django.core.cache import cache from django.core.cache import cache
from rest_framework.decorators import api_view from rest_framework.decorators import api_view
from rest_framework.response import Response from rest_framework.response import Response
from django.http import HttpResponse
from django.conf import settings
from urllib.parse import quote
@api_view(['GET', 'POST']) @api_view(['POST'])
def manage_items(request, *args, **kwargs): def manage_items(request):
if request.method == 'POST': if request.method == 'POST':
# Define a timeout value (in seconds) # Define a timeout value (in seconds)
timeout = 5 timeout = 5
@ -15,6 +19,7 @@ def manage_items(request, *args, **kwargs):
# Get the list of files and the TTL value from the request data # Get the list of files and the TTL value from the request data
files = request.FILES.getlist('file') files = request.FILES.getlist('file')
ttl = request.data.get('ttl') ttl = request.data.get('ttl')
if not ttl: if not ttl:
# Set ttl to 3 days # Set ttl to 3 days
ttl = 259200 # 3 * 24 * 60 * 60 ttl = 259200 # 3 * 24 * 60 * 60
@ -28,28 +33,33 @@ def manage_items(request, *args, **kwargs):
except ValueError: except ValueError:
return Response({'msg': 'Invalid TTL format'}, status=400) return Response({'msg': 'Invalid TTL format'}, status=400)
# Define a function to save a single file in a thread # Define a function to save a single file
def save_file_to_redis(file): def save_file_locally(file):
key = uuid.uuid4().hex key = uuid.uuid4().hex
# Get the filename # Get the filename
filename = file.name filename = file.name
# Convert file to bytes # Define the path to save the file locally
content = file.read() save_path = os.path.join(settings.MEDIA_ROOT, filename)
# Set with the provided TTL # Save the file locally
with open(save_path, 'wb') as destination:
for chunk in file.chunks():
destination.write(chunk)
# Store the file path in the cache with the provided TTL
cache.set(key, cache.set(key,
{ {
'filename': filename, 'filename': filename,
'content': content 'path': save_path,
}, },
timeout=ttl) timeout=ttl)
response = { response = {
'key': key, 'key': key,
'filename': filename, 'filename': filename,
'msg': f"{key} successfully set to {filename} with TTL {ttl} seconds" 'msg': f"{key} successfully set to {filename} with TTL {ttl} seconds",
} }
# Append the response to the shared responses list # Append the response to the shared responses list
@ -61,7 +71,7 @@ def manage_items(request, *args, **kwargs):
# Create a thread for each file # Create a thread for each file
file_threads = [] file_threads = []
for file in files: for file in files:
file_thread = threading.Thread(target=save_file_to_redis, args=(file,)) file_thread = threading.Thread(target=save_file_locally, args=(file,))
file_threads.append(file_thread) file_threads.append(file_thread)
# Start all file-saving threads # Start all file-saving threads
@ -93,21 +103,55 @@ def manage_items(request, *args, **kwargs):
@api_view(['GET', 'PUT', 'DELETE']) @api_view(['GET', 'PUT', 'DELETE'])
def manage_item(request, *args, **kwargs): def manage_item(request, *args, **kwargs):
if request.method == 'GET': if request.method == 'GET':
if kwargs['key']: if 'key' in kwargs:
value = cache.get(kwargs['key']) value = cache.get(kwargs['key'])
if value: if value:
# Check if the 'path' key is in the stored value
if 'path' in value:
file_path = value['path']
if os.path.exists(file_path):
with open(file_path, 'rb') as f:
response = HttpResponse(f.read(), content_type='application/octet-stream')
response['Content-Disposition'] = f'attachment; filename="{quote(value["filename"])}"'
return response
else:
response = { response = {
'key': kwargs['key'], 'msg': 'File not found'
'file': value['content'], }
'filename': value['filename'], return Response(response, status=404)
'msg': 'success' else:
response = {
'msg': 'Not found'
}
return Response(response, status=404)
elif request.method == 'DELETE':
if 'key' in kwargs:
value = cache.get(kwargs['key'])
if value:
if 'path' in value:
file_path = value['path']
# Check if the file exists
if os.path.exists(file_path):
# Delete the file
os.remove(file_path)
# Delete the cache entry
cache.delete(kwargs['key'])
response = {
'msg': f"{kwargs['key']} successfully deleted"
} }
return Response(response, status=200) return Response(response, status=200)
else:
response = {
'msg': 'File not found'
}
return Response(response, status=404)
else: else:
response = { response = {
'key': kwargs['key'], 'key': kwargs['key'],
'file': None,
'filename': None,
'msg': 'Not found' 'msg': 'Not found'
} }
return Response(response, status=404) return Response(response, status=404)
@ -132,23 +176,6 @@ def manage_item(request, *args, **kwargs):
# 'msg': 'Not found' # 'msg': 'Not found'
# } # }
# return Response(response, status=404) # return Response(response, status=404)
elif request.method == 'DELETE':
if kwargs['key']:
result = cache.delete(kwargs['key'])
if result == 1:
response = {
'msg': f"{kwargs['key']} successfully deleted"
}
return Response(response, status=404)
else:
response = {
'key': kwargs['key'],
'file': None,
'msg': 'Not found'
}
return Response(response, status=404)
# class FileView(viewsets.ModelViewSet): # class FileView(viewsets.ModelViewSet):
# queryset = File.objects.all() # queryset = File.objects.all()
# serializer_class = FileSerializer # serializer_class = FileSerializer