File size: 7,314 Bytes
a84f31a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
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
# Create Socket.IO async server with ASGI mode
sio = socketio.AsyncServer(async_mode='asgi')
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="your-secret-key")
# Mount the Socket.IO app with FastAPI
socket_app = socketio.ASGIApp(sio, other_asgi_app=app)
# Temporary directory for uploaded files
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(...)):
# Create or retrieve a unique session ID for the user (used for Socket.IO isolation)
session_id = request.session.get("id")
if not session_id:
session_id = str(uuid.uuid4())
request.session["id"] = session_id
# Generate a unique file ID and save the file in chunks
file_id = str(uuid.uuid4())
file_path = UPLOAD_DIR / file_id
CHUNK_SIZE = 1024 * 1024 # 1 MB
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)
# Construct the download URL (using base URL and file_id)
base_url = request.url_for("index").rstrip("/")
download_url = f"{base_url}/download/{file_id}"
# Emit completion event with the download URL
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):
# Use the socket id as the room identifier
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}")
|