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}")