import tkinter as tk from tkinter import ttk, messagebox import os import requests import hashlib from zipfile import ZipFile import io import shutil import tempfile import traceback import threading GITHUB_REPO_ZIP = "https://github.com/ZockerKatze/pfand_PKG/archive/refs/heads/main.zip" IGNORED_FILES = {"key.py"} class GitHubUpdater(tk.Toplevel): def __init__(self, master=None): super().__init__(master) self.title("🔄 Pfand Updater") self.geometry("800x600") self.configure(bg="#ffffff") self.local_dir = os.getcwd() self.file_differences = [] self.structure = {} self.current_view = "root" self._setup_style() self._build_ui() # Run update check in background threading.Thread(target=self.check_for_updates, daemon=True).start() def _setup_style(self): style = ttk.Style(self) style.theme_use('clam') style.configure("TLabel", font=("Segoe UI", 11), background="#ffffff") style.configure("TButton", font=("Segoe UI", 10), padding=6, relief="flat", borderwidth=0) style.map("TButton", background=[("active", "#e0e0e0")]) style.configure("Header.TLabel", font=("Segoe UI", 20, "bold"), background="#ffffff", foreground="#333") style.configure("Status.TLabel", font=("Segoe UI", 12), background="#ffffff", foreground="#555") style.configure("Treeview", font=("Segoe UI", 10)) style.configure("TFrame", background="#ffffff") def _build_ui(self): header = ttk.Label(self, text="Pfand Updater", style="Header.TLabel") header.pack(pady=(20, 5)) self.status_label = ttk.Label(self, text="🔍 Suche nach Updates...", style="Status.TLabel") self.status_label.pack(pady=(0, 10)) self.frame = ttk.Frame(self) self.frame.pack(expand=True, fill="both", padx=20, pady=10) self.canvas = tk.Canvas(self.frame, bg="#fafafa", bd=0, highlightthickness=0) self.scrollbar = ttk.Scrollbar(self.frame, orient="vertical", command=self.canvas.yview) self.scrollable_frame = ttk.Frame(self.canvas) self.scrollable_frame.bind( "", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")) ) self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") self.canvas.configure(yscrollcommand=self.scrollbar.set) self.canvas.pack(side="left", fill="both", expand=True) self.scrollbar.pack(side="right", fill="y") button_frame = ttk.Frame(self) button_frame.pack(pady=15) self.back_button = ttk.Button(button_frame, text="⬅️ Zurück", command=self.show_root_view) self.back_button.pack(side="left", padx=10) self.back_button.pack_forget() self.update_button = ttk.Button(button_frame, text="⬆️ Dateien aktualisieren", command=self.perform_update, state='disabled') self.update_button.pack(side="left", padx=10) self.toggle_debug_btn = ttk.Button(self, text="🐞 Fehlerdetails anzeigen", command=self.toggle_debug_output) self.toggle_debug_btn.pack() self.toggle_debug_btn.pack_forget() self.debug_output = tk.Text(self, height=8, bg="#f5f5f5", font=("Courier", 9)) self.debug_output.pack(fill="x", padx=20, pady=(0, 10)) self.debug_output.pack_forget() self.debug_visible = False def toggle_debug_output(self): self.debug_visible = not self.debug_visible if self.debug_visible: self.debug_output.pack() self.toggle_debug_btn.config(text="🔽 Fehlerdetails verbergen") else: self.debug_output.pack_forget() self.toggle_debug_btn.config(text="🐞 Fehlerdetails anzeigen") def show_root_view(self): self.current_view = "root" self.back_button.pack_forget() self.display_structure(self.structure) def display_structure(self, struct, parent_path=""): for widget in self.scrollable_frame.winfo_children(): widget.destroy() for name, content in sorted(struct.items()): full_path = os.path.join(parent_path, name) lbl = ttk.Label(self.scrollable_frame, text=f"📁 {name}" if isinstance(content, dict) else f"📄 {name}", style="TLabel") if isinstance(content, dict): lbl.bind("", lambda e, p=full_path: self.open_folder(p)) lbl.pack(fill="x", padx=20, pady=6, anchor="w") def open_folder(self, folder_path): self.current_view = folder_path self.back_button.pack() parts = folder_path.split(os.sep) subtree = self.structure for part in parts: subtree = subtree.get(part, {}) self.display_structure(subtree, folder_path) def check_for_updates(self): try: self.status_label.config(text="⬇️ Lade Update herunter...", foreground="#ffb300") self.update_idletasks() response = requests.get(GITHUB_REPO_ZIP) with ZipFile(io.BytesIO(response.content)) as zip_file: temp_dir = tempfile.mkdtemp() zip_file.extractall(temp_dir) extracted_path = os.path.join(temp_dir, os.listdir(temp_dir)[0]) self.file_differences = self.compare_directories(extracted_path, self.local_dir) if self.file_differences: self.structure = self.build_structure(self.file_differences) self.status_label.config(text="⚠️ Updates verfügbar", foreground="#e53935") self.display_structure(self.structure) self.update_button.config(state='normal') else: self.status_label.config(text="✅ Alles ist aktuell", foreground="#43a047") except Exception: self.status_label.config(text="❌ Fehler beim Laden", foreground="#e53935") self.toggle_debug_btn.pack() self.debug_output.insert("1.0", traceback.format_exc()) def compare_directories(self, src_dir, dest_dir): differences = [] for root, _, files in os.walk(src_dir): for file in files: if file in IGNORED_FILES: continue src_path = os.path.join(root, file) rel_path = os.path.relpath(src_path, src_dir) dest_path = os.path.join(dest_dir, rel_path) if not os.path.exists(dest_path) or not self.files_match(src_path, dest_path): differences.append(rel_path) return differences def build_structure(self, file_paths): tree = {} for path in file_paths: parts = path.split(os.sep) d = tree for part in parts[:-1]: d = d.setdefault(part, {}) d[parts[-1]] = path return tree def files_match(self, file1, file2): return self.hash_file(file1) == self.hash_file(file2) def hash_file(self, filepath): hash_md5 = hashlib.md5() with open(filepath, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) return hash_md5.hexdigest() def perform_update(self): self.update_button.config(state='disabled') self.status_label.config(text="🚧 Update läuft...", foreground="#fb8c00") self.update_idletasks() try: response = requests.get(GITHUB_REPO_ZIP) with ZipFile(io.BytesIO(response.content)) as zip_file: temp_dir = tempfile.mkdtemp() zip_file.extractall(temp_dir) extracted_path = os.path.join(temp_dir, os.listdir(temp_dir)[0]) for rel_path in self.file_differences: src_path = os.path.join(extracted_path, rel_path) dest_path = os.path.join(self.local_dir, rel_path) os.makedirs(os.path.dirname(dest_path), exist_ok=True) shutil.copy2(src_path, dest_path) messagebox.showinfo("✅ Aktualisiert", "Dateien wurden erfolgreich aktualisiert.") self.destroy() except Exception as e: messagebox.showerror("❌ Fehler", str(e)) self.toggle_debug_btn.pack() self.debug_output.insert("1.0", traceback.format_exc()) @staticmethod def files_match_static(file1, file2): def hash_file(filepath): hash_md5 = hashlib.md5() with open(filepath, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) return hash_md5.hexdigest() return hash_file(file1) == hash_file(file2) def run_silent_update(master=None): try: response = requests.get(GITHUB_REPO_ZIP) with ZipFile(io.BytesIO(response.content)) as zip_file: temp_dir = tempfile.mkdtemp() zip_file.extractall(temp_dir) extracted_path = os.path.join(temp_dir, os.listdir(temp_dir)[0]) file_differences = [] for root_dir, _, files in os.walk(extracted_path): for file in files: if file in IGNORED_FILES: continue src_path = os.path.join(root_dir, file) rel_path = os.path.relpath(src_path, extracted_path) dest_path = os.path.join(os.getcwd(), rel_path) if not os.path.exists(dest_path) or not GitHubUpdater.files_match_static(src_path, dest_path): file_differences.append(rel_path) if file_differences: result = messagebox.askyesno("🔄 Update verfügbar", "Es sind Updates verfügbar. Möchten Sie aktualisieren?") if result: updater = GitHubUpdater(master) updater.grab_set() else: print("Keine Updates verfügbar.") except Exception as e: print(f"Update-Check-Fehler: {e}") def open_updater(): root = tk.Tk() root.withdraw() updater = GitHubUpdater(root) updater.mainloop()