THED2 commited on
Commit
ec95e06
·
verified ·
1 Parent(s): 45d6603

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +441 -0
app.py ADDED
@@ -0,0 +1,441 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import os
3
+ import re
4
+ import time
5
+ import uuid
6
+ import shutil
7
+ import aiohttp
8
+ import gc # Memory management ke liye garbage collector
9
+ import zipfile # ZIP file handle karne ke liye in-built library
10
+ import urllib3 # Zero-buffer strict low RAM downloding ke liye
11
+ from fastapi import FastAPI, BackgroundTasks, Request, File, UploadFile
12
+ from fastapi.middleware.cors import CORSMiddleware
13
+ from fastapi.responses import FileResponse, StreamingResponse
14
+ from pydantic import BaseModel
15
+
16
+ app = FastAPI()
17
+
18
+ app.add_middleware(
19
+ CORSMiddleware,
20
+ allow_origins=["*"],
21
+ allow_credentials=True,
22
+ allow_methods=["*"],
23
+ allow_headers=["*"],
24
+ )
25
+
26
+ tasks = {}
27
+ DATA_DIR = "/data"
28
+ os.makedirs(DATA_DIR, exist_ok=True)
29
+
30
+ class DownloadRequest(BaseModel):
31
+ url: str
32
+
33
+ def clean_filename(url: str) -> str:
34
+ """URL se sahi extension aur filename nikalne ka jugaad"""
35
+ path = url.split("?")[0]
36
+ filename = path.split("/")[-1]
37
+ if not filename or "." not in filename:
38
+ return "downloaded_file.bin"
39
+ return filename
40
+
41
+ async def cleanup_old_files():
42
+ while True:
43
+ try:
44
+ now = time.time()
45
+ for filename in os.listdir(DATA_DIR):
46
+ filepath = os.path.join(DATA_DIR, filename)
47
+ if os.path.isfile(filepath):
48
+ if now - os.path.getmtime(filepath) > 86400:
49
+ os.remove(filepath)
50
+ for tid, tinfo in list(tasks.items()):
51
+ if tinfo.get("file_path") == filepath:
52
+ del tasks[tid]
53
+ except Exception as e:
54
+ print(f"Cleanup error: {e}")
55
+ await asyncio.sleep(3600)
56
+
57
+ @app.on_event("startup")
58
+ async def startup_event():
59
+ asyncio.create_task(cleanup_old_files())
60
+
61
+ def sync_download_worker(task_id: str, url: str):
62
+ """Urllib3 pool se strict raw streaming taaki RAM me cache accumulate na ho"""
63
+ file_path = tasks[task_id]["file_path"]
64
+ try:
65
+ http = urllib3.PoolManager(block=True, maxsize=1)
66
+ response = http.request('GET', url, preload_content=False, timeout=None)
67
+
68
+ total_size = int(response.headers.get('Content-Length', 0))
69
+ tasks[task_id]["total_size"] = total_size
70
+
71
+ # Proper filename aur extension extraction (.apk wagera)
72
+ cd = response.headers.get('Content-Disposition')
73
+ if cd and 'filename=' in cd:
74
+ fname = re.findall('filename="([^"]+)"', cd)
75
+ if not fname:
76
+ fname = re.findall('filename=([^;]+)', cd)
77
+ if fname:
78
+ new_filename = fname[0]
79
+ file_path = os.path.join(DATA_DIR, f"{task_id}_{new_filename}")
80
+ tasks[task_id]["file_path"] = file_path
81
+ tasks[task_id]["original_filename"] = new_filename
82
+
83
+ downloaded = 0
84
+ start_time = time.time()
85
+ last_time = start_time
86
+ last_downloaded = 0
87
+ chunk_counter = 0
88
+
89
+ with open(file_path, 'wb') as f:
90
+ # 64KB strict buffer chunking, RAM me data holds hi nahi hoga
91
+ for chunk in response.stream(64 * 1024):
92
+ if not chunk:
93
+ break
94
+ f.write(chunk)
95
+ downloaded += len(chunk)
96
+ tasks[task_id]["downloaded"] = downloaded
97
+
98
+ current_time = time.time()
99
+ if current_time - last_time >= 1.0:
100
+ speed = (downloaded - last_downloaded) / (current_time - last_time)
101
+ tasks[task_id]["speed"] = speed
102
+ last_time = current_time
103
+ last_downloaded = downloaded
104
+
105
+ f.flush()
106
+ chunk_counter += 1
107
+ if chunk_counter % 10 == 0:
108
+ os.fsync(f.fileno())
109
+ del chunk
110
+ gc.collect() # Strict cleanup inside raw sync thread
111
+
112
+ response.release_conn()
113
+ tasks[task_id]["status"] = "completed"
114
+ tasks[task_id]["speed"] = 0.0
115
+ except Exception as e:
116
+ tasks[task_id]["status"] = "error"
117
+ tasks[task_id]["error"] = str(e)
118
+ finally:
119
+ gc.collect()
120
+
121
+ async def download_file(task_id: str, url: str):
122
+ # Loop block bypass karne ke liye execution ko framework thread pool me run karenge
123
+ loop = asyncio.get_event_loop()
124
+ await loop.run_in_executor(None, sync_download_worker, task_id, url)
125
+
126
+ @app.post("/start_download")
127
+ async def start_download(req: DownloadRequest, background_tasks: BackgroundTasks):
128
+ task_id = str(uuid.uuid4())
129
+ filename = clean_filename(req.url)
130
+
131
+ tasks[task_id] = {
132
+ "task_id": task_id,
133
+ "url": req.url,
134
+ "status": "downloading",
135
+ "total_size": 0,
136
+ "downloaded": 0,
137
+ "speed": 0.0,
138
+ "file_path": os.path.join(DATA_DIR, f"{task_id}_{filename}"),
139
+ "original_filename": filename,
140
+ "timestamp": time.time()
141
+ }
142
+ background_tasks.add_task(download_file, task_id, req.url)
143
+ return {"task_id": task_id}
144
+
145
+ @app.get("/status/{task_id}")
146
+ async def get_status(task_id: str):
147
+ if task_id not in tasks:
148
+ return {"error": "Task not found"}
149
+ return tasks[task_id]
150
+
151
+ @app.get("/download/{task_id}")
152
+ async def download(task_id: str):
153
+ if task_id not in tasks or tasks[task_id]["status"] != "completed":
154
+ return {"error": "File not ready"}
155
+ return FileResponse(tasks[task_id]["file_path"], filename=tasks[task_id].get("original_filename"))
156
+
157
+ @app.get("/stream/{task_id}")
158
+ async def stream(task_id: str, request: Request):
159
+ if task_id not in tasks or tasks[task_id]["status"] != "completed":
160
+ return {"error": "File not ready"}
161
+
162
+ file_path = tasks[task_id]["file_path"]
163
+ file_size = os.path.getsize(file_path)
164
+ range_header = request.headers.get("Range")
165
+
166
+ if range_header:
167
+ byte1, byte2 = 0, None
168
+ match = range_header.replace("bytes=", "").split("-")
169
+ if match[0]:
170
+ byte1 = int(match[0])
171
+ if len(match) > 1 and match[1]:
172
+ byte2 = int(match[1])
173
+
174
+ length = file_size - byte1
175
+ if byte2 is not None:
176
+ length = byte2 + 1 - byte1
177
+
178
+ def file_iterator(start, length):
179
+ with open(file_path, "rb") as f:
180
+ f.seek(start)
181
+ chunk_size = 64 * 1024
182
+ while length > 0:
183
+ read_size = min(chunk_size, length)
184
+ data = f.read(read_size)
185
+ if not data:
186
+ break
187
+ yield data
188
+ length -= len(data)
189
+ del data
190
+ gc.collect()
191
+
192
+ headers = {
193
+ "Content-Range": f"bytes {byte1}-{byte1+length-1}/{file_size}",
194
+ "Accept-Ranges": "bytes",
195
+ "Content-Length": str(length),
196
+ }
197
+ return StreamingResponse(file_iterator(byte1, length), status_code=206, headers=headers)
198
+ else:
199
+ return FileResponse(file_path)
200
+
201
+ @app.get("/storage")
202
+ async def get_storage():
203
+ total, used, free = shutil.disk_usage(DATA_DIR)
204
+ return {"total": total, "used": used, "free": free}
205
+
206
+ @app.post("/delete_all")
207
+ async def delete_all():
208
+ for filename in os.listdir(DATA_DIR):
209
+ filepath = os.path.join(DATA_DIR, filename)
210
+ try:
211
+ if os.path.isfile(filepath):
212
+ os.remove(filepath)
213
+ elif os.path.isdir(filepath):
214
+ shutil.rmtree(filepath)
215
+ except Exception as e:
216
+ print(f"Failed to delete {filepath}: {e}")
217
+ tasks.clear()
218
+ gc.collect()
219
+ return {"status": "success"}
220
+
221
+ @app.get("/history")
222
+ async def get_history():
223
+ return {"history": list(tasks.values())}
224
+
225
+ @app.post("/upload")
226
+ async def upload_file(file: UploadFile = File(...)):
227
+ task_id = str(uuid.uuid4())
228
+ original_filename = file.filename if file.filename else "uploaded_file.bin"
229
+ file_path = os.path.join(DATA_DIR, f"{task_id}_{original_filename}")
230
+
231
+ with open(file_path, "wb") as buffer:
232
+ while True:
233
+ chunk = await file.read(64 * 1024)
234
+ if not chunk:
235
+ break
236
+ buffer.write(chunk)
237
+ buffer.flush()
238
+ os.fsync(buffer.fileno())
239
+ del chunk
240
+ gc.collect()
241
+
242
+ await file.close()
243
+ file_size = os.path.getsize(file_path)
244
+
245
+ tasks[task_id] = {
246
+ "task_id": task_id,
247
+ "url": "local_upload",
248
+ "status": "completed",
249
+ "total_size": file_size,
250
+ "downloaded": file_size,
251
+ "speed": 0.0,
252
+ "file_path": file_path,
253
+ "original_filename": original_filename,
254
+ "timestamp": time.time()
255
+ }
256
+ gc.collect()
257
+ return {"task_id": task_id}
258
+
259
+ @app.post("/start_gofile_transfer")
260
+ async def start_gofile_transfer(req: DownloadRequest, background_tasks: BackgroundTasks):
261
+ task_id = str(uuid.uuid4())
262
+ filename = clean_filename(req.url)
263
+
264
+ tasks[task_id] = {
265
+ "task_id": task_id,
266
+ "url": req.url,
267
+ "status": "downloading",
268
+ "total_size": 0,
269
+ "downloaded": 0,
270
+ "speed": 0.0,
271
+ "file_path": os.path.join(DATA_DIR, f"{task_id}_{filename}"),
272
+ "original_filename": filename,
273
+ "gofile_url": None,
274
+ "timestamp": time.time()
275
+ }
276
+ background_tasks.add_task(process_gofile_transfer, task_id, req.url)
277
+ return {"task_id": task_id}
278
+
279
+ async def file_sender(file_path, chunk_size=64 * 1024):
280
+ with open(file_path, 'rb') as f:
281
+ while True:
282
+ chunk = f.read(chunk_size)
283
+ if not chunk:
284
+ break
285
+ yield chunk
286
+ del chunk
287
+ gc.collect()
288
+
289
+ def sync_gofile_download_part(task_id: str, url: str):
290
+ """GoFile processing ke liye bhi same raw low RAM wrapper"""
291
+ file_path = tasks[task_id]["file_path"]
292
+ http = urllib3.PoolManager(block=True, maxsize=1)
293
+ response = http.request('GET', url, preload_content=False, timeout=None)
294
+
295
+ total_size = int(response.headers.get('Content-Length', 0))
296
+ tasks[task_id]["total_size"] = total_size
297
+
298
+ cd = response.headers.get('Content-Disposition')
299
+ if cd and 'filename=' in cd:
300
+ fname = re.findall('filename="([^"]+)"', cd)
301
+ if not fname:
302
+ fname = re.findall('filename=([^;]+)', cd)
303
+ if fname:
304
+ new_filename = fname[0]
305
+ file_path = os.path.join(DATA_DIR, f"{task_id}_{new_filename}")
306
+ tasks[task_id]["file_path"] = file_path
307
+ tasks[task_id]["original_filename"] = new_filename
308
+
309
+ downloaded = 0
310
+ start_time = time.time()
311
+ last_time = start_time
312
+ last_downloaded = 0
313
+ chunk_counter = 0
314
+
315
+ with open(file_path, 'wb') as f:
316
+ for chunk in response.stream(64 * 1024):
317
+ if not chunk:
318
+ break
319
+ f.write(chunk)
320
+ downloaded += len(chunk)
321
+ tasks[task_id]["downloaded"] = downloaded
322
+
323
+ current_time = time.time()
324
+ if current_time - last_time >= 1.0:
325
+ tasks[task_id]["speed"] = (downloaded - last_downloaded) / (current_time - last_time)
326
+ last_time = current_time
327
+ last_downloaded = downloaded
328
+
329
+ f.flush()
330
+ chunk_counter += 1
331
+ if chunk_counter % 10 == 0:
332
+ os.fsync(f.fileno())
333
+ del chunk
334
+ gc.collect()
335
+ response.release_conn()
336
+
337
+ async def process_gofile_transfer(task_id: str, url: str):
338
+ try:
339
+ loop = asyncio.get_event_loop()
340
+ await loop.run_in_executor(None, sync_gofile_download_part, task_id, url)
341
+
342
+ file_path = tasks[task_id]["file_path"]
343
+ tasks[task_id]["status"] = "uploading_to_gofile"
344
+ tasks[task_id]["speed"] = 0.0
345
+
346
+ async with aiohttp.ClientSession() as session:
347
+ async with session.get("https://api.gofile.io/servers") as resp:
348
+ servers_data = await resp.json()
349
+ server = servers_data["data"]["servers"][0]["name"]
350
+
351
+ upload_url = f"https://{server}.gofile.io/uploadFile"
352
+
353
+ data = aiohttp.FormData()
354
+ data.add_field('file', file_sender(file_path), filename=tasks[task_id]["original_filename"])
355
+
356
+ async with session.post(upload_url, data=data) as upload_resp:
357
+ upload_result = await upload_resp.json()
358
+ if upload_result["status"] == "ok":
359
+ tasks[task_id]["gofile_url"] = upload_result["data"]["downloadPage"]
360
+ tasks[task_id]["status"] = "completed"
361
+ else:
362
+ raise Exception("GoFile upload failed")
363
+
364
+ try:
365
+ os.remove(file_path)
366
+ except:
367
+ pass
368
+
369
+ except Exception as e:
370
+ tasks[task_id]["status"] = "error"
371
+ tasks[task_id]["error"] = str(e)
372
+ finally:
373
+ gc.collect()
374
+
375
+ # ==================== ZIP MANAGEMENT FEATURES ====================
376
+
377
+ @app.get("/list_zip/{task_id}")
378
+ async def list_zip_contents(task_id: str):
379
+ if task_id not in tasks:
380
+ return {"error": "Task not found"}
381
+
382
+ file_path = tasks[task_id]["file_path"]
383
+ if not os.path.exists(file_path):
384
+ return {"error": "File does not exist on server"}
385
+
386
+ if not zipfile.is_zipfile(file_path):
387
+ return {"error": "This file is not a valid ZIP archive"}
388
+
389
+ try:
390
+ with zipfile.ZipFile(file_path, 'r') as z:
391
+ file_list = []
392
+ for info in z.infolist():
393
+ file_list.append({
394
+ "filename": info.filename,
395
+ "file_size_mb": round(info.file_size / (1024 * 1024), 2),
396
+ "is_dir": info.is_dir()
397
+ })
398
+ return {"task_id": task_id, "total_files": len(file_list), "files": file_list}
399
+ except Exception as e:
400
+ return {"error": f"Failed to read ZIP: {str(e)}"}
401
+
402
+ @app.post("/extract_zip/{task_id}")
403
+ async def extract_zip_file(task_id: str, background_tasks: BackgroundTasks):
404
+ if task_id not in tasks:
405
+ return {"error": "Task not found"}
406
+
407
+ file_path = tasks[task_id]["file_path"]
408
+ if not os.path.exists(file_path):
409
+ return {"error": "File does not exist on server"}
410
+
411
+ if not zipfile.is_zipfile(file_path):
412
+ return {"error": "This file is not a valid ZIP archive"}
413
+
414
+ extract_folder = os.path.join(DATA_DIR, f"extracted_{task_id}")
415
+ os.makedirs(extract_folder, exist_ok=True)
416
+
417
+ tasks[task_id]["status"] = "extracting"
418
+ tasks[task_id]["extract_path"] = extract_folder
419
+
420
+ background_tasks.add_task(process_zip_extraction, task_id, file_path, extract_folder)
421
+ return {"status": "extraction_started", "task_id": task_id, "target_folder": extract_folder}
422
+
423
+ def process_zip_extraction(task_id: str, zip_path: str, target_dir: str):
424
+ try:
425
+ with zipfile.ZipFile(zip_path, 'r') as z:
426
+ file_counter = 0
427
+ for member in z.namelist():
428
+ z.extract(member, path=target_dir)
429
+ file_counter += 1
430
+ if file_counter % 10 == 0:
431
+ gc.collect()
432
+ tasks[task_id]["status"] = "extracted"
433
+ except Exception as e:
434
+ tasks[task_id]["status"] = "extraction_error"
435
+ tasks[task_id]["error"] = str(e)
436
+ finally:
437
+ gc.collect()
438
+
439
+ if __name__ == "__main__":
440
+ import uvicorn
441
+ uvicorn.run(app, host="0.0.0.0", port=7860)