From 01aa1249b9e2a390660571dc20dca45b6a79e699 Mon Sep 17 00:00:00 2001 From: "vladimir.buzalka" Date: Tue, 13 Jan 2026 16:42:40 +0100 Subject: [PATCH] z230 --- Mazání Library/10 První skript.py | 200 ++++++++++++++++++++++++++++++ Pandas/10 DuplicateTest.py | 67 ++++++++++ 2 files changed, 267 insertions(+) create mode 100644 Mazání Library/10 První skript.py create mode 100644 Pandas/10 DuplicateTest.py diff --git a/Mazání Library/10 První skript.py b/Mazání Library/10 První skript.py new file mode 100644 index 0000000..d381f82 --- /dev/null +++ b/Mazání Library/10 První skript.py @@ -0,0 +1,200 @@ +import os +import sys +import pymysql +import pymysql.cursors + +# ================= KONFIGURACE ================= + +# --- BEZPEČNOSTNÍ POJISTKA --- +# True = POUZE VÝPIS (nic se nesmaže, databáze se nezmění) +# False = OSTRÝ REŽIM (maže soubory i záznamy v DB!) +DRY_MODE = True + +# 1. Přístup k MySQL +DB_CONFIG = { + 'host': '192.168.1.76', + 'port': 3307, + 'user': 'root', + 'password': 'Vlado9674+', + 'db': 'torrents', + 'charset': 'utf8mb4', + 'autocommit': True +} + +TABULKA = "file_md5_index" + +# 2. Mapování cest +SERVER_PREFIX = "/mnt/user/Library" +# Používáme 'r' pro raw string, aby se zpětná lomítka chápala správně +LOCAL_PREFIX = r"\\tower1\#library" + + +# =============================================== + +def get_connection(): + return pymysql.connect( + cursorclass=pymysql.cursors.DictCursor, + **DB_CONFIG + ) + + +def convert_path(db_path): + """Převede cestu z Linux serveru na lokální cestu Windows.""" + if db_path.startswith(SERVER_PREFIX): + relative_path = db_path[len(SERVER_PREFIX):] + # Ořízneme počáteční lomítka z relativní cesty, aby fungoval join + relative_path = relative_path.lstrip("/").lstrip("\\") + # Spojí cesty a opraví lomítka + local_path = os.path.join(LOCAL_PREFIX, relative_path) + return os.path.normpath(local_path) + return None + + +def step_1_mark_duplicates(): + print(f"\n--- KROK 1: Hledání duplicit v DB (DRY_MODE={DRY_MODE}) ---") + + try: + conn = get_connection() + with conn.cursor() as cursor: + if DRY_MODE: + # V DRY_MODE jen počítáme, co bychom označili (neprovádíme UPDATE) + sql = f""" + SELECT COUNT(*) as pocet + FROM {TABULKA} t1 + JOIN {TABULKA} t2 ON t1.blake3 = t2.blake3 + WHERE t1.host_name = 'TOWER1' + AND t2.host_name = 'SYNOLOGY' + AND (t1.to_delete IS NULL OR t1.to_delete = 0); + """ + cursor.execute(sql) + result = cursor.fetchone() + affected = result['pocet'] + print(f"[DRY-RUN] Našel jsem {affected} shodných záznamů (DB nebude změněna).") + else: + # V OSTRÉM režimu provádíme UPDATE + sql = f""" + UPDATE {TABULKA} t1 + JOIN {TABULKA} t2 ON t1.blake3 = t2.blake3 + SET t1.to_delete = 1 + WHERE t1.host_name = 'TOWER' + AND t2.host_name = 'SYNOLOGY' + AND (t1.to_delete IS NULL OR t1.to_delete = 0); + """ + print("Provádím UPDATE záznamů v databázi...") + cursor.execute(sql) + affected = cursor.rowcount + conn.commit() + print(f"Hotovo. Označeno {affected} záznamů ke smazání.") + + conn.close() + return affected + + except pymysql.MySQLError as e: + print(f"Chyba MySQL při označování: {e}") + sys.exit(1) + + +def step_2_delete_files(): + print(f"\n--- KROK 2: Mazání souborů (DRY_MODE={DRY_MODE}) ---") + + try: + conn = get_connection() + files_to_process = [] + + with conn.cursor() as cursor: + print("Stahuji seznam souborů...") + + if DRY_MODE: + # V DRY_MODE nemůžeme hledat podle 'to_delete=1' (protože jsme nic neoznačili), + # takže musíme použít JOIN dotaz přímo pro simulaci výpisu. + sql = f""" + SELECT t1.id, t1.full_path + FROM {TABULKA} t1 + JOIN {TABULKA} t2 ON t1.blake3 = t2.blake3 + WHERE t1.host_name = 'TOWER' + AND t2.host_name = 'SYNOLOGY' + AND (t1.to_delete IS NULL OR t1.to_delete = 0) + """ + else: + # V OSTRÉM režimu bereme to, co jsme v kroku 1 označili + sql = f"SELECT id, full_path FROM {TABULKA} WHERE host_name = 'TOWER' AND to_delete = 1" + + cursor.execute(sql) + files_to_process = cursor.fetchall() + + count = len(files_to_process) + print(f"Nalezeno {count} souborů.") + + if count == 0: + print("Žádné soubory k zpracování. Konec.") + return + + # V ostrém režimu se zeptáme na potvrzení + if not DRY_MODE: + confirm = input(f"-> [POZOR] Opravdu chcete SMAZAT {count} souborů? (napište 'ano'): ") + if confirm.lower() != 'ano': + print("Operace zrušena.") + return + else: + print("-" * 40) + print("VÝPIS SOUBORŮ, KTERÉ BY BYLY SMAZÁNY:") + print("-" * 40) + + deleted_counter = 0 + errors = 0 + + for row in files_to_process: + db_id = row['id'] + server_path = row['full_path'] + local_path = convert_path(server_path) + + if not local_path: + print(f"[SKIP PATH] Nesedí prefix: {server_path}") + continue + + # --- LOGIKA DRY RUN vs REAL --- + if DRY_MODE: + # Pouze výpis + print(f"[DRY-RUN] Bylo by smazáno: {local_path}") + deleted_counter += 1 + else: + # Ostré mazání + try: + if os.path.exists(local_path): + os.remove(local_path) + print(f"[OK SMAZÁNO] {local_path}") + + # Smazání z DB + with conn.cursor() as del_cursor: + del_sql = f"DELETE FROM {TABULKA} WHERE id = %s" + del_cursor.execute(del_sql, (db_id,)) + conn.commit() + deleted_counter += 1 + else: + print(f"[NENÍ NA DISKU] Mažu jen z DB: {local_path}") + with conn.cursor() as del_cursor: + del_sql = f"DELETE FROM {TABULKA} WHERE id = %s" + del_cursor.execute(del_sql, (db_id,)) + conn.commit() + deleted_counter += 1 + + except OSError as e: + print(f"[CHYBA OS] {local_path}: {e}") + errors += 1 + except pymysql.MySQLError as e: + print(f"[CHYBA DB] ID {db_id}: {e}") + + conn.close() + print("-" * 30) + if DRY_MODE: + print(f"DRY RUN DOKONČEN. Zobrazena simulace pro {deleted_counter} souborů.") + else: + print(f"HOTOVO. Úspěšně smazáno: {deleted_counter}, Chyby: {errors}") + + except pymysql.MySQLError as e: + print(f"Kritická chyba DB: {e}") + + +if __name__ == "__main__": + step_1_mark_duplicates() + step_2_delete_files() \ No newline at end of file diff --git a/Pandas/10 DuplicateTest.py b/Pandas/10 DuplicateTest.py new file mode 100644 index 0000000..3c29bb4 --- /dev/null +++ b/Pandas/10 DuplicateTest.py @@ -0,0 +1,67 @@ +import pandas as pd +from sqlalchemy import create_engine +import time + +# --- KONFIGURACE --- +db_user = 'root' +db_pass = 'Vlado9674+' +db_host = '192.168.1.76' +db_port = '3307' +db_name = 'torrents' # <--- ZDE DOPLNIT NÁZEV DATABÁZE + +# --- PŘIPOJENÍ --- +connection_string = f'mysql+mysqlconnector://{db_user}:{db_pass}@{db_host}:{db_port}/{db_name}' +engine = create_engine(connection_string) + +# SQL dotaz - vybíráme i full_path, abychom mohli v Pandas ukázat příklad cesty +# POZOR: Načítání 5.8M textových řetězců (full_path) zabere dost RAM (odhadem 2-4 GB). +query = """ +SELECT id, blake3, file_size, full_path +FROM file_md5_index FORCE INDEX (idx_full_path_prefix) +WHERE host_name='Tower1' AND full_path LIKE '/mnt/user/#Library%' +""" + +print("1. Začínám stahovat data z MySQL do RAM...") +start_load = time.time() + +try: + # Stáhnutí dat + df = pd.read_sql(query, engine) + end_load = time.time() + print(f"-> Data stažena za: {end_load - start_load:.2f} sekund") + print(f"-> Počet řádků v paměti: {len(df)}") + + print("\n2. Začínám hledat duplicity (Pandas GroupBy)...") + start_process = time.time() + + # Logika hledání duplicit + # Najdeme jen ty, co mají duplicitní hash + duplicity = df[df.duplicated(subset=['blake3'], keep=False)] + + if not duplicity.empty: + # Seskupení + vysledek = duplicity.groupby('blake3').agg({ + 'file_size': 'first', # Velikost souboru (předpokládáme stejnou pro stejný hash) + 'id': 'count', # Počet výskytů + 'full_path': lambda x: x.iloc[0] # Ukázka první cesty (rychlejší než 'first') + }).rename(columns={'id': 'pocet_kopii'}) + + # Filtrujeme jen ty, co mají skutečně více kopií a seřadíme podle velikosti * počet kopií + # (Chceme vidět, kde plýtváme nejvíc místa) + vysledek['celkove_plytvani'] = vysledek['file_size'] * (vysledek['pocet_kopii'] - 1) + vysledek = vysledek.sort_values('celkove_plytvani', ascending=False) + + end_process = time.time() + print(f"-> Zpracováno za: {end_process - start_process:.4f} sekund") + + print("\n--- TOP 20 NEJVĚTŠÍCH DUPLICIT ---") + # Zobrazíme hash, počet kopií, velikost jednoho souboru a ukázku cesty + print(vysledek[['pocet_kopii', 'file_size', 'full_path']].head(20)) + + print(f"\nCelkem nalezeno {len(vysledek)} unikátních souborů, které mají duplicity.") + else: + print("Nebyly nalezeny žádné duplicity.") + +except Exception as e: + print(f"\nCHYBA: {e}") + print("Zkontrolujte prosím název databáze a jestli máte dost paměti RAM.") \ No newline at end of file