배포
This commit is contained in:
commit
d9ef64eafe
35 files changed
+4733
No files matched your search
@@ -0,0 +1,129 @@
|
||||
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()
|
||||
Reference in new issue
Block a user