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

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import React, {useState} from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
import {Link} from 'react-router-dom';
function DownloadFile() {
const [passcode, setPasscode] = useState('');
@ -12,37 +12,42 @@ function DownloadFile() {
const handleDownloadFile = () => {
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 => {
// print json data from response
console.log(response.data);
const fileData = response.data.file;
if (fileData) {
// Convert the data to a Blob
const blob = new Blob([fileData], { type: 'application/octet-stream' });
let filename = 'downloaded_file'; // Default filename
// Create a temporary URL for the Blob
const url = window.URL.createObjectURL(blob);
// Check if the Content-Disposition header exists
if (response.headers['content-disposition']) {
const contentDisposition = response.headers['content-disposition'];
const filenameMatch = contentDisposition.match(/filename="(.+)"/);
// Create a dynamically generated anchor element
const a = document.createElement('a');
a.href = url;
a.download = response.data.filename;
// Trigger a click event on the anchor element to simulate a download
a.click();
// Clean up the temporary URL
window.URL.revokeObjectURL(url);
if (filenameMatch) {
filename = filenameMatch[1];
}
}
const blob = new Blob([response.data], {type: 'application/octet-stream'});
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
// Clean up the temporary URL and remove the dynamically created anchor element
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
})
.catch(error => {
// print error if any
console.log(error);
});
}
};
return (
<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">
@ -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"
/>
</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
</button>
</div>

View File

@ -95,6 +95,10 @@ CORS_ALLOWED_ORIGINS = [
'http://localhost:3000',
]
CORS_EXPOSE_HEADERS = [
'Content-Disposition',
]
TEMPLATES = [
{
'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
# https://docs.djangoproject.com/en/4.2/topics/i18n/

View File

@ -1,13 +1,17 @@
import threading
import uuid
import os
from django.core.cache import cache
from rest_framework.decorators import api_view
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'])
def manage_items(request, *args, **kwargs):
@api_view(['POST'])
def manage_items(request):
if request.method == 'POST':
# Define a timeout value (in seconds)
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
files = request.FILES.getlist('file')
ttl = request.data.get('ttl')
if not ttl:
# Set ttl to 3 days
ttl = 259200 # 3 * 24 * 60 * 60
@ -28,28 +33,33 @@ def manage_items(request, *args, **kwargs):
except ValueError:
return Response({'msg': 'Invalid TTL format'}, status=400)
# Define a function to save a single file in a thread
def save_file_to_redis(file):
# Define a function to save a single file
def save_file_locally(file):
key = uuid.uuid4().hex
# Get the filename
filename = file.name
# Convert file to bytes
content = file.read()
# Define the path to save the file locally
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,
{
'filename': filename,
'content': content
'path': save_path,
},
timeout=ttl)
response = {
'key': key,
'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
@ -61,7 +71,7 @@ def manage_items(request, *args, **kwargs):
# Create a thread for each file
file_threads = []
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)
# Start all file-saving threads
@ -93,62 +103,79 @@ def manage_items(request, *args, **kwargs):
@api_view(['GET', 'PUT', 'DELETE'])
def manage_item(request, *args, **kwargs):
if request.method == 'GET':
if kwargs['key']:
if 'key' in kwargs:
value = cache.get(kwargs['key'])
if value:
response = {
'key': kwargs['key'],
'file': value['content'],
'filename': value['filename'],
'msg': 'success'
}
return Response(response, status=200)
# 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 = {
'msg': 'File not found'
}
return Response(response, status=404)
else:
response = {
'key': kwargs['key'],
'file': None,
'filename': None,
'msg': 'Not found'
}
return Response(response, status=404)
# elif request.method == 'PUT':
# if kwargs['key']:
# request_data = json.loads(request.body)
# new_value = request_data['value']
# value = redis_instance.get(kwargs['key'])
# if value:
# redis_instance.set(kwargs['key'], new_value)
# response = {
# 'key': kwargs['key'],
# 'file': value,
# 'msg': f"Successfully updated {kwargs['key']}"
# }
# return Response(response, status=200)
# else:
# response = {
# 'key': kwargs['key'],
# 'value': None,
# 'msg': 'Not found'
# }
# 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)
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)
else:
response = {
'msg': 'File not found'
}
return Response(response, status=404)
else:
response = {
'key': kwargs['key'],
'file': None,
'msg': 'Not found'
}
return Response(response, status=404)
# elif request.method == 'PUT':
# if kwargs['key']:
# request_data = json.loads(request.body)
# new_value = request_data['value']
# value = redis_instance.get(kwargs['key'])
# if value:
# redis_instance.set(kwargs['key'], new_value)
# response = {
# 'key': kwargs['key'],
# 'file': value,
# 'msg': f"Successfully updated {kwargs['key']}"
# }
# return Response(response, status=200)
# else:
# response = {
# 'key': kwargs['key'],
# 'value': None,
# 'msg': 'Not found'
# }
# return Response(response, status=404)
# class FileView(viewsets.ModelViewSet):
# queryset = File.objects.all()
# serializer_class = FileSerializer