import subprocess import json import os from pathlib import Path from datetime import datetime import zipfile import time import traceback from EmailMessagingGraph import send_mail # ============================================================ # CONFIG # ============================================================ GBAK = r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe" FB_USER = "SYSDBA" FB_PASS = "masterkey" FB_PORT = "3050" SRC_DIR = Path(r"c:\medicusext") BACKUP_DIR = Path(r"U:\medicusbackup") MAIL_TO = "vladimir.buzalka@buzalka.cz" CHUNK = 8 * 1024 * 1024 # 8 MB # ============================================================ # MAIN # ============================================================ def main(): BACKUP_DIR.mkdir(parents=True, exist_ok=True) now = datetime.now() ts = now.strftime("%Y-%m-%d_%H-%M-%S") # Find all FDB files (case-insensitive) fdb_files = sorted(SRC_DIR.glob("MEDICUS_FILES_*.fdb")) fdb_upper = sorted(SRC_DIR.glob("MEDICUS_FILES_*.FDB")) fdb_all = sorted( set(fdb_files + fdb_upper), key=lambda p: p.name.lower(), ) backed_up = [] errors = [] fbk_paths = [] # FBK files to be zipped together # -------------------------------------------------------- # 1) GBAK all databases # -------------------------------------------------------- for fdb in fdb_all: name = fdb.stem fbk = BACKUP_DIR / f"{name}_{ts}.fbk" log = BACKUP_DIR / f"{name}_{ts}.log" result = { "file": fdb.name, "ok": False, "fbk_size": 0, "zip_size": 0, "t_gbak": 0, "t_zip": 0, "error": None, } try: print(f"GBAK: {fdb.name} ... ", end="", flush=True) t0 = time.time() db_conn = f"localhost/{FB_PORT}:{fdb}" cmd = [ GBAK, "-b", "-user", FB_USER, "-pas", FB_PASS, db_conn, str(fbk), "-v", ] with open(log, "w", encoding="utf-8") as f: subprocess.run( cmd, stdout=f, stderr=subprocess.STDOUT, check=True, ) result["t_gbak"] = time.time() - t0 result["fbk_size"] = fbk.stat().st_size print(f"OK ({result['t_gbak']:.0f}s)") # Delete log, keep FBK for zipping log.unlink() result["ok"] = True fbk_paths.append((fbk, result)) backed_up.append(result) except Exception: result["error"] = traceback.format_exc() errors.append(result) for f in (fbk, log): if f.exists(): f.unlink() # -------------------------------------------------------- # 2) ZIP all FBK files into one archive # -------------------------------------------------------- total_zip_size = 0 if fbk_paths: zip_path = BACKUP_DIR / f"MEDICUS_FILES_{ts}.zip" print(f"\nZIP: {zip_path.name}") t_zip_start = time.time() # Calculate total size for progress total_fbk_size = sum(fbk.stat().st_size for fbk, _ in fbk_paths) total_processed = 0 with zipfile.ZipFile( zip_path, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9, ) as zf: for fbk, result in fbk_paths: zi = zipfile.ZipInfo(fbk.name) zi.compress_type = zipfile.ZIP_DEFLATED with zf.open(zi, "w", force_zip64=True) as z: with open(fbk, "rb") as src: while buf := src.read(CHUNK): z.write(buf) total_processed += len(buf) pct = total_processed * 100 / total_fbk_size print( f"\r {fbk.name}: {pct:6.2f}%", end="", flush=True, ) print() t_zip_total = time.time() - t_zip_start total_zip_size = zip_path.stat().st_size print(f"ZIP OK ({t_zip_total:.0f}s, {total_zip_size/1024/1024:.1f} MB)") # Fill zip_size into each result and delete FBK files for fbk, result in fbk_paths: result["zip_size"] = total_zip_size fbk.unlink() # -------------------------------------------------------- # Build report # -------------------------------------------------------- report = [] report.append(f"Backup externi DB - {now.strftime('%d.%m.%Y %H:%M')}") report.append(f"Celkem souboru: {len(fdb_all)}") report.append(f"Zalohovano: {len(backed_up)}") report.append(f"Chyby: {len(errors)}") report.append("") if backed_up: report.append("--- Backed up ---") total_fbk_mb = sum(r["fbk_size"] for r in backed_up) / 1024 / 1024 for r in backed_up: report.append( f" {r['file']}: " f"FBK {r['fbk_size']/1024/1024:.1f} MB " f"(gbak {r['t_gbak']:.0f}s)" ) report.append(f" Total FBK: {total_fbk_mb:.1f} MB -> ZIP: {total_zip_size/1024/1024:.1f} MB") report.append("") if errors: report.append("--- ERRORS ---") for r in errors: report.append(f" {r['file']}: {r['error']}") report.append("") # Send email has_errors = len(errors) > 0 subject = ( f"{'X' if has_errors else 'OK'} " f"MEDICUS externi DB - " f"backup {len(backed_up)}/{len(fdb_all)}" f"{f', {len(errors)} errors' if has_errors else ''}" ) send_mail(MAIL_TO, subject, "\n".join(report)) print("\n" + "\n".join(report)) if errors: raise RuntimeError(f"{len(errors)} backup(s) failed") if __name__ == "__main__": main()