This commit is contained in:
2025-12-29 22:51:13 +01:00
parent 64472e59ba
commit 1124a1fbe1
4 changed files with 311 additions and 110 deletions

View File

@@ -1,85 +1,103 @@
import os
import nntplib
import sabctools
import io
import os
import binascii
import re
import threading
from concurrent.futures import ThreadPoolExecutor
from dotenv import load_dotenv
from db import get_conn
# --- KONFIGURACE ---
INPUT_FILE = r"u:\PycharmProjects\NewsGroups\downloads\raw\part_001.raw"
OUTPUT_DIR = r"u:\PycharmProjects\NewsGroups\downloads\decoded"
# --- Konfigurace zůstává ---
load_dotenv()
EWEKA_USER = os.getenv("EWEKA_USER")
EWEKA_PASS = os.getenv("EWEKA_PASS")
EWEKA_HOST = "news.eweka.nl"
NEWSGROUP = 'alt.binaries.e-book.magazines'
SUBJECT_FILTER = '%PC Pro 2011-07.pdf%'
FINAL_PATH = os.path.join("FastLane/output", "Fast_Lane_Biker_Dec_2011.pdf")
MAX_WORKERS = 50
PART_FIXED_SIZE = 384000
file_lock = threading.Lock()
def ultimate_bomba_decoder():
if not os.path.exists(INPUT_FILE):
print(f"❌ Soubor nenalezen: {INPUT_FILE}")
def download_chunk(articles_subset):
"""
Tato funkce běží v jednom vlákně a obsluhuje JEDNO TRVALÉ spojení.
articles_subset je seznam tuplic (index, art_num).
"""
if not articles_subset:
return
print(f"📖 Načítám soubor...")
with open(INPUT_FILE, "rb") as f:
raw_data = f.read()
try:
# PŘIHLÁŠENÍ JEN JEDNOU NA ZAČÁTKU
server = nntplib.NNTP(EWEKA_HOST, user=EWEKA_USER, password=EWEKA_PASS)
server.group(NEWSGROUP)
# 1. OPRAVA SYNTAX WARNING A VYTAŽENÍ METADAT
# Používáme [0-9] místo \d pro odstranění varování v Pythonu 3.13
yend_match = re.search(b"=yend size=([0-9]+).*pcrc32=([0-9a-fA-F]+)", raw_data)
expected_size = 0
expected_crc_str = ""
if yend_match:
expected_size = int(yend_match.group(1))
expected_crc_str = yend_match.group(2).decode().lower()
print(f"🎯 Metadata nalezena: Očekávaná velikost={expected_size}, Očekávané CRC={expected_crc_str}")
# Otevřeme soubor pro zápis (v r+b módu)
with open(FINAL_PATH, "r+b") as f_out:
for index, art_num in articles_subset:
try:
resp, info = server.body(str(art_num))
stuffed_lines = [(b"." + l if l.startswith(b".") else l) for l in info.lines]
raw_body = b"\r\n".join(stuffed_lines)
# 2. KLÍČOVÁ OPRAVA (Čištění dat)
# Odstraníme prázdné znaky na začátku/konci a sjednotíme konce řádků na \r\n
processed_data = raw_data.strip()
# Tento trik zajistí, že i linuxové konce řádků budou pro yEnc správně \r\n
processed_data = processed_data.replace(b"\r\n", b"\n").replace(b"\n", b"\r\n")
wrapped = b"222 0 <id>\r\n" + raw_body + b"\r\n.\r\n"
decoder = sabctools.Decoder(len(wrapped))
decoder.process(io.BytesIO(wrapped).readinto(decoder))
res = next(decoder, None)
# 3. ZABALENÍ DO NNTP OBÁLKY
wrapped = b"222 0 <part1@id>\r\n" + processed_data + b"\r\n.\r\n"
if res and res.data:
current_offset = index * PART_FIXED_SIZE
# 4. DEKÓDOVÁNÍ (Sabctools 3.13 Streaming API)
decoder = sabctools.Decoder(len(wrapped))
buf = io.BytesIO(wrapped)
n = buf.readinto(decoder)
decoder.process(n)
# Zápis na specifický offset
with file_lock:
f_out.seek(current_offset)
f_out.write(res.data)
response = next(decoder, None)
print(f" [OK] Part {art_num} (Vlákno {threading.current_thread().name})")
except Exception as e:
print(f" [!] Chyba u článku {art_num}: {e}")
if response and response.data:
# 5. KONTROLA INTEGRITY (Vlastní výpočet CRC32)
# binascii.crc32 vrací integer, :08x ho převede na hexadecimální formát
vypoctene_crc_int = binascii.crc32(response.data)
vypoctene_crc_str = f"{vypoctene_crc_int:08x}".lower()
server.quit()
except Exception as e:
print(f" [!!!] Vlákno se nemohlo připojit: {e}")
real_size = len(response.data)
print("-" * 40)
print(f"📊 Kontrola integrity:")
print(f" Skutečná velikost: {real_size} (Očekáváno: {expected_size})")
print(f" Vypočítané CRC: {vypoctene_crc_str}")
print(f" Očekávané CRC: {expected_crc_str}")
def final_precision_downloader():
conn = get_conn()
cur = conn.cursor()
cur.execute("""
SELECT article_number FROM articles
WHERE newsgroup = %s AND metadata->>'subject' LIKE %s
ORDER BY article_number;
""", (NEWSGROUP, SUBJECT_FILTER))
articles = cur.fetchall()
cur.close()
conn.close()
if vypoctene_crc_str == expected_crc_str:
print("✅ BINGO! Soubor je 100% v pořádku.")
else:
print("⚠️ POZOR: CRC nesouhlasí, data mohou být poškozena.")
if not articles: return
# 6. ULOŽENÍ
if not os.path.exists(OUTPUT_DIR):
os.makedirs(OUTPUT_DIR)
# Příprava souboru (alokace místa)
# ... (zde ponechte váš kód pro zjištění total_size a truncate) ...
# Pro ukázku zkráceno:
total_size = len(articles) * PART_FIXED_SIZE # Přibližné, raději použijte váš výpočet z minula
with open(FINAL_PATH, "wb") as f:
f.truncate(total_size)
# Jméno z yEnc hlavičky: PC Pro 2011-07.pdf
out_name = response.file_name or "decoded_part.bin"
out_path = os.path.join(OUTPUT_DIR, out_name)
# ROZDĚLENÍ PRÁCE: Rozdělíme seznam článků na 5 částí
all_tasks = [(i, art[0]) for i, art in enumerate(articles)]
chunk_size = (len(all_tasks) + MAX_WORKERS - 1) // MAX_WORKERS
subsets = [all_tasks[i:i + chunk_size] for i in range(0, len(all_tasks), chunk_size)]
with open(out_path, "wb") as f_out:
f_out.write(response.data)
print(f"🚀 Startuji stahování s {len(subsets)} trvalými spoji...")
print(f"💾 Uloženo do: {out_path}")
print("-" * 40)
else:
print("❌ Chyba: Dekodér nevrátil žádná data. Zkontrolujte, zda je soubor kompletní.")
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
executor.map(download_chunk, subsets)
print("🏁 Hotovo.")
if __name__ == "__main__":
ultimate_bomba_decoder()
final_precision_downloader()