Files
pfand_PKG/PfandApplication/updater.py

256 lines
10 KiB
Python

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://rattatwinko.servecounterstrike.com/gitea/rattatwinko/pfand_PKG/archive/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(
"<Configure>",
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("<Double-Button-1>", 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()