diff --git a/Seedbox/70 Manager.py b/Seedbox/70 Manager.py index 6e91c3a..ad2044f 100644 --- a/Seedbox/70 Manager.py +++ b/Seedbox/70 Manager.py @@ -1,16 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -Download Manager — UltraCC Seedbox -Smyčka každých N minut: +Download Manager — Multi-client (UltraCC + lokální qBittorrent) +Smyčka každých N minut pro každý klient: 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 + +Oba klienti sdílí stejnou DB frontu. Torrent "nárokovaný" jedním klientem +(qb_state='added') nebude nabídnut druhému klientovi. """ import pymysql import qbittorrentapi -import time import sys from datetime import datetime @@ -18,26 +20,45 @@ 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" +CLIENTS = [ + { + "name": "UltraCC Seedbox", + "max_concurrent": 20, + "qbt": { + "host": "https://vladob.zen.usbx.me/qbittorrent", + "username": "vladob", + "password": "jCni3U6d#y4bfcm", + "VERIFY_WEBUI_CERTIFICATE": False, + }, + }, + { + "name": "Local qBittorrent", + "max_concurrent": 20, + "qbt": { + "host": "192.168.1.76", + "port": 8080, + "username": "admin", + "password": "adminadmin", + }, + }, +] DB_CONFIG = { - "host": "192.168.1.76", - "port": 3306, - "user": "root", - "password": "Vlado9674+", - "database": "torrents", - "charset": "utf8mb4", + "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í +# +# DŮLEŽITÉ: 'added' je v exclusion listu — torrent nárokovaný +# jedním klientem nebude nabídnut druhému. # ============================================================ SELECT_NEXT = """ SELECT id, torrent_hash, torrent_content, title_visible, size_pretty, seeders @@ -46,7 +67,7 @@ SELECT_NEXT = """ torrent_content IS NOT NULL AND qb_completed_datetime IS NULL AND (qb_state IS NULL OR qb_state NOT IN ( - 'incomplete', 'invalid', 'dead', + 'added', 'incomplete', 'invalid', 'dead', 'completed', 'completed_removed' )) ORDER BY @@ -72,13 +93,8 @@ 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, - ) +def connect_qbt(cfg: dict): + qbt = qbittorrentapi.Client(**cfg) qbt.auth_log_in() return qbt @@ -137,12 +153,12 @@ def get_active_hashes(qbt): # STEP 3: Add new torrents # ============================================================ -def add_torrents(qbt, cursor, active_hashes, slots): +def add_torrents(qbt, cursor, active_hashes, slots, client_name: str): """ 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). + Přeskočí ty, které jsou tam již nahrané (dle active_hashes). + Zapíše qb_client = client_name, aby bylo vidět, kdo torrent stahuje. """ - # Načti více než slots, abychom mohli přeskočit ty v qB cursor.execute(SELECT_NEXT, (slots * 3,)) rows = cursor.fetchall() @@ -155,7 +171,7 @@ def add_torrents(qbt, cursor, active_hashes, slots): t_hash = t_hash.lower() if t_hash else "" if t_hash in active_hashes: - # Aktualizuj příznak v DB pokud tam už je + # Torrent je v qB, ale DB ho ještě nemá označený cursor.execute(""" UPDATE torrents SET qb_added=1, qb_last_update=NOW() WHERE id=%s AND (qb_added IS NULL OR qb_added=0) @@ -176,18 +192,61 @@ def add_torrents(qbt, cursor, active_hashes, slots): cursor.execute(""" UPDATE torrents - SET qb_added=1, qb_state='added', qb_last_update=NOW() + SET qb_added=1, qb_state='added', qb_client=%s, qb_last_update=NOW() WHERE id=%s - """, (t_id,)) + """, (client_name, t_id)) print(f" ➕ Přidán: {(title or '')[:45]} " f"| {size or '?':>10} | seeds={seeders}") added += 1 - time.sleep(0.3) return added +# ============================================================ +# PROCESS ONE CLIENT (steps 1-3) +# ============================================================ + +def process_client(client_cfg: dict, cursor): + name = client_cfg["name"] + max_concurrent = client_cfg["max_concurrent"] + + print(f"\n ┌── {name} (max {max_concurrent}) ──") + + try: + qbt = connect_qbt(client_cfg["qbt"]) + except Exception as e: + print(f" │ ❌ Nelze se připojit: {e}") + print(f" └──") + return + + # Krok 1: Dokončené + print(f" │ [1] Kontrola dokončených...") + removed = handle_completed(qbt, cursor) + if removed == 0: + print(f" │ Žádné dokončené.") + + # Krok 2: Stav slotů + active_hashes = get_active_hashes(qbt) + active = len(active_hashes) + slots = max(0, max_concurrent - active) + + print(f" │ [2] Sloty: {active}/{max_concurrent} aktivních | volných: {slots}") + + # Krok 3: Doplnění + if slots > 0: + print(f" │ [3] Doplňuji {slots} torrentů...") + added = add_torrents(qbt, cursor, active_hashes, slots, name) + if added == 0: + print(f" │ Žá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" └──") + + # ============================================================ # MAIN LOOP # ============================================================ @@ -195,83 +254,51 @@ def add_torrents(qbt, cursor, active_hashes, slots): def main(): sys.stdout.reconfigure(encoding="utf-8") + now = datetime.now() print("=" * 60) - print("DOWNLOAD MANAGER — UltraCC Seedbox") - print(f"MAX_CONCURRENT = {MAX_CONCURRENT} | interval = {LOOP_INTERVAL}s") - print("Ctrl+C pro zastavení") + print(f"DOWNLOAD MANAGER — Multi-client {now:%Y-%m-%d %H:%M:%S}") + for c in CLIENTS: + print(f" • {c['name']} (max {c['max_concurrent']})") + print(f"Celkem slotů: {sum(c['max_concurrent'] for c in CLIENTS)}") 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}") + # DB statistiky — celkové + 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" DB — staženo: {db_completed or 0} " + f"| čeká: {db_waiting or 0} " + f"| dead/incomplete: {db_dead or 0}") - # 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 + # DB statistiky — per-client + cursor.execute(""" + SELECT qb_client, COUNT(*) AS cnt + FROM torrents + WHERE qb_client IS NOT NULL + GROUP BY qb_client + """) + per_client = cursor.fetchall() + if per_client: + parts = " | ".join(f"{name}: {cnt}" for name, cnt in per_client) + print(f" DB — per-client: {parts}") - # --- 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.") + # Zpracuj každý klient + # (druhý klient vidí stav DB aktualizovaný prvním) + for client_cfg in CLIENTS: + process_client(client_cfg, cursor) finally: db.close() - print("👋 Bye.") + print("\n👋 Hotovo.") if __name__ == "__main__":