#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Download Manager — UltraCC Seedbox Smyčka každých N minut: 1. Dokončené torrenty → odeber z qBittorrentu (data zachovej), zapiš do DB 2. Spočítej volné sloty 3. Doplň nové torrenty dle priority: seeders + velikost """ import pymysql import qbittorrentapi import time import sys from datetime import datetime # ============================================================ # CONFIG # ============================================================ MAX_CONCURRENT = 20 # max torrentů najednou v qBittorrentu LOOP_INTERVAL = 300 # sekund mezi iteracemi (5 minut) QBT_URL = "https://vladob.zen.usbx.me/qbittorrent" QBT_USER = "vladob" QBT_PASS = "jCni3U6d#y4bfcm" DB_CONFIG = { "host": "192.168.1.76", "port": 3306, "user": "root", "password": "Vlado9674+", "database": "torrents", "charset": "utf8mb4", "autocommit": True, } # ============================================================ # PRIORITY SQL # Pořadí: nejlepší seeders + nejmenší soubory první # ============================================================ SELECT_NEXT = """ SELECT id, torrent_hash, torrent_content, title_visible, size_pretty, seeders FROM torrents WHERE torrent_content IS NOT NULL AND qb_completed_datetime IS NULL AND (qb_state IS NULL OR qb_state NOT IN ( 'incomplete', 'invalid', 'dead', 'completed', 'completed_removed' )) ORDER BY CASE WHEN seeders >= 3 THEN 1 WHEN seeders >= 1 THEN 2 ELSE 3 END ASC, CASE WHEN size_pretty LIKE '%%MB%%' THEN 0 ELSE 1 END ASC, seeders DESC, added_datetime DESC LIMIT %s """ # ============================================================ # CONNECT # ============================================================ def connect_db(): return pymysql.connect(**DB_CONFIG) def connect_qbt(): qbt = qbittorrentapi.Client( host=QBT_URL, username=QBT_USER, password=QBT_PASS, VERIFY_WEBUI_CERTIFICATE=False, ) qbt.auth_log_in() return qbt # ============================================================ # STEP 1: Handle completed torrents # ============================================================ def handle_completed(qbt, cursor): """ Dokončené torrenty odebere z qBittorrentu (data zůstanou) a zapíše qb_completed_datetime do DB. """ removed = 0 for t in qbt.torrents_info(): if not t.completion_on: continue thash = t.hash.lower() completed_dt = datetime.fromtimestamp(t.completion_on) try: qbt.torrents_delete(torrent_hashes=thash, delete_files=False) except Exception as e: print(f" ⚠️ Nelze odebrat {t.name[:40]}: {e}") continue cursor.execute(""" UPDATE torrents SET qb_state = 'completed_removed', qb_progress = 100, qb_completed_datetime = COALESCE(qb_completed_datetime, %s), qb_last_update = NOW() WHERE torrent_hash = %s OR qb_hash = %s """, (completed_dt, thash, thash)) print(f" ✅ Dokončen a odebrán: {t.name[:50]}") removed += 1 return removed # ============================================================ # STEP 2: Count active (non-completed) torrents in qBittorrent # ============================================================ def get_active_hashes(qbt): """ Vrátí sadu hashů torrentů, které jsou aktuálně v qBittorrentu. """ return {t.hash.lower() for t in qbt.torrents_info()} # ============================================================ # STEP 3: Add new torrents # ============================================================ def add_torrents(qbt, cursor, active_hashes, slots): """ Vybere z DB torrenty dle priority a přidá je do qBittorrentu. Přeskočí ty, které jsou tam již nahrané (dle qb_hash / active_hashes). """ # Načti více než slots, abychom mohli přeskočit ty v qB cursor.execute(SELECT_NEXT, (slots * 3,)) rows = cursor.fetchall() added = 0 for row in rows: if added >= slots: break t_id, t_hash, content, title, size, seeders = row t_hash = t_hash.lower() if t_hash else "" if t_hash in active_hashes: # Aktualizuj příznak v DB pokud tam už je cursor.execute(""" UPDATE torrents SET qb_added=1, qb_last_update=NOW() WHERE id=%s AND (qb_added IS NULL OR qb_added=0) """, (t_id,)) continue if not content: continue try: qbt.torrents_add( torrent_files={f"{t_hash}.torrent": content}, is_paused=False, ) except Exception as e: print(f" ❌ Nelze přidat {(title or '')[:40]}: {e}") continue cursor.execute(""" UPDATE torrents SET qb_added=1, qb_state='added', qb_last_update=NOW() WHERE id=%s """, (t_id,)) print(f" ➕ Přidán: {(title or '')[:45]} " f"| {size or '?':>10} | seeds={seeders}") added += 1 time.sleep(0.3) return added # ============================================================ # MAIN LOOP # ============================================================ def main(): sys.stdout.reconfigure(encoding="utf-8") print("=" * 60) print("DOWNLOAD MANAGER — UltraCC Seedbox") print(f"MAX_CONCURRENT = {MAX_CONCURRENT} | interval = {LOOP_INTERVAL}s") print("Ctrl+C pro zastavení") print("=" * 60) db = connect_db() cursor = db.cursor() iteration = 0 try: while True: iteration += 1 now = datetime.now() print(f"\n{'─'*60}") print(f" Iterace {iteration} — {now:%Y-%m-%d %H:%M:%S}") print(f"{'─'*60}") # Připoj se k qBittorrentu (nové spojení každou iteraci) try: qbt = connect_qbt() except Exception as e: print(f" ❌ Nelze se připojit k qBittorrentu: {e}") print(f" ⏳ Zkusím za {LOOP_INTERVAL}s...") time.sleep(LOOP_INTERVAL) continue # --- Krok 1: Dokončené --- print(" [1] Kontrola dokončených...") removed = handle_completed(qbt, cursor) if removed == 0: print(" Žádné dokončené.") # --- Krok 2: Stav slotů --- active_hashes = get_active_hashes(qbt) active = len(active_hashes) slots = max(0, MAX_CONCURRENT - active) # Statistiky z DB cursor.execute(""" SELECT SUM(CASE WHEN qb_completed_datetime IS NOT NULL THEN 1 ELSE 0 END), SUM(CASE WHEN qb_state IS NULL AND torrent_content IS NOT NULL THEN 1 ELSE 0 END), SUM(CASE WHEN qb_state IN ('incomplete','dead') THEN 1 ELSE 0 END) FROM torrents """) db_completed, db_waiting, db_dead = cursor.fetchone() print(f" [2] Sloty: {active}/{MAX_CONCURRENT} aktivních " f"| volných: {slots}") print(f" DB — staženo: {db_completed or 0} " f"| čeká: {db_waiting or 0} " f"| dead/incomplete: {db_dead or 0}") # --- Krok 3: Doplnění --- if slots > 0: print(f" [3] Doplňuji {slots} torrentů...") added = add_torrents(qbt, cursor, active_hashes, slots) if added == 0: print(" Žádné vhodné torrenty k přidání.") else: print(f" Přidáno: {added}") else: print(f" [3] Sloty plné ({active}/{MAX_CONCURRENT}), přeskakuji.") print(f"\n ⏳ Další iterace za {LOOP_INTERVAL // 60} min " f"({(now.replace(second=0, microsecond=0)).__class__.__name__} " f"→ přibližně {LOOP_INTERVAL // 60} min)...") time.sleep(LOOP_INTERVAL) except KeyboardInterrupt: print("\n\n🛑 Zastaveno uživatelem.") finally: db.close() print("👋 Bye.") if __name__ == "__main__": main()