THED1 commited on
Commit
1c306ae
·
verified ·
1 Parent(s): ea14154

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +44 -48
app.py CHANGED
@@ -22,9 +22,7 @@ app.add_middleware(
22
  allow_headers=["*"],
23
  )
24
 
25
- # tasks = { "task_id": { ... } }
26
  tasks = {}
27
-
28
  DATA_DIR = "/data"
29
  os.makedirs(DATA_DIR, exist_ok=True)
30
 
@@ -38,16 +36,14 @@ async def cleanup_old_files():
38
  for filename in os.listdir(DATA_DIR):
39
  filepath = os.path.join(DATA_DIR, filename)
40
  if os.path.isfile(filepath):
41
- # 24 hours = 86400 seconds
42
  if now - os.path.getmtime(filepath) > 86400:
43
  os.remove(filepath)
44
- # Remove from tasks if exists
45
  for tid, tinfo in list(tasks.items()):
46
  if tinfo.get("file_path") == filepath:
47
  del tasks[tid]
48
  except Exception as e:
49
  print(f"Cleanup error: {e}")
50
- await asyncio.sleep(3600) # Check every hour
51
 
52
  @app.on_event("startup")
53
  async def startup_event():
@@ -55,9 +51,7 @@ async def startup_event():
55
 
56
  async def download_file(task_id: str, url: str):
57
  file_path = tasks[task_id]["file_path"]
58
-
59
  try:
60
- # read_bufsize chota rakha hai taaki network cache RAM block na kare
61
  connector = aiohttp.TCPConnector(limit=10)
62
  async with aiohttp.ClientSession(connector=connector, read_bufsize=256 * 1024) as session:
63
  async with session.get(url, timeout=None) as response:
@@ -65,10 +59,8 @@ async def download_file(task_id: str, url: str):
65
  total_size = int(response.headers.get('Content-Length', 0))
66
  tasks[task_id]["total_size"] = total_size
67
 
68
- # Check for Content-Disposition header for filename
69
  cd = response.headers.get('Content-Disposition')
70
  if cd and 'filename=' in cd:
71
- # Extract filename from header
72
  fname = re.findall('filename="([^"]+)"', cd)
73
  if not fname:
74
  fname = re.findall('filename=([^;]+)', cd)
@@ -86,7 +78,6 @@ async def download_file(task_id: str, url: str):
86
  chunk_counter = 0
87
 
88
  with open(file_path, 'wb') as f:
89
- # Iter_chunked strictly allocated chunks ko read karega
90
  async for chunk in response.content.iter_chunked(256 * 1024):
91
  if not chunk:
92
  break
@@ -102,13 +93,11 @@ async def download_file(task_id: str, url: str):
102
  last_downloaded = downloaded
103
 
104
  f.flush()
105
-
106
- # Har thodi der mein file data disk par push karke RAM se clear karna
107
  chunk_counter += 1
108
  if chunk_counter % 20 == 0:
109
  os.fsync(f.fileno())
110
  del chunk
111
- gc.collect() # Force fully garbage clean
112
 
113
  tasks[task_id]["status"] = "completed"
114
  tasks[task_id]["speed"] = 0.0
@@ -121,8 +110,6 @@ async def download_file(task_id: str, url: str):
121
  @app.post("/start_download")
122
  async def start_download(req: DownloadRequest, background_tasks: BackgroundTasks):
123
  task_id = str(uuid.uuid4())
124
-
125
- # Try to extract filename from URL
126
  filename = req.url.split("/")[-1]
127
  if "?" in filename:
128
  filename = filename.split("?")[0]
@@ -140,7 +127,6 @@ async def start_download(req: DownloadRequest, background_tasks: BackgroundTasks
140
  "original_filename": filename,
141
  "timestamp": time.time()
142
  }
143
-
144
  background_tasks.add_task(download_file, task_id, req.url)
145
  return {"task_id": task_id}
146
 
@@ -154,11 +140,7 @@ async def get_status(task_id: str):
154
  async def download(task_id: str):
155
  if task_id not in tasks or tasks[task_id]["status"] != "completed":
156
  return {"error": "File not ready"}
157
-
158
- file_path = tasks[task_id]["file_path"]
159
- filename = tasks[task_id].get("original_filename", f"downloaded_{task_id}.bin")
160
-
161
- return FileResponse(file_path, filename=filename)
162
 
163
  @app.get("/stream/{task_id}")
164
  async def stream(task_id: str, request: Request):
@@ -167,8 +149,8 @@ async def stream(task_id: str, request: Request):
167
 
168
  file_path = tasks[task_id]["file_path"]
169
  file_size = os.path.getsize(file_path)
170
-
171
  range_header = request.headers.get("Range")
 
172
  if range_header:
173
  byte1, byte2 = 0, None
174
  match = range_header.replace("bytes=", "").split("-")
@@ -184,7 +166,7 @@ async def stream(task_id: str, request: Request):
184
  def file_iterator(start, length):
185
  with open(file_path, "rb") as f:
186
  f.seek(start)
187
- chunk_size = 1024 * 1024
188
  while length > 0:
189
  read_size = min(chunk_size, length)
190
  data = f.read(read_size)
@@ -192,6 +174,7 @@ async def stream(task_id: str, request: Request):
192
  break
193
  yield data
194
  length -= len(data)
 
195
 
196
  headers = {
197
  "Content-Range": f"bytes {byte1}-{byte1+length-1}/{file_size}",
@@ -214,6 +197,8 @@ async def delete_all():
214
  try:
215
  if os.path.isfile(filepath):
216
  os.remove(filepath)
 
 
217
  except Exception as e:
218
  print(f"Failed to delete {filepath}: {e}")
219
  tasks.clear()
@@ -224,20 +209,24 @@ async def delete_all():
224
  async def get_history():
225
  return {"history": list(tasks.values())}
226
 
227
-
228
  @app.post("/upload")
229
  async def upload_file(file: UploadFile = File(...)):
230
  task_id = str(uuid.uuid4())
231
  original_filename = file.filename if file.filename else "uploaded_file.bin"
232
  file_path = os.path.join(DATA_DIR, f"{task_id}_{original_filename}")
233
 
 
234
  with open(file_path, "wb") as buffer:
235
- while chunk := await file.read(512 * 1024):
 
 
 
236
  buffer.write(chunk)
237
  buffer.flush()
238
  os.fsync(buffer.fileno())
239
  del chunk
240
-
 
241
  file_size = os.path.getsize(file_path)
242
 
243
  tasks[task_id] = {
@@ -254,7 +243,6 @@ async def upload_file(file: UploadFile = File(...)):
254
  gc.collect()
255
  return {"task_id": task_id}
256
 
257
-
258
  @app.post("/start_gofile_transfer")
259
  async def start_gofile_transfer(req: DownloadRequest, background_tasks: BackgroundTasks):
260
  task_id = str(uuid.uuid4())
@@ -279,10 +267,20 @@ async def start_gofile_transfer(req: DownloadRequest, background_tasks: Backgrou
279
  background_tasks.add_task(process_gofile_transfer, task_id, req.url)
280
  return {"task_id": task_id}
281
 
 
 
 
 
 
 
 
 
 
 
282
  async def process_gofile_transfer(task_id: str, url: str):
283
  file_path = tasks[task_id]["file_path"]
284
  try:
285
- # 1. Download File with memory lock
286
  connector = aiohttp.TCPConnector(limit=10)
287
  async with aiohttp.ClientSession(connector=connector, read_bufsize=256 * 1024) as session:
288
  async with session.get(url, timeout=None) as response:
@@ -329,31 +327,29 @@ async def process_gofile_transfer(task_id: str, url: str):
329
  del chunk
330
  gc.collect()
331
 
332
- # 2. Upload to GoFile
333
  tasks[task_id]["status"] = "uploading_to_gofile"
334
  tasks[task_id]["speed"] = 0.0
335
 
336
  async with aiohttp.ClientSession() as session:
337
- # Get available server
338
  async with session.get("https://api.gofile.io/servers") as resp:
339
  servers_data = await resp.json()
340
  server = servers_data["data"]["servers"][0]["name"]
341
 
342
  upload_url = f"https://{server}.gofile.io/uploadFile"
343
 
 
344
  data = aiohttp.FormData()
345
- with open(file_path, 'rb') as f_upload:
346
- data.add_field('file', f_upload, filename=tasks[task_id]["original_filename"])
347
 
348
- async with session.post(upload_url, data=data) as upload_resp:
349
- upload_result = await upload_resp.json()
350
- if upload_result["status"] == "ok":
351
- tasks[task_id]["gofile_url"] = upload_result["data"]["downloadPage"]
352
- tasks[task_id]["status"] = "completed"
353
- else:
354
- raise Exception("GoFile upload failed")
355
 
356
- # Local cache file clean up after completion
357
  try:
358
  os.remove(file_path)
359
  except:
@@ -369,7 +365,6 @@ async def process_gofile_transfer(task_id: str, url: str):
369
 
370
  @app.get("/list_zip/{task_id}")
371
  async def list_zip_contents(task_id: str):
372
- """Bina extract kiye ZIP ke andar ki saari files ki list dikhayega"""
373
  if task_id not in tasks:
374
  return {"error": "Task not found"}
375
 
@@ -395,7 +390,6 @@ async def list_zip_contents(task_id: str):
395
 
396
  @app.post("/extract_zip/{task_id}")
397
  async def extract_zip_file(task_id: str, background_tasks: BackgroundTasks):
398
- """Background mein ZIP file ko extract karne ka process shuru karega"""
399
  if task_id not in tasks:
400
  return {"error": "Task not found"}
401
 
@@ -406,22 +400,26 @@ async def extract_zip_file(task_id: str, background_tasks: BackgroundTasks):
406
  if not zipfile.is_zipfile(file_path):
407
  return {"error": "This file is not a valid ZIP archive"}
408
 
409
- # Extraction folder name (Task ID ke sath folder banega jisse files mix na hon)
410
  extract_folder = os.path.join(DATA_DIR, f"extracted_{task_id}")
411
  os.makedirs(extract_folder, exist_ok=True)
412
 
413
  tasks[task_id]["status"] = "extracting"
414
  tasks[task_id]["extract_path"] = extract_folder
415
 
416
- # Background mein execution taaki API request time out na ho aur UI freeze na ho
417
  background_tasks.add_task(process_zip_extraction, task_id, file_path, extract_folder)
418
  return {"status": "extraction_started", "task_id": task_id, "target_folder": extract_folder}
419
 
420
  def process_zip_extraction(task_id: str, zip_path: str, target_dir: str):
421
- """Background extraction worker with automatic memory release"""
422
  try:
423
  with zipfile.ZipFile(zip_path, 'r') as z:
424
- z.extractall(path=target_dir)
 
 
 
 
 
 
425
  tasks[task_id]["status"] = "extracted"
426
  except Exception as e:
427
  tasks[task_id]["status"] = "extraction_error"
@@ -429,8 +427,6 @@ def process_zip_extraction(task_id: str, zip_path: str, target_dir: str):
429
  finally:
430
  gc.collect()
431
 
432
- # =====================================================================
433
-
434
  if __name__ == "__main__":
435
  import uvicorn
436
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
22
  allow_headers=["*"],
23
  )
24
 
 
25
  tasks = {}
 
26
  DATA_DIR = "/data"
27
  os.makedirs(DATA_DIR, exist_ok=True)
28
 
 
36
  for filename in os.listdir(DATA_DIR):
37
  filepath = os.path.join(DATA_DIR, filename)
38
  if os.path.isfile(filepath):
 
39
  if now - os.path.getmtime(filepath) > 86400:
40
  os.remove(filepath)
 
41
  for tid, tinfo in list(tasks.items()):
42
  if tinfo.get("file_path") == filepath:
43
  del tasks[tid]
44
  except Exception as e:
45
  print(f"Cleanup error: {e}")
46
+ await asyncio.sleep(3600)
47
 
48
  @app.on_event("startup")
49
  async def startup_event():
 
51
 
52
  async def download_file(task_id: str, url: str):
53
  file_path = tasks[task_id]["file_path"]
 
54
  try:
 
55
  connector = aiohttp.TCPConnector(limit=10)
56
  async with aiohttp.ClientSession(connector=connector, read_bufsize=256 * 1024) as session:
57
  async with session.get(url, timeout=None) as response:
 
59
  total_size = int(response.headers.get('Content-Length', 0))
60
  tasks[task_id]["total_size"] = total_size
61
 
 
62
  cd = response.headers.get('Content-Disposition')
63
  if cd and 'filename=' in cd:
 
64
  fname = re.findall('filename="([^"]+)"', cd)
65
  if not fname:
66
  fname = re.findall('filename=([^;]+)', cd)
 
78
  chunk_counter = 0
79
 
80
  with open(file_path, 'wb') as f:
 
81
  async for chunk in response.content.iter_chunked(256 * 1024):
82
  if not chunk:
83
  break
 
93
  last_downloaded = downloaded
94
 
95
  f.flush()
 
 
96
  chunk_counter += 1
97
  if chunk_counter % 20 == 0:
98
  os.fsync(f.fileno())
99
  del chunk
100
+ gc.collect()
101
 
102
  tasks[task_id]["status"] = "completed"
103
  tasks[task_id]["speed"] = 0.0
 
110
  @app.post("/start_download")
111
  async def start_download(req: DownloadRequest, background_tasks: BackgroundTasks):
112
  task_id = str(uuid.uuid4())
 
 
113
  filename = req.url.split("/")[-1]
114
  if "?" in filename:
115
  filename = filename.split("?")[0]
 
127
  "original_filename": filename,
128
  "timestamp": time.time()
129
  }
 
130
  background_tasks.add_task(download_file, task_id, req.url)
131
  return {"task_id": task_id}
132
 
 
140
  async def download(task_id: str):
141
  if task_id not in tasks or tasks[task_id]["status"] != "completed":
142
  return {"error": "File not ready"}
143
+ return FileResponse(tasks[task_id]["file_path"], filename=tasks[task_id].get("original_filename"))
 
 
 
 
144
 
145
  @app.get("/stream/{task_id}")
146
  async def stream(task_id: str, request: Request):
 
149
 
150
  file_path = tasks[task_id]["file_path"]
151
  file_size = os.path.getsize(file_path)
 
152
  range_header = request.headers.get("Range")
153
+
154
  if range_header:
155
  byte1, byte2 = 0, None
156
  match = range_header.replace("bytes=", "").split("-")
 
166
  def file_iterator(start, length):
167
  with open(file_path, "rb") as f:
168
  f.seek(start)
169
+ chunk_size = 512 * 1024 # Reduced chunk size for memory safety
170
  while length > 0:
171
  read_size = min(chunk_size, length)
172
  data = f.read(read_size)
 
174
  break
175
  yield data
176
  length -= len(data)
177
+ del data
178
 
179
  headers = {
180
  "Content-Range": f"bytes {byte1}-{byte1+length-1}/{file_size}",
 
197
  try:
198
  if os.path.isfile(filepath):
199
  os.remove(filepath)
200
+ elif os.path.isdir(filepath):
201
+ shutil.rmtree(filepath)
202
  except Exception as e:
203
  print(f"Failed to delete {filepath}: {e}")
204
  tasks.clear()
 
209
  async def get_history():
210
  return {"history": list(tasks.values())}
211
 
 
212
  @app.post("/upload")
213
  async def upload_file(file: UploadFile = File(...)):
214
  task_id = str(uuid.uuid4())
215
  original_filename = file.filename if file.filename else "uploaded_file.bin"
216
  file_path = os.path.join(DATA_DIR, f"{task_id}_{original_filename}")
217
 
218
+ # Using chunked reading directly from the file stream to save memory
219
  with open(file_path, "wb") as buffer:
220
+ while True:
221
+ chunk = await file.read(256 * 1024)
222
+ if not chunk:
223
+ break
224
  buffer.write(chunk)
225
  buffer.flush()
226
  os.fsync(buffer.fileno())
227
  del chunk
228
+
229
+ await file.close() # Strictly close file to free memory
230
  file_size = os.path.getsize(file_path)
231
 
232
  tasks[task_id] = {
 
243
  gc.collect()
244
  return {"task_id": task_id}
245
 
 
246
  @app.post("/start_gofile_transfer")
247
  async def start_gofile_transfer(req: DownloadRequest, background_tasks: BackgroundTasks):
248
  task_id = str(uuid.uuid4())
 
267
  background_tasks.add_task(process_gofile_transfer, task_id, req.url)
268
  return {"task_id": task_id}
269
 
270
+ # Chunked file reader generator to avoid streaming upload memory spikes
271
+ async def file_sender(file_path, chunk_size=256 * 1024):
272
+ with open(file_path, 'rb') as f:
273
+ while True:
274
+ chunk = f.read(chunk_size)
275
+ if not chunk:
276
+ break
277
+ yield chunk
278
+ del chunk
279
+
280
  async def process_gofile_transfer(task_id: str, url: str):
281
  file_path = tasks[task_id]["file_path"]
282
  try:
283
+ # 1. Download Setup
284
  connector = aiohttp.TCPConnector(limit=10)
285
  async with aiohttp.ClientSession(connector=connector, read_bufsize=256 * 1024) as session:
286
  async with session.get(url, timeout=None) as response:
 
327
  del chunk
328
  gc.collect()
329
 
330
+ # 2. Upload to GoFile (STREAMING JUGAD WITH ZERO RAM IMPRINT)
331
  tasks[task_id]["status"] = "uploading_to_gofile"
332
  tasks[task_id]["speed"] = 0.0
333
 
334
  async with aiohttp.ClientSession() as session:
 
335
  async with session.get("https://api.gofile.io/servers") as resp:
336
  servers_data = await resp.json()
337
  server = servers_data["data"]["servers"][0]["name"]
338
 
339
  upload_url = f"https://{server}.gofile.io/uploadFile"
340
 
341
+ # Form data wrapping without buffering entire file array into memory
342
  data = aiohttp.FormData()
343
+ data.add_field('file', file_sender(file_path), filename=tasks[task_id]["original_filename"])
 
344
 
345
+ async with session.post(upload_url, data=data) as upload_resp:
346
+ upload_result = await upload_resp.json()
347
+ if upload_result["status"] == "ok":
348
+ tasks[task_id]["gofile_url"] = upload_result["data"]["downloadPage"]
349
+ tasks[task_id]["status"] = "completed"
350
+ else:
351
+ raise Exception("GoFile upload failed")
352
 
 
353
  try:
354
  os.remove(file_path)
355
  except:
 
365
 
366
  @app.get("/list_zip/{task_id}")
367
  async def list_zip_contents(task_id: str):
 
368
  if task_id not in tasks:
369
  return {"error": "Task not found"}
370
 
 
390
 
391
  @app.post("/extract_zip/{task_id}")
392
  async def extract_zip_file(task_id: str, background_tasks: BackgroundTasks):
 
393
  if task_id not in tasks:
394
  return {"error": "Task not found"}
395
 
 
400
  if not zipfile.is_zipfile(file_path):
401
  return {"error": "This file is not a valid ZIP archive"}
402
 
 
403
  extract_folder = os.path.join(DATA_DIR, f"extracted_{task_id}")
404
  os.makedirs(extract_folder, exist_ok=True)
405
 
406
  tasks[task_id]["status"] = "extracting"
407
  tasks[task_id]["extract_path"] = extract_folder
408
 
 
409
  background_tasks.add_task(process_zip_extraction, task_id, file_path, extract_folder)
410
  return {"status": "extraction_started", "task_id": task_id, "target_folder": extract_folder}
411
 
412
  def process_zip_extraction(task_id: str, zip_path: str, target_dir: str):
413
+ """Memory safe extraction processing member by member loop"""
414
  try:
415
  with zipfile.ZipFile(zip_path, 'r') as z:
416
+ file_counter = 0
417
+ for member in z.namelist():
418
+ z.extract(member, path=target_dir)
419
+ file_counter += 1
420
+ # Release lock periodically inside loops
421
+ if file_counter % 50 == 0:
422
+ gc.collect()
423
  tasks[task_id]["status"] = "extracted"
424
  except Exception as e:
425
  tasks[task_id]["status"] = "extraction_error"
 
427
  finally:
428
  gc.collect()
429
 
 
 
430
  if __name__ == "__main__":
431
  import uvicorn
432
  uvicorn.run(app, host="0.0.0.0", port=7860)