Compare commits
12 Commits
7646f6f68f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 058281df0e | |||
| 3ce8fa0080 | |||
| 695d7167ab | |||
| 1aa10c92d6 | |||
| c4f2d8b13d | |||
| a74ad8ff00 | |||
| afbca5b348 | |||
| 20c4a7d8b4 | |||
| 489b236b9b | |||
| 197cb3f8db | |||
| b37db5397e | |||
| 15b498ca55 |
94
Seedbox/50 PrintFilenamesFromTorrentFiles.py
Normal file
94
Seedbox/50 PrintFilenamesFromTorrentFiles.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
import bencodepy
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DB CONFIG – UPRAV
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.50",
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "torrents",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"cursorclass": pymysql.cursors.SSCursor # streaming
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# HELPERS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def decode_if_bytes(value):
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
return value.decode("utf-8", errors="replace")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def parse_torrent(blob):
|
||||||
|
data = bencodepy.decode(blob)
|
||||||
|
|
||||||
|
info = data[b"info"]
|
||||||
|
|
||||||
|
torrent_name = decode_if_bytes(info[b"name"])
|
||||||
|
|
||||||
|
files = []
|
||||||
|
|
||||||
|
# multi-file torrent
|
||||||
|
if b"files" in info:
|
||||||
|
for f in info[b"files"]:
|
||||||
|
path = "/".join(decode_if_bytes(p) for p in f[b"path"])
|
||||||
|
length = f[b"length"]
|
||||||
|
files.append((path, length))
|
||||||
|
|
||||||
|
# single file torrent
|
||||||
|
else:
|
||||||
|
length = info[b"length"]
|
||||||
|
files.append((torrent_name, length))
|
||||||
|
|
||||||
|
return torrent_name, files
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MAIN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, title_visible, torrent_content
|
||||||
|
FROM torrents
|
||||||
|
WHERE torrent_content IS NOT NULL
|
||||||
|
""")
|
||||||
|
|
||||||
|
for row in cursor:
|
||||||
|
torrent_id = row[0]
|
||||||
|
title_visible = row[1]
|
||||||
|
blob = row[2]
|
||||||
|
|
||||||
|
try:
|
||||||
|
name, files = parse_torrent(blob)
|
||||||
|
|
||||||
|
print("=" * 70)
|
||||||
|
print(f"DB ID : {torrent_id}")
|
||||||
|
print(f"Title visible : {title_visible}")
|
||||||
|
print(f"Torrent name : {name}")
|
||||||
|
print("Files:")
|
||||||
|
|
||||||
|
for f, size in files:
|
||||||
|
print(f" - {f} ({size} bytes)")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR parsing torrent ID {torrent_id}: {e}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
137
Seedbox/51 ExportTorrentObsahuDoExcel.py
Normal file
137
Seedbox/51 ExportTorrentObsahuDoExcel.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
import bencodepy
|
||||||
|
from openpyxl import Workbook
|
||||||
|
from openpyxl.utils import get_column_letter
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DB CONFIG
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.50",
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "torrents",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"cursorclass": pymysql.cursors.SSCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
OUTPUT_FILE = "torrent_report.xlsx"
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# HELPERS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def decode_if_bytes(value):
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
return value.decode("utf-8", errors="replace")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def get_root_name(info):
|
||||||
|
# prefer UTF-8 variant
|
||||||
|
if b"name.utf-8" in info:
|
||||||
|
return decode_if_bytes(info[b"name.utf-8"])
|
||||||
|
return decode_if_bytes(info[b"name"])
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_parts(file_entry):
|
||||||
|
# prefer UTF-8 variant
|
||||||
|
if b"path.utf-8" in file_entry:
|
||||||
|
return [decode_if_bytes(p) for p in file_entry[b"path.utf-8"]]
|
||||||
|
return [decode_if_bytes(p) for p in file_entry[b"path"]]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_torrent(blob):
|
||||||
|
|
||||||
|
data = bencodepy.decode(blob)
|
||||||
|
info = data[b"info"]
|
||||||
|
|
||||||
|
root_name = get_root_name(info)
|
||||||
|
|
||||||
|
files = []
|
||||||
|
|
||||||
|
# =====================
|
||||||
|
# MULTI FILE TORRENT
|
||||||
|
# =====================
|
||||||
|
if b"files" in info:
|
||||||
|
|
||||||
|
for f in info[b"files"]:
|
||||||
|
|
||||||
|
parts = get_file_parts(f)
|
||||||
|
|
||||||
|
# ochrana proti root/root duplicite
|
||||||
|
if parts and parts[0] == root_name:
|
||||||
|
full_path = "/".join(parts)
|
||||||
|
else:
|
||||||
|
full_path = root_name + "/" + "/".join(parts)
|
||||||
|
|
||||||
|
files.append(full_path)
|
||||||
|
|
||||||
|
# =====================
|
||||||
|
# SINGLE FILE TORRENT
|
||||||
|
# =====================
|
||||||
|
else:
|
||||||
|
files.append(root_name)
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MAIN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
|
||||||
|
wb = Workbook()
|
||||||
|
ws = wb.active
|
||||||
|
ws.title = "Torrent report"
|
||||||
|
|
||||||
|
ws.append(["QB Status", "Title", "Torrent Path"])
|
||||||
|
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT qb_state, title_visible, torrent_content
|
||||||
|
FROM torrents
|
||||||
|
WHERE torrent_content IS NOT NULL
|
||||||
|
""")
|
||||||
|
|
||||||
|
for qb_state, title_visible, blob in cursor:
|
||||||
|
|
||||||
|
qb_state = qb_state or "UNKNOWN"
|
||||||
|
title_visible = title_visible or ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
files = parse_torrent(blob)
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
ws.append([qb_state, title_visible, f])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
ws.append([qb_state, title_visible, f"ERROR: {e}"])
|
||||||
|
|
||||||
|
# autosize
|
||||||
|
for col in ws.columns:
|
||||||
|
max_len = 0
|
||||||
|
col_letter = get_column_letter(col[0].column)
|
||||||
|
|
||||||
|
for cell in col:
|
||||||
|
if cell.value:
|
||||||
|
max_len = max(max_len, len(str(cell.value)))
|
||||||
|
|
||||||
|
ws.column_dimensions[col_letter].width = min(max_len + 2, 90)
|
||||||
|
|
||||||
|
wb.save(OUTPUT_FILE)
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print("DONE ->", OUTPUT_FILE)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
48
Seedbox/52 UložBalíkAudioknih.py
Normal file
48
Seedbox/52 UložBalíkAudioknih.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import pymysql
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# CONFIG
|
||||||
|
# =========================
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.50",
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "torrents",
|
||||||
|
"charset": "utf8mb4"
|
||||||
|
}
|
||||||
|
|
||||||
|
SEARCH_NAME = "Balík audioknih"
|
||||||
|
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# MAIN
|
||||||
|
# =========================
|
||||||
|
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, title_visible, torrent_content
|
||||||
|
FROM torrents
|
||||||
|
WHERE title_visible LIKE %s
|
||||||
|
LIMIT 1
|
||||||
|
""", ("%" + SEARCH_NAME + "%",))
|
||||||
|
|
||||||
|
row = cursor.fetchone()
|
||||||
|
|
||||||
|
if not row:
|
||||||
|
print("Torrent not found")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
torrent_id, title, blob = row
|
||||||
|
|
||||||
|
filename = f"{title}.torrent".replace("/", "_")
|
||||||
|
|
||||||
|
with open(filename, "wb") as f:
|
||||||
|
f.write(blob)
|
||||||
|
|
||||||
|
print("Saved:", filename)
|
||||||
|
|
||||||
|
conn.close()
|
||||||
@@ -1,43 +1,68 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Download Manager — UltraCC Seedbox
|
Download Manager — Multi-client (UltraCC + lokální qBittorrent)
|
||||||
Smyčka každých N minut:
|
Smyčka každých N minut pro každý klient:
|
||||||
1. Dokončené torrenty → odeber z qBittorrentu (data zachovej), zapiš do DB
|
1. Dokončené torrenty → odeber z qBittorrentu (data zachovej), zapiš do DB
|
||||||
2. Spočítej volné sloty
|
2. Spočítej volné sloty
|
||||||
3. Doplň nové torrenty dle priority: seeders + velikost
|
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 pymysql
|
||||||
import qbittorrentapi
|
import qbittorrentapi
|
||||||
import time
|
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# CONFIG
|
# CONFIG
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
MAX_CONCURRENT = 20 # max torrentů najednou v qBittorrentu
|
DEAD_AFTER_HOURS = 72 # progress < 95% po 72h → dead
|
||||||
LOOP_INTERVAL = 300 # sekund mezi iteracemi (5 minut)
|
DEAD_PROGRESS_THRESHOLD = 95.0
|
||||||
|
STUCK_AFTER_HOURS = 168 # progress >= 95% ale < 100% po 7 dnech → dead
|
||||||
|
|
||||||
QBT_URL = "https://vladob.zen.usbx.me/qbittorrent"
|
CLIENTS = [
|
||||||
QBT_USER = "vladob"
|
{
|
||||||
QBT_PASS = "jCni3U6d#y4bfcm"
|
"name": "UltraCC Seedbox",
|
||||||
|
"max_concurrent": 30,
|
||||||
|
"qbt": {
|
||||||
|
"host": "https://vladob.zen.usbx.me/qbittorrent",
|
||||||
|
"username": "vladob",
|
||||||
|
"password": "jCni3U6d#y4bfcm",
|
||||||
|
"VERIFY_WEBUI_CERTIFICATE": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Local qBittorrent",
|
||||||
|
"max_concurrent": 30,
|
||||||
|
"qbt": {
|
||||||
|
"host": "192.168.1.76",
|
||||||
|
"port": 8080,
|
||||||
|
"username": "admin",
|
||||||
|
"password": "adminadmin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
DB_CONFIG = {
|
DB_CONFIG = {
|
||||||
"host": "192.168.1.76",
|
"host": "192.168.1.76",
|
||||||
"port": 3306,
|
"port": 3306,
|
||||||
"user": "root",
|
"user": "root",
|
||||||
"password": "Vlado9674+",
|
"password": "Vlado9674+",
|
||||||
"database": "torrents",
|
"database": "torrents",
|
||||||
"charset": "utf8mb4",
|
"charset": "utf8mb4",
|
||||||
"autocommit": True,
|
"autocommit": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# PRIORITY SQL
|
# PRIORITY SQL
|
||||||
# Pořadí: nejlepší seeders + nejmenší soubory první
|
# 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_NEXT = """
|
||||||
SELECT id, torrent_hash, torrent_content, title_visible, size_pretty, seeders
|
SELECT id, torrent_hash, torrent_content, title_visible, size_pretty, seeders
|
||||||
@@ -46,7 +71,7 @@ SELECT_NEXT = """
|
|||||||
torrent_content IS NOT NULL
|
torrent_content IS NOT NULL
|
||||||
AND qb_completed_datetime IS NULL
|
AND qb_completed_datetime IS NULL
|
||||||
AND (qb_state IS NULL OR qb_state NOT IN (
|
AND (qb_state IS NULL OR qb_state NOT IN (
|
||||||
'incomplete', 'invalid', 'dead',
|
'added', 'incomplete', 'invalid', 'dead',
|
||||||
'completed', 'completed_removed'
|
'completed', 'completed_removed'
|
||||||
))
|
))
|
||||||
ORDER BY
|
ORDER BY
|
||||||
@@ -72,13 +97,8 @@ def connect_db():
|
|||||||
return pymysql.connect(**DB_CONFIG)
|
return pymysql.connect(**DB_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
def connect_qbt():
|
def connect_qbt(cfg: dict):
|
||||||
qbt = qbittorrentapi.Client(
|
qbt = qbittorrentapi.Client(**cfg)
|
||||||
host=QBT_URL,
|
|
||||||
username=QBT_USER,
|
|
||||||
password=QBT_PASS,
|
|
||||||
VERIFY_WEBUI_CERTIFICATE=False,
|
|
||||||
)
|
|
||||||
qbt.auth_log_in()
|
qbt.auth_log_in()
|
||||||
return qbt
|
return qbt
|
||||||
|
|
||||||
@@ -94,11 +114,15 @@ def handle_completed(qbt, cursor):
|
|||||||
"""
|
"""
|
||||||
removed = 0
|
removed = 0
|
||||||
for t in qbt.torrents_info():
|
for t in qbt.torrents_info():
|
||||||
if not t.completion_on:
|
if not t.completion_on or t.completion_on < 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
completed_dt = datetime.fromtimestamp(t.completion_on)
|
||||||
|
except (OSError, ValueError, OverflowError):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
thash = t.hash.lower()
|
thash = t.hash.lower()
|
||||||
completed_dt = datetime.fromtimestamp(t.completion_on)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
qbt.torrents_delete(torrent_hashes=thash, delete_files=False)
|
qbt.torrents_delete(torrent_hashes=thash, delete_files=False)
|
||||||
@@ -122,6 +146,68 @@ def handle_completed(qbt, cursor):
|
|||||||
return removed
|
return removed
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# STEP 1b: Handle dead torrents (z 50 MrtveTorrenty.py)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def handle_dead_torrents(qbt, cursor):
|
||||||
|
"""
|
||||||
|
Mrtvé torrenty: nízký progress po 72h NEBO zaseknutý >= 95% po 7 dnech.
|
||||||
|
Smaže z qBittorrentu včetně souborů a označí v DB jako incomplete.
|
||||||
|
"""
|
||||||
|
now = datetime.now()
|
||||||
|
deadline_a = now - timedelta(hours=DEAD_AFTER_HOURS)
|
||||||
|
deadline_b = now - timedelta(hours=STUCK_AFTER_HOURS)
|
||||||
|
|
||||||
|
dead_count = 0
|
||||||
|
|
||||||
|
for t in qbt.torrents_info():
|
||||||
|
# Přeskočit dokončené
|
||||||
|
if t.completion_on and t.completion_on > 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
added_on = t.added_on
|
||||||
|
if not added_on:
|
||||||
|
continue
|
||||||
|
|
||||||
|
added_dt = datetime.fromtimestamp(added_on)
|
||||||
|
progress_pct = float(t.progress) * 100.0
|
||||||
|
|
||||||
|
# Kritérium A: nízký progress po 72h
|
||||||
|
is_dead_a = (added_dt <= deadline_a) and (progress_pct < DEAD_PROGRESS_THRESHOLD)
|
||||||
|
|
||||||
|
# Kritérium B: zaseknutý blízko 100% po 7 dnech
|
||||||
|
is_dead_b = (added_dt <= deadline_b) and (progress_pct >= DEAD_PROGRESS_THRESHOLD) and (progress_pct < 100.0)
|
||||||
|
|
||||||
|
if not is_dead_a and not is_dead_b:
|
||||||
|
continue
|
||||||
|
|
||||||
|
thash = t.hash.lower()
|
||||||
|
reason = "nízký progress po 72h" if is_dead_a else "zaseknutý blízko 100% po 7 dnech"
|
||||||
|
|
||||||
|
print(f" 💀 MRTVÝ ({reason}): {t.name[:50]}")
|
||||||
|
print(f" Progress: {progress_pct:.1f}% | Stav: {t.state} | Seeds: {t.num_seeds}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
qbt.torrents_delete(torrent_hashes=thash, delete_files=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Smazání selhalo: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE torrents
|
||||||
|
SET
|
||||||
|
qb_state = 'incomplete',
|
||||||
|
qb_progress = %s,
|
||||||
|
qb_last_update = NOW()
|
||||||
|
WHERE torrent_hash = %s OR qb_hash = %s
|
||||||
|
""", (progress_pct, thash, thash))
|
||||||
|
|
||||||
|
dead_count += 1
|
||||||
|
|
||||||
|
return dead_count
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# STEP 2: Count active (non-completed) torrents in qBittorrent
|
# STEP 2: Count active (non-completed) torrents in qBittorrent
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -137,12 +223,12 @@ def get_active_hashes(qbt):
|
|||||||
# STEP 3: Add new torrents
|
# 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.
|
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,))
|
cursor.execute(SELECT_NEXT, (slots * 3,))
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
@@ -155,7 +241,7 @@ def add_torrents(qbt, cursor, active_hashes, slots):
|
|||||||
t_hash = t_hash.lower() if t_hash else ""
|
t_hash = t_hash.lower() if t_hash else ""
|
||||||
|
|
||||||
if t_hash in active_hashes:
|
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("""
|
cursor.execute("""
|
||||||
UPDATE torrents SET qb_added=1, qb_last_update=NOW()
|
UPDATE torrents SET qb_added=1, qb_last_update=NOW()
|
||||||
WHERE id=%s AND (qb_added IS NULL OR qb_added=0)
|
WHERE id=%s AND (qb_added IS NULL OR qb_added=0)
|
||||||
@@ -176,18 +262,69 @@ def add_torrents(qbt, cursor, active_hashes, slots):
|
|||||||
|
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
UPDATE torrents
|
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
|
WHERE id=%s
|
||||||
""", (t_id,))
|
""", (client_name, t_id))
|
||||||
|
|
||||||
print(f" ➕ Přidán: {(title or '')[:45]} "
|
print(f" ➕ Přidán: {(title or '')[:45]} "
|
||||||
f"| {size or '?':>10} | seeds={seeders}")
|
f"| {size or '?':>10} | seeds={seeders}")
|
||||||
added += 1
|
added += 1
|
||||||
time.sleep(0.3)
|
|
||||||
|
|
||||||
return added
|
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 1b: Mrtvé torrenty
|
||||||
|
print(f" │ [1b] Kontrola mrtvých torrentů...")
|
||||||
|
dead = handle_dead_torrents(qbt, cursor)
|
||||||
|
if dead == 0:
|
||||||
|
print(f" │ Žádné mrtvé.")
|
||||||
|
else:
|
||||||
|
print(f" │ Odstraněno mrtvých: {dead}")
|
||||||
|
|
||||||
|
# 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
|
# MAIN LOOP
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -195,83 +332,51 @@ def add_torrents(qbt, cursor, active_hashes, slots):
|
|||||||
def main():
|
def main():
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
print("DOWNLOAD MANAGER — UltraCC Seedbox")
|
print(f"DOWNLOAD MANAGER — Multi-client {now:%Y-%m-%d %H:%M:%S}")
|
||||||
print(f"MAX_CONCURRENT = {MAX_CONCURRENT} | interval = {LOOP_INTERVAL}s")
|
for c in CLIENTS:
|
||||||
print("Ctrl+C pro zastavení")
|
print(f" • {c['name']} (max {c['max_concurrent']})")
|
||||||
|
print(f"Celkem slotů: {sum(c['max_concurrent'] for c in CLIENTS)}")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
||||||
db = connect_db()
|
db = connect_db()
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
|
|
||||||
iteration = 0
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
# DB statistiky — celkové
|
||||||
iteration += 1
|
cursor.execute("""
|
||||||
now = datetime.now()
|
SELECT
|
||||||
print(f"\n{'─'*60}")
|
SUM(CASE WHEN qb_completed_datetime IS NOT NULL THEN 1 ELSE 0 END),
|
||||||
print(f" Iterace {iteration} — {now:%Y-%m-%d %H:%M:%S}")
|
SUM(CASE WHEN qb_state IS NULL AND torrent_content IS NOT NULL THEN 1 ELSE 0 END),
|
||||||
print(f"{'─'*60}")
|
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)
|
# DB statistiky — per-client
|
||||||
try:
|
cursor.execute("""
|
||||||
qbt = connect_qbt()
|
SELECT qb_client, COUNT(*) AS cnt
|
||||||
except Exception as e:
|
FROM torrents
|
||||||
print(f" ❌ Nelze se připojit k qBittorrentu: {e}")
|
WHERE qb_client IS NOT NULL
|
||||||
print(f" ⏳ Zkusím za {LOOP_INTERVAL}s...")
|
GROUP BY qb_client
|
||||||
time.sleep(LOOP_INTERVAL)
|
""")
|
||||||
continue
|
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é ---
|
# Zpracuj každý klient
|
||||||
print(" [1] Kontrola dokončených...")
|
# (druhý klient vidí stav DB aktualizovaný prvním)
|
||||||
removed = handle_completed(qbt, cursor)
|
for client_cfg in CLIENTS:
|
||||||
if removed == 0:
|
process_client(client_cfg, cursor)
|
||||||
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:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
print("👋 Bye.")
|
print("\n👋 Hotovo.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
182
Seedbox/80 DeleteWhatWeHaveUltraCC2.py
Normal file
182
Seedbox/80 DeleteWhatWeHaveUltraCC2.py
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Projde /mnt/user/torrents/ultracc, pro každý soubor spočítá blake3
|
||||||
|
a porovná s tabulkou file_md5_index. Pokud je hash nalezen → soubor smaže.
|
||||||
|
Po smazání souborů odstraní prázdné adresáře.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import blake3
|
||||||
|
import pymysql
|
||||||
|
import paramiko
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONFIG
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
SCAN_DIR = "//tower/torrents/ultracc2"
|
||||||
|
|
||||||
|
SSH_CONFIG = {
|
||||||
|
"hostname": "192.168.1.76",
|
||||||
|
"port": 22,
|
||||||
|
"username": "root",
|
||||||
|
"password": "7309208104",
|
||||||
|
}
|
||||||
|
|
||||||
|
ULTRACC_DIRS = [
|
||||||
|
"/mnt/user/Torrents/UltraCC",
|
||||||
|
"/mnt/user/Torrents/UltraCC1",
|
||||||
|
"/mnt/user/Torrents/UltraCC2",
|
||||||
|
]
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.76",
|
||||||
|
"port": 3306,
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "torrents",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
}
|
||||||
|
|
||||||
|
CHUNK_SIZE = 8 * 1024 * 1024 # 8 MB
|
||||||
|
DRY_RUN = False # True = pouze vypíše, nesmaže
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# HELPERS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def compute_blake3(path: Path) -> bytes:
|
||||||
|
"""Vrátí blake3 digest jako 32 raw bytes."""
|
||||||
|
h = blake3.blake3()
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
while True:
|
||||||
|
chunk = f.read(CHUNK_SIZE)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
h.update(chunk)
|
||||||
|
return h.digest()
|
||||||
|
|
||||||
|
|
||||||
|
def hash_in_db(cursor, digest: bytes):
|
||||||
|
"""Vrátí (host_name, full_path) prvního záznamu s daným hashem, nebo None."""
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT host_name, full_path FROM file_md5_index WHERE blake3 = %s AND host_name = 'tower1' AND full_path LIKE '/mnt/user/#ColdData/Porno/%%' LIMIT 1",
|
||||||
|
(digest,)
|
||||||
|
)
|
||||||
|
return cursor.fetchone() # None nebo (host_name, full_path)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_empty_dirs(root: str) -> int:
|
||||||
|
"""Rekurzivně smaže prázdné adresáře pod root. Vrátí počet smazaných."""
|
||||||
|
removed = 0
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root, topdown=False):
|
||||||
|
if dirpath == root:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
os.rmdir(dirpath)
|
||||||
|
print(f" [rmdir] {dirpath}")
|
||||||
|
removed += 1
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return removed
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MAIN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def set_ultracc_permissions():
|
||||||
|
"""Přes SSH nastaví na Tower chown nobody:users + chmod 777 pro všechny UltraCC adresáře."""
|
||||||
|
print("Nastavuji práva na Tower (UltraCC*)...")
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect(**SSH_CONFIG)
|
||||||
|
for d in ULTRACC_DIRS:
|
||||||
|
_, out, err = ssh.exec_command(
|
||||||
|
'chown -R nobody:users "%s" && chmod -R 777 "%s" && echo OK' % (d, d)
|
||||||
|
)
|
||||||
|
result = out.read().decode().strip()
|
||||||
|
error = err.read().decode().strip()
|
||||||
|
if result == "OK":
|
||||||
|
print(f" [OK] {d}")
|
||||||
|
else:
|
||||||
|
print(f" [CHYBA] {d}: {error}")
|
||||||
|
ssh.close()
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
dry_run = DRY_RUN
|
||||||
|
|
||||||
|
set_ultracc_permissions()
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print("=== DRY RUN — nic se nesmaže ===\n")
|
||||||
|
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
scan_root = Path(SCAN_DIR)
|
||||||
|
if not scan_root.exists():
|
||||||
|
print(f"CHYBA: Adresář neexistuje: {SCAN_DIR}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
files_checked = 0
|
||||||
|
files_deleted = 0
|
||||||
|
files_kept = 0
|
||||||
|
bytes_deleted = 0
|
||||||
|
|
||||||
|
for file_path in scan_root.rglob("*"):
|
||||||
|
if not file_path.is_file():
|
||||||
|
continue
|
||||||
|
|
||||||
|
files_checked += 1
|
||||||
|
size = file_path.stat().st_size
|
||||||
|
|
||||||
|
try:
|
||||||
|
digest = compute_blake3(file_path)
|
||||||
|
except OSError as e:
|
||||||
|
print(f" [CHYBA čtení] {file_path}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
db_match = hash_in_db(cursor, digest)
|
||||||
|
if db_match:
|
||||||
|
db_host, db_path = db_match
|
||||||
|
print(f" [SMAZAT] {file_path} ({size:,} B)")
|
||||||
|
print(f" ↳ originál v DB: [{db_host}] {db_path}")
|
||||||
|
if not dry_run:
|
||||||
|
try:
|
||||||
|
file_path.unlink()
|
||||||
|
files_deleted += 1
|
||||||
|
bytes_deleted += size
|
||||||
|
except OSError as e:
|
||||||
|
print(f" [CHYBA smazání] {file_path}: {e}")
|
||||||
|
else:
|
||||||
|
files_deleted += 1
|
||||||
|
bytes_deleted += size
|
||||||
|
else:
|
||||||
|
print(f" [zachovat] {file_path} ({size:,} B)")
|
||||||
|
files_kept += 1
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f"Zkontrolováno: {files_checked} souborů")
|
||||||
|
print(f"Ke smazání: {files_deleted} souborů ({bytes_deleted / 1024**3:.2f} GB)")
|
||||||
|
print(f"Zachováno: {files_kept} souborů")
|
||||||
|
|
||||||
|
if not dry_run and files_deleted > 0:
|
||||||
|
print("\nOdstraňuji prázdné adresáře...")
|
||||||
|
removed = remove_empty_dirs(SCAN_DIR)
|
||||||
|
print(f"Odstraněno prázdných adresářů: {removed}")
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print("\n(Dry run — žádné změny nebyly provedeny)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
182
Seedbox/81 DeleteWhatWeHaveUltraCC.py
Normal file
182
Seedbox/81 DeleteWhatWeHaveUltraCC.py
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Projde /mnt/user/torrents/ultracc, pro každý soubor spočítá blake3
|
||||||
|
a porovná s tabulkou file_md5_index. Pokud je hash nalezen → soubor smaže.
|
||||||
|
Po smazání souborů odstraní prázdné adresáře.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import blake3
|
||||||
|
import pymysql
|
||||||
|
import paramiko
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONFIG
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
SCAN_DIR = "//tower/torrents/ultracc"
|
||||||
|
|
||||||
|
SSH_CONFIG = {
|
||||||
|
"hostname": "192.168.1.76",
|
||||||
|
"port": 22,
|
||||||
|
"username": "root",
|
||||||
|
"password": "7309208104",
|
||||||
|
}
|
||||||
|
|
||||||
|
ULTRACC_DIRS = [
|
||||||
|
"/mnt/user/Torrents/UltraCC",
|
||||||
|
"/mnt/user/Torrents/UltraCC1",
|
||||||
|
"/mnt/user/Torrents/UltraCC2",
|
||||||
|
]
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.76",
|
||||||
|
"port": 3306,
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "torrents",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
}
|
||||||
|
|
||||||
|
CHUNK_SIZE = 8 * 1024 * 1024 # 8 MB
|
||||||
|
DRY_RUN = True # True = pouze vypíše, nesmaže
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# HELPERS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def compute_blake3(path: Path) -> bytes:
|
||||||
|
"""Vrátí blake3 digest jako 32 raw bytes."""
|
||||||
|
h = blake3.blake3()
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
while True:
|
||||||
|
chunk = f.read(CHUNK_SIZE)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
h.update(chunk)
|
||||||
|
return h.digest()
|
||||||
|
|
||||||
|
|
||||||
|
def hash_in_db(cursor, digest: bytes):
|
||||||
|
"""Vrátí (host_name, full_path) prvního záznamu s daným hashem, nebo None."""
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT host_name, full_path FROM file_md5_index WHERE blake3 = %s AND host_name = 'tower1' AND full_path LIKE '/mnt/user/#ColdData/Porno/%%' LIMIT 1",
|
||||||
|
(digest,)
|
||||||
|
)
|
||||||
|
return cursor.fetchone() # None nebo (host_name, full_path)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_empty_dirs(root: str) -> int:
|
||||||
|
"""Rekurzivně smaže prázdné adresáře pod root. Vrátí počet smazaných."""
|
||||||
|
removed = 0
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root, topdown=False):
|
||||||
|
if dirpath == root:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
os.rmdir(dirpath)
|
||||||
|
print(f" [rmdir] {dirpath}")
|
||||||
|
removed += 1
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return removed
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MAIN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def set_ultracc_permissions():
|
||||||
|
"""Přes SSH nastaví na Tower chown nobody:users + chmod 777 pro všechny UltraCC adresáře."""
|
||||||
|
print("Nastavuji práva na Tower (UltraCC*)...")
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect(**SSH_CONFIG)
|
||||||
|
for d in ULTRACC_DIRS:
|
||||||
|
_, out, err = ssh.exec_command(
|
||||||
|
'chown -R nobody:users "%s" && chmod -R 777 "%s" && echo OK' % (d, d)
|
||||||
|
)
|
||||||
|
result = out.read().decode().strip()
|
||||||
|
error = err.read().decode().strip()
|
||||||
|
if result == "OK":
|
||||||
|
print(f" [OK] {d}")
|
||||||
|
else:
|
||||||
|
print(f" [CHYBA] {d}: {error}")
|
||||||
|
ssh.close()
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
dry_run = DRY_RUN
|
||||||
|
|
||||||
|
set_ultracc_permissions()
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print("=== DRY RUN — nic se nesmaže ===\n")
|
||||||
|
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
scan_root = Path(SCAN_DIR)
|
||||||
|
if not scan_root.exists():
|
||||||
|
print(f"CHYBA: Adresář neexistuje: {SCAN_DIR}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
files_checked = 0
|
||||||
|
files_deleted = 0
|
||||||
|
files_kept = 0
|
||||||
|
bytes_deleted = 0
|
||||||
|
|
||||||
|
for file_path in scan_root.rglob("*"):
|
||||||
|
if not file_path.is_file():
|
||||||
|
continue
|
||||||
|
|
||||||
|
files_checked += 1
|
||||||
|
size = file_path.stat().st_size
|
||||||
|
|
||||||
|
try:
|
||||||
|
digest = compute_blake3(file_path)
|
||||||
|
except OSError as e:
|
||||||
|
print(f" [CHYBA čtení] {file_path}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
db_match = hash_in_db(cursor, digest)
|
||||||
|
if db_match:
|
||||||
|
db_host, db_path = db_match
|
||||||
|
print(f" [SMAZAT] {file_path} ({size:,} B)")
|
||||||
|
print(f" ↳ originál v DB: [{db_host}] {db_path}")
|
||||||
|
if not dry_run:
|
||||||
|
try:
|
||||||
|
file_path.unlink()
|
||||||
|
files_deleted += 1
|
||||||
|
bytes_deleted += size
|
||||||
|
except OSError as e:
|
||||||
|
print(f" [CHYBA smazání] {file_path}: {e}")
|
||||||
|
else:
|
||||||
|
files_deleted += 1
|
||||||
|
bytes_deleted += size
|
||||||
|
else:
|
||||||
|
print(f" [zachovat] {file_path} ({size:,} B)")
|
||||||
|
files_kept += 1
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f"Zkontrolováno: {files_checked} souborů")
|
||||||
|
print(f"Ke smazání: {files_deleted} souborů ({bytes_deleted / 1024**3:.2f} GB)")
|
||||||
|
print(f"Zachováno: {files_kept} souborů")
|
||||||
|
|
||||||
|
if not dry_run and files_deleted > 0:
|
||||||
|
print("\nOdstraňuji prázdné adresáře...")
|
||||||
|
removed = remove_empty_dirs(SCAN_DIR)
|
||||||
|
print(f"Odstraněno prázdných adresářů: {removed}")
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print("\n(Dry run — žádné změny nebyly provedeny)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
182
Seedbox/82 DeleteWhatWeHaveUltraCC1.py
Normal file
182
Seedbox/82 DeleteWhatWeHaveUltraCC1.py
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Projde /mnt/user/torrents/ultracc, pro každý soubor spočítá blake3
|
||||||
|
a porovná s tabulkou file_md5_index. Pokud je hash nalezen → soubor smaže.
|
||||||
|
Po smazání souborů odstraní prázdné adresáře.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import blake3
|
||||||
|
import pymysql
|
||||||
|
import paramiko
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONFIG
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
SCAN_DIR = "//tower/torrents/ultracc1"
|
||||||
|
|
||||||
|
SSH_CONFIG = {
|
||||||
|
"hostname": "192.168.1.76",
|
||||||
|
"port": 22,
|
||||||
|
"username": "root",
|
||||||
|
"password": "7309208104",
|
||||||
|
}
|
||||||
|
|
||||||
|
ULTRACC_DIRS = [
|
||||||
|
"/mnt/user/Torrents/UltraCC",
|
||||||
|
"/mnt/user/Torrents/UltraCC1",
|
||||||
|
"/mnt/user/Torrents/UltraCC2",
|
||||||
|
]
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.76",
|
||||||
|
"port": 3306,
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "torrents",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
}
|
||||||
|
|
||||||
|
CHUNK_SIZE = 8 * 1024 * 1024 # 8 MB
|
||||||
|
DRY_RUN = False # True = pouze vypíše, nesmaže
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# HELPERS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def compute_blake3(path: Path) -> bytes:
|
||||||
|
"""Vrátí blake3 digest jako 32 raw bytes."""
|
||||||
|
h = blake3.blake3()
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
while True:
|
||||||
|
chunk = f.read(CHUNK_SIZE)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
h.update(chunk)
|
||||||
|
return h.digest()
|
||||||
|
|
||||||
|
|
||||||
|
def hash_in_db(cursor, digest: bytes):
|
||||||
|
"""Vrátí (host_name, full_path) prvního záznamu s daným hashem, nebo None."""
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT host_name, full_path FROM file_md5_index WHERE blake3 = %s AND host_name = 'tower1' AND full_path LIKE '/mnt/user/#ColdData/Porno/%%' LIMIT 1",
|
||||||
|
(digest,)
|
||||||
|
)
|
||||||
|
return cursor.fetchone() # None nebo (host_name, full_path)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_empty_dirs(root: str) -> int:
|
||||||
|
"""Rekurzivně smaže prázdné adresáře pod root. Vrátí počet smazaných."""
|
||||||
|
removed = 0
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root, topdown=False):
|
||||||
|
if dirpath == root:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
os.rmdir(dirpath)
|
||||||
|
print(f" [rmdir] {dirpath}")
|
||||||
|
removed += 1
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return removed
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MAIN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def set_ultracc_permissions():
|
||||||
|
"""Přes SSH nastaví na Tower chown nobody:users + chmod 777 pro všechny UltraCC adresáře."""
|
||||||
|
print("Nastavuji práva na Tower (UltraCC*)...")
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect(**SSH_CONFIG)
|
||||||
|
for d in ULTRACC_DIRS:
|
||||||
|
_, out, err = ssh.exec_command(
|
||||||
|
'chown -R nobody:users "%s" && chmod -R 777 "%s" && echo OK' % (d, d)
|
||||||
|
)
|
||||||
|
result = out.read().decode().strip()
|
||||||
|
error = err.read().decode().strip()
|
||||||
|
if result == "OK":
|
||||||
|
print(f" [OK] {d}")
|
||||||
|
else:
|
||||||
|
print(f" [CHYBA] {d}: {error}")
|
||||||
|
ssh.close()
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
dry_run = DRY_RUN
|
||||||
|
|
||||||
|
set_ultracc_permissions()
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print("=== DRY RUN — nic se nesmaže ===\n")
|
||||||
|
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
scan_root = Path(SCAN_DIR)
|
||||||
|
if not scan_root.exists():
|
||||||
|
print(f"CHYBA: Adresář neexistuje: {SCAN_DIR}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
files_checked = 0
|
||||||
|
files_deleted = 0
|
||||||
|
files_kept = 0
|
||||||
|
bytes_deleted = 0
|
||||||
|
|
||||||
|
for file_path in scan_root.rglob("*"):
|
||||||
|
if not file_path.is_file():
|
||||||
|
continue
|
||||||
|
|
||||||
|
files_checked += 1
|
||||||
|
size = file_path.stat().st_size
|
||||||
|
|
||||||
|
try:
|
||||||
|
digest = compute_blake3(file_path)
|
||||||
|
except OSError as e:
|
||||||
|
print(f" [CHYBA čtení] {file_path}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
db_match = hash_in_db(cursor, digest)
|
||||||
|
if db_match:
|
||||||
|
db_host, db_path = db_match
|
||||||
|
print(f" [SMAZAT] {file_path} ({size:,} B)")
|
||||||
|
print(f" ↳ originál v DB: [{db_host}] {db_path}")
|
||||||
|
if not dry_run:
|
||||||
|
try:
|
||||||
|
file_path.unlink()
|
||||||
|
files_deleted += 1
|
||||||
|
bytes_deleted += size
|
||||||
|
except OSError as e:
|
||||||
|
print(f" [CHYBA smazání] {file_path}: {e}")
|
||||||
|
else:
|
||||||
|
files_deleted += 1
|
||||||
|
bytes_deleted += size
|
||||||
|
else:
|
||||||
|
print(f" [zachovat] {file_path} ({size:,} B)")
|
||||||
|
files_kept += 1
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f"Zkontrolováno: {files_checked} souborů")
|
||||||
|
print(f"Ke smazání: {files_deleted} souborů ({bytes_deleted / 1024**3:.2f} GB)")
|
||||||
|
print(f"Zachováno: {files_kept} souborů")
|
||||||
|
|
||||||
|
if not dry_run and files_deleted > 0:
|
||||||
|
print("\nOdstraňuji prázdné adresáře...")
|
||||||
|
removed = remove_empty_dirs(SCAN_DIR)
|
||||||
|
print(f"Odstraněno prázdných adresářů: {removed}")
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print("\n(Dry run — žádné změny nebyly provedeny)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
473
Seedbox/Balík audioknih.torrent
Normal file
473
Seedbox/Balík audioknih.torrent
Normal file
File diff suppressed because one or more lines are too long
BIN
Seedbox/torrent_report.xlsx
Normal file
BIN
Seedbox/torrent_report.xlsx
Normal file
Binary file not shown.
Reference in New Issue
Block a user