dumper / app.py
Fred808's picture
Update app.py
f69e3cd verified
import os
import asyncio
import tempfile
import shutil
import time
from datetime import datetime, timedelta
from typing import Dict, List, Optional
from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.responses import FileResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from torrentp import TorrentDownloader
app = FastAPI(title="Torrent Downloader Service", version="1.0.0")
# Enable CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Data models
class DownloadRequest(BaseModel):
magnet_link: str
class DownloadResponse(BaseModel):
download_id: str
status: str
class StatusResponse(BaseModel):
status: str
progress: Optional[int] = None
files: Optional[List[str]] = None
message: Optional[str] = None
class FileInfo(BaseModel):
filename: str
size: int
path: str
class FilesResponse(BaseModel):
files: List[FileInfo]
# Global storage
download_status: Dict[str, Dict] = {}
download_files: Dict[str, Dict] = {}
def cleanup_old_files():
"""Clean up files older than 1 hour"""
current_time = datetime.now()
to_remove = []
for download_id, info in download_files.items():
if current_time - info['created'] > timedelta(hours=1):
try:
if os.path.exists(info['path']):
if os.path.isdir(info['path']):
shutil.rmtree(info['path'])
else:
os.remove(info['path'])
to_remove.append(download_id)
except Exception as e:
print(f"Error cleaning up {info['path']}: {e}")
for download_id in to_remove:
download_files.pop(download_id, None)
download_status.pop(download_id, None)
async def download_torrent_async(magnet_link: str, download_id: str, temp_dir: str):
"""Async function to download torrent"""
try:
download_status[download_id] = {"status": "downloading", "progress": 0}
# Initialize TorrentDownloader
torrent_downloader = TorrentDownloader(magnet_link, temp_dir, stop_after_download=True)
await torrent_downloader.start_download()
# Find downloaded files
downloaded_files = []
for root, dirs, files in os.walk(temp_dir):
for file in files:
file_path = os.path.join(root, file)
downloaded_files.append(file_path)
if downloaded_files:
download_status[download_id] = {"status": "completed", "files": downloaded_files}
# Store file info with creation time for cleanup
download_files[download_id] = {
"path": temp_dir,
"files": downloaded_files,
"created": datetime.now()
}
else:
download_status[download_id] = {"status": "error", "message": "No files downloaded"}
except Exception as e:
download_status[download_id] = {"status": "error", "message": str(e)}
@app.post("/download", response_model=DownloadResponse)
async def start_download(request: DownloadRequest, background_tasks: BackgroundTasks):
"""Start a torrent download"""
cleanup_old_files() # Clean up old files before starting new download
magnet_link = request.magnet_link
download_id = str(int(time.time() * 1000)) # Use timestamp as ID
# Create temporary directory for this download
temp_dir = tempfile.mkdtemp(prefix=f"torrent_{download_id}_")
# Start download in background
background_tasks.add_task(download_torrent_async, magnet_link, download_id, temp_dir)
download_status[download_id] = {"status": "starting"}
return DownloadResponse(download_id=download_id, status="started")
@app.get("/status/{download_id}", response_model=StatusResponse)
async def get_status(download_id: str):
"""Get download status"""
if download_id not in download_status:
raise HTTPException(status_code=404, detail="Download ID not found")
status_data = download_status[download_id]
return StatusResponse(**status_data)
@app.get("/files/{download_id}", response_model=FilesResponse)
async def list_files(download_id: str):
"""List downloaded files"""
if download_id not in download_files:
raise HTTPException(status_code=404, detail="Download ID not found or no files available")
files_info = []
for file_path in download_files[download_id]["files"]:
if os.path.exists(file_path):
files_info.append(FileInfo(
filename=os.path.basename(file_path),
size=os.path.getsize(file_path),
path=file_path
))
return FilesResponse(files=files_info)
@app.get("/download-file/{download_id}/{filename}")
async def download_file(download_id: str, filename: str):
"""Download a specific file"""
if download_id not in download_files:
raise HTTPException(status_code=404, detail="Download ID not found")
# Find the file
target_file = None
for file_path in download_files[download_id]["files"]:
if os.path.basename(file_path) == filename:
target_file = file_path
break
if not target_file or not os.path.exists(target_file):
raise HTTPException(status_code=404, detail="File not found")
return FileResponse(
path=target_file,
filename=filename,
media_type='application/octet-stream'
)
@app.delete("/cleanup/{download_id}")
async def cleanup_download(download_id: str):
"""Manually cleanup a download"""
if download_id in download_files:
try:
temp_dir = download_files[download_id]["path"]
if os.path.exists(temp_dir):
shutil.rmtree(temp_dir)
download_files.pop(download_id, None)
download_status.pop(download_id, None)
return {"message": "Cleanup successful"}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Cleanup failed: {str(e)}")
else:
raise HTTPException(status_code=404, detail="Download ID not found")
@app.get("/")
async def root():
"""Root endpoint with service info"""
return {
"service": "Torrent Downloader Service",
"version": "1.0.0",
"endpoints": {
"POST /download": "Start a torrent download",
"GET /status/{download_id}": "Check download status",
"GET /files/{download_id}": "List downloaded files",
"GET /download-file/{download_id}/{filename}": "Download a specific file",
"DELETE /cleanup/{download_id}": "Clean up a download",
"GET /docs": "API documentation"
}
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)