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)