File size: 7,012 Bytes
6b6503b ec48a63 f69e3cd ec48a63 c0223a0 f69e3cd c0223a0 f69e3cd ec48a63 f69e3cd ec48a63 f69e3cd ec48a63 f69e3cd ec48a63 f69e3cd ec48a63 f69e3cd b9d8908 ec48a63 f69e3cd c0223a0 |
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 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
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)
|