Files
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

93 lines
3.0 KiB
Python

import sqlite3
from datetime import datetime, timezone
def init_db(db_path):
"""Initialize the database and return a connection."""
conn = sqlite3.connect(db_path)
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("""
CREATE TABLE IF NOT EXISTS seen_files (
hash TEXT PRIMARY KEY,
filename TEXT NOT NULL,
source TEXT,
info_hash TEXT,
first_seen TEXT NOT NULL,
times_seen INTEGER DEFAULT 1
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS processed_torrents (
info_hash TEXT PRIMARY KEY,
torrent_name TEXT,
processed_at TEXT NOT NULL
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS processed_files (
info_hash TEXT NOT NULL,
file_name TEXT NOT NULL,
processed_at TEXT NOT NULL,
PRIMARY KEY (info_hash, file_name)
)
""")
conn.commit()
return conn
def is_known(conn, file_hash):
"""Check if a file hash is already in the database."""
row = conn.execute("SELECT 1 FROM seen_files WHERE hash = ?", (file_hash,)).fetchone()
return row is not None
def record_file(conn, file_hash, filename, source, info_hash=None):
"""Record a file hash in the database."""
now = datetime.now(timezone.utc).isoformat()
conn.execute(
"INSERT OR IGNORE INTO seen_files (hash, filename, source, info_hash, first_seen) VALUES (?, ?, ?, ?, ?)",
(file_hash, filename, source, info_hash, now),
)
conn.commit()
def increment_seen(conn, file_hash):
"""Increment the times_seen counter for a known hash."""
conn.execute("UPDATE seen_files SET times_seen = times_seen + 1 WHERE hash = ?", (file_hash,))
conn.commit()
def is_torrent_processed(conn, info_hash):
"""Check if a torrent has already been fully processed."""
row = conn.execute("SELECT 1 FROM processed_torrents WHERE info_hash = ?", (info_hash,)).fetchone()
return row is not None
def record_torrent(conn, info_hash, torrent_name):
"""Record a torrent as fully processed."""
now = datetime.now(timezone.utc).isoformat()
conn.execute(
"INSERT OR IGNORE INTO processed_torrents (info_hash, torrent_name, processed_at) VALUES (?, ?, ?)",
(info_hash, torrent_name, now),
)
conn.commit()
def is_file_processed(conn, info_hash, file_name):
"""Check if an individual file within a torrent has already been processed."""
row = conn.execute(
"SELECT 1 FROM processed_files WHERE info_hash = ? AND file_name = ?",
(info_hash, file_name),
).fetchone()
return row is not None
def record_processed_file(conn, info_hash, file_name):
"""Record an individual file within a torrent as processed."""
now = datetime.now(timezone.utc).isoformat()
conn.execute(
"INSERT OR IGNORE INTO processed_files (info_hash, file_name, processed_at) VALUES (?, ?, ?)",
(info_hash, file_name, now),
)
conn.commit()