Files
torrents/Seedbox/70 Manager.py

279 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()