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:
parent
314c84403e
commit
8bd5984a94
|
@ -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
|
||||
|
||||
// Check if the Content-Disposition header exists
|
||||
if (response.headers['content-disposition']) {
|
||||
const contentDisposition = response.headers['content-disposition'];
|
||||
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);
|
||||
|
||||
// Create a dynamically generated anchor element
|
||||
const a = document.createElement('a');
|
||||
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();
|
||||
|
||||
// Clean up the temporary URL
|
||||
// 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>
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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:
|
||||
# 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 = {
|
||||
'key': kwargs['key'],
|
||||
'file': value['content'],
|
||||
'filename': value['filename'],
|
||||
'msg': 'success'
|
||||
'msg': 'File not found'
|
||||
}
|
||||
return Response(response, status=404)
|
||||
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)
|
||||
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"
|
||||
'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
|
||||
|
|
Loading…
Reference in New Issue