|
|
import uuid |
|
|
from pathlib import Path |
|
|
|
|
|
import socketio |
|
|
from fastapi import FastAPI, Request, UploadFile, File |
|
|
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse |
|
|
from starlette.middleware.sessions import SessionMiddleware |
|
|
|
|
|
|
|
|
sio = socketio.AsyncServer(async_mode='asgi') |
|
|
app = FastAPI() |
|
|
app.add_middleware(SessionMiddleware, secret_key="your-secret-key") |
|
|
|
|
|
|
|
|
socket_app = socketio.ASGIApp(sio, other_asgi_app=app) |
|
|
|
|
|
|
|
|
UPLOAD_DIR = Path("temp_uploads") |
|
|
UPLOAD_DIR.mkdir(exist_ok=True) |
|
|
|
|
|
@app.get("/", response_class=HTMLResponse) |
|
|
async def index(request: Request): |
|
|
html_content = """ |
|
|
<!DOCTYPE html> |
|
|
<html> |
|
|
<head> |
|
|
<title>File Upload with Local History</title> |
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> |
|
|
<script src="https://cdn.socket.io/4.6.1/socket.io.min.js"></script> |
|
|
</head> |
|
|
<body class="bg-dark text-light"> |
|
|
<div class="container mt-5"> |
|
|
<!-- Card for File Upload --> |
|
|
<div class="card bg-secondary mb-4"> |
|
|
<div class="card-header"> |
|
|
<h2>Upload File</h2> |
|
|
</div> |
|
|
<div class="card-body"> |
|
|
<form id="uploadForm"> |
|
|
<div class="mb-3"> |
|
|
<input type="file" id="fileInput" class="form-control"> |
|
|
</div> |
|
|
<button type="submit" class="btn btn-primary">Upload</button> |
|
|
</form> |
|
|
<div class="progress mt-3" style="height: 25px; display: none;"> |
|
|
<div id="progressBar" class="progress-bar" role="progressbar" style="width: 0%;">0%</div> |
|
|
</div> |
|
|
<div id="downloadLink" class="mt-3"></div> |
|
|
</div> |
|
|
</div> |
|
|
<!-- Card for Upload History --> |
|
|
<div class="card bg-secondary"> |
|
|
<div class="card-header"> |
|
|
<h3>Your Upload History (Stored in Local Storage)</h3> |
|
|
</div> |
|
|
<div class="card-body"> |
|
|
<ul id="historyList" class="list-group"></ul> |
|
|
<button id="refreshHistory" class="btn btn-secondary mt-2">Refresh History</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<script> |
|
|
// Socket.IO connection |
|
|
const socket = io(); |
|
|
|
|
|
socket.on('connect', () => { |
|
|
console.log('Connected to Socket.IO server'); |
|
|
}); |
|
|
|
|
|
// Listen for progress updates |
|
|
socket.on('upload_progress', data => { |
|
|
const progressDiv = document.querySelector('.progress'); |
|
|
const progressBar = document.getElementById('progressBar'); |
|
|
progressDiv.style.display = 'block'; |
|
|
progressBar.style.width = data.percent + '%'; |
|
|
progressBar.innerText = data.percent + '%'; |
|
|
}); |
|
|
|
|
|
// Listen for upload completion |
|
|
socket.on('upload_complete', data => { |
|
|
const downloadLinkDiv = document.getElementById('downloadLink'); |
|
|
downloadLinkDiv.innerHTML = '<a href="' + data.download_url + '" class="text-info">Download File</a>'; |
|
|
storeInHistory(data.download_url); |
|
|
refreshHistory(); |
|
|
}); |
|
|
|
|
|
// Upload file via XMLHttpRequest |
|
|
document.getElementById('uploadForm').addEventListener('submit', function(event) { |
|
|
event.preventDefault(); |
|
|
const fileInput = document.getElementById('fileInput'); |
|
|
const file = fileInput.files[0]; |
|
|
if (!file) { |
|
|
alert("Please select a file!"); |
|
|
return; |
|
|
} |
|
|
const formData = new FormData(); |
|
|
formData.append('file', file); |
|
|
const xhr = new XMLHttpRequest(); |
|
|
xhr.open('POST', '/upload', true); |
|
|
xhr.send(formData); |
|
|
}); |
|
|
|
|
|
// Local storage functions to store and retrieve history |
|
|
function storeInHistory(downloadUrl) { |
|
|
let history = JSON.parse(localStorage.getItem('uploadHistory')) || []; |
|
|
history.push(downloadUrl); |
|
|
localStorage.setItem('uploadHistory', JSON.stringify(history)); |
|
|
} |
|
|
|
|
|
function refreshHistory() { |
|
|
let history = JSON.parse(localStorage.getItem('uploadHistory')) || []; |
|
|
const historyList = document.getElementById('historyList'); |
|
|
historyList.innerHTML = ""; |
|
|
history.forEach(link => { |
|
|
const li = document.createElement('li'); |
|
|
li.className = 'list-group-item bg-dark text-light'; |
|
|
li.innerHTML = '<a href="' + link + '" class="text-info">' + link + '</a>'; |
|
|
historyList.appendChild(li); |
|
|
}); |
|
|
} |
|
|
|
|
|
// Refresh history on page load |
|
|
window.onload = refreshHistory; |
|
|
// Refresh history manually on button click |
|
|
document.getElementById('refreshHistory').addEventListener('click', refreshHistory); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
""" |
|
|
return HTMLResponse(content=html_content) |
|
|
|
|
|
@app.post("/upload") |
|
|
async def upload_file(request: Request, file: UploadFile = File(...)): |
|
|
|
|
|
session_id = request.session.get("id") |
|
|
if not session_id: |
|
|
session_id = str(uuid.uuid4()) |
|
|
request.session["id"] = session_id |
|
|
|
|
|
|
|
|
file_id = str(uuid.uuid4()) |
|
|
file_path = UPLOAD_DIR / file_id |
|
|
|
|
|
CHUNK_SIZE = 1024 * 1024 |
|
|
total = 0 |
|
|
content = await file.read() |
|
|
total_size = len(content) |
|
|
with file_path.open("wb") as f: |
|
|
for i in range(0, total_size, CHUNK_SIZE): |
|
|
chunk = content[i:i+CHUNK_SIZE] |
|
|
f.write(chunk) |
|
|
total += len(chunk) |
|
|
percent = int((total / total_size) * 100) |
|
|
await sio.emit('upload_progress', {'percent': percent}, to=session_id) |
|
|
|
|
|
|
|
|
base_url = request.url_for("index").rstrip("/") |
|
|
download_url = f"{base_url}/download/{file_id}" |
|
|
|
|
|
|
|
|
await sio.emit('upload_complete', {'download_url': download_url}, to=session_id) |
|
|
|
|
|
return JSONResponse({"download_url": download_url}) |
|
|
|
|
|
@app.get("/download/{file_id}") |
|
|
async def download_file(file_id: str): |
|
|
file_path = UPLOAD_DIR / file_id |
|
|
if file_path.exists(): |
|
|
return FileResponse(file_path, filename="downloaded_file") |
|
|
return JSONResponse({"error": "File not found"}, status_code=404) |
|
|
|
|
|
@sio.event |
|
|
async def connect(sid, environ): |
|
|
|
|
|
await sio.save_session(sid, {"room": sid}) |
|
|
await sio.enter_room(sid, sid) |
|
|
print(f"Client connected: {sid}") |
|
|
|
|
|
@sio.event |
|
|
async def disconnect(sid): |
|
|
print(f"Client disconnected: {sid}") |
|
|
|