Files
StashHandler/qbit_client.py
T
bvandeusen 5bcb35e737 Implement per-file import for in-progress torrents
Files within a downloading torrent are now imported as soon as their
individual progress reaches 100%, without waiting for the full torrent
to complete. A new processed_files table (info_hash, file_name) tracks
which files have been handled to avoid redundant re-hashing each poll.

- db.py: add processed_files table, is_file_processed, record_processed_file
- qbit_client.py: replace get_completed_torrents with get_torrents(filter, category)
- stash_handler.py: extract process_torrent_files helper, daemon now polls
  both downloading and completed torrents each cycle

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:51:52 -05:00

54 lines
1.9 KiB
Python

import logging
import requests
log = logging.getLogger(__name__)
class QBitClient:
def __init__(self, url, username, password):
self.url = url.rstrip("/")
self.username = username
self.password = password
self.session = requests.Session()
self._logged_in = False
def login(self):
"""Authenticate with qBittorrent and store the session cookie."""
resp = self.session.post(
f"{self.url}/api/v2/auth/login",
data={"username": self.username, "password": self.password},
)
resp.raise_for_status()
if resp.text.strip() == "Ok.":
self._logged_in = True
log.info("Logged in to qBittorrent at %s", self.url)
else:
raise RuntimeError(f"qBit login failed: {resp.text}")
def _request(self, method, endpoint, **kwargs):
"""Make an API request, re-authenticating on 403."""
if not self._logged_in:
self.login()
resp = self.session.request(method, f"{self.url}{endpoint}", **kwargs)
if resp.status_code == 403:
log.info("Session expired, re-authenticating")
self.login()
resp = self.session.request(method, f"{self.url}{endpoint}", **kwargs)
resp.raise_for_status()
return resp
def get_torrents(self, filter=None, category=None):
"""Return a list of torrent dicts, optionally filtered by state and/or category."""
params = {}
if filter:
params["filter"] = filter
if category:
params["category"] = category
resp = self._request("GET", "/api/v2/torrents/info", params=params)
return resp.json()
def get_torrent_files(self, info_hash):
"""Return the file list for a specific torrent."""
resp = self._request("GET", "/api/v2/torrents/files", params={"hash": info_hash})
return resp.json()