Add Seedbox/70 Manager.py — continuous download manager for UltraCC
This commit is contained in:
278
Seedbox/70 Manager.py
Normal file
278
Seedbox/70 Manager.py
Normal file
@@ -0,0 +1,278 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user