130 lines
5.1 KiB
Python
130 lines
5.1 KiB
Python
import logging
|
|
from typing import Dict, Any, List, Optional
|
|
import httpx
|
|
from .config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
STATUS_MAP = {
|
|
0: "일시정지됨",
|
|
1: "검사 대기",
|
|
2: "검사 중",
|
|
3: "다운로드 대기",
|
|
4: "다운로드 중",
|
|
5: "시딩 대기",
|
|
6: "시딩 중 (완료)",
|
|
}
|
|
|
|
class TransmissionClient:
|
|
def __init__(self):
|
|
self.session_id: str = ""
|
|
self.base_url = settings.rpc_url
|
|
self.auth = None
|
|
if settings.TRANSMISSION_USER and settings.TRANSMISSION_PASSWORD:
|
|
self.auth = (settings.TRANSMISSION_USER, settings.TRANSMISSION_PASSWORD)
|
|
|
|
async def _request(self, method: str, arguments: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
"""Transmission RPC 요청 전송 (세션 ID 자동 획득 및 409 처리)"""
|
|
payload = {"method": method}
|
|
if arguments:
|
|
payload["arguments"] = arguments
|
|
|
|
async with httpx.AsyncClient(timeout=10.0, auth=self.auth) as client:
|
|
headers = {}
|
|
if self.session_id:
|
|
headers["X-Transmission-Session-Id"] = self.session_id
|
|
|
|
# 첫 번째 요청
|
|
response = await client.post(self.base_url, json=payload, headers=headers)
|
|
|
|
# 409 Conflict: 세션 ID 갱신 후 재시도
|
|
if response.status_code == 409:
|
|
new_session_id = response.headers.get("X-Transmission-Session-Id")
|
|
if not new_session_id:
|
|
raise Exception("Transmission에서 X-Transmission-Session-Id 헤더를 받지 못했습니다.")
|
|
self.session_id = new_session_id
|
|
headers["X-Transmission-Session-Id"] = self.session_id
|
|
response = await client.post(self.base_url, json=payload, headers=headers)
|
|
|
|
if response.status_code != 200:
|
|
raise Exception(f"Transmission RPC 오류: HTTP {response.status_code} - {response.text}")
|
|
|
|
res_data = response.json()
|
|
if res_data.get("result") != "success":
|
|
raise Exception(f"Transmission 요청 실패: {res_data.get('result')}")
|
|
|
|
return res_data
|
|
|
|
async def add_torrent(self, magnet_or_url: str, download_dir: Optional[str] = None, paused: bool = False) -> Dict[str, Any]:
|
|
"""마그넷 또는 토렌트 URL 추가"""
|
|
arguments: Dict[str, Any] = {
|
|
"filename": magnet_or_url.strip(),
|
|
"paused": paused
|
|
}
|
|
if download_dir and download_dir.strip():
|
|
arguments["download-dir"] = download_dir.strip()
|
|
|
|
data = await self._request("torrent-add", arguments)
|
|
args = data.get("arguments", {})
|
|
|
|
# 'torrent-added' or 'torrent-duplicate'
|
|
if "torrent-added" in args:
|
|
return {"status": "added", "torrent": args["torrent-added"]}
|
|
elif "torrent-duplicate" in args:
|
|
return {"status": "duplicate", "torrent": args["torrent-duplicate"]}
|
|
else:
|
|
return {"status": "unknown", "arguments": args}
|
|
|
|
async def get_torrents(self) -> List[Dict[str, Any]]:
|
|
"""토렌트 목록 조회"""
|
|
fields = [
|
|
"id", "name", "status", "percentDone", "rateDownload",
|
|
"rateUpload", "totalSize", "eta", "downloadDir",
|
|
"errorString", "isFinished", "addedDate"
|
|
]
|
|
data = await self._request("torrent-get", {"fields": fields})
|
|
torrents = data.get("arguments", {}).get("torrents", [])
|
|
|
|
# 가공 및 상태 레이블 추가
|
|
result = []
|
|
for t in torrents:
|
|
status_code = t.get("status", 0)
|
|
result.append({
|
|
"id": t.get("id"),
|
|
"name": t.get("name", "알 수 없음"),
|
|
"status": status_code,
|
|
"status_label": STATUS_MAP.get(status_code, "알 수 없음"),
|
|
"percent_done": round(t.get("percentDone", 0) * 100, 2),
|
|
"rate_download": t.get("rateDownload", 0),
|
|
"rate_upload": t.get("rateUpload", 0),
|
|
"total_size": t.get("totalSize", 0),
|
|
"eta": t.get("eta"),
|
|
"download_dir": t.get("downloadDir"),
|
|
"error_string": t.get("errorString"),
|
|
"is_finished": t.get("isFinished", False),
|
|
})
|
|
return result
|
|
|
|
async def remove_torrent(self, torrent_id: int, delete_local_data: bool = False) -> None:
|
|
"""토렌트 삭제"""
|
|
arguments = {
|
|
"ids": [torrent_id],
|
|
"delete-local-data": delete_local_data
|
|
}
|
|
await self._request("torrent-remove", arguments)
|
|
|
|
async def start_torrent(self, torrent_id: int) -> None:
|
|
"""토렌트 시작/재개"""
|
|
await self._request("torrent-start", {"ids": [torrent_id]})
|
|
|
|
async def stop_torrent(self, torrent_id: int) -> None:
|
|
"""토렌트 일시정지"""
|
|
await self._request("torrent-stop", {"ids": [torrent_id]})
|
|
|
|
async def get_session_stats(self) -> Dict[str, Any]:
|
|
"""세션 정보 및 통계 확인"""
|
|
session_data = await self._request("session-get")
|
|
return session_data.get("arguments", {})
|
|
|
|
transmission_client = TransmissionClient()
|