diff --git a/pruef_updater.py b/pruef_updater.py new file mode 100644 index 0000000..e1bebef --- /dev/null +++ b/pruef_updater.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- +"""Pruefstand: der Updater - vor allem die Faelle, in denen er NICHTS tun darf. + +Sein Bild (06.09.2026): + + "der updater schaut beim starten erst: ist auf dem git link eine neue + version mit exe? wenn ja, erst laden, ersetzen und dann starten." + "einmal pruefen auf vollstaendigkeit." + "wir werden den namen mit nummer fuehren - wenn er merkt, eh warte mal, + die heisst anders, dann neu machen" + +Ein Updater ist gefaehrlicher als das Programm, das er aktualisiert: Er +entscheidet ueber die Datei, die der Betrieb braucht. Deshalb wird hier weniger +geprueft, DASS er aktualisiert, als dass er es in den falschen Faellen NICHT +tut - und dass er nie etwas ueberschreibt. + +Ohne Netz: die Release-Liste wird eingesetzt, nicht geholt. +""" +import os +import shutil +import sys +import tempfile + +QUELLE = r"C:\claude\rechnungstool" +sys.path.insert(0, QUELLE) + +import updater as U # noqa: E402 + +fehler = [] + + +def pruefe(bedingung, text): + zeile = (" OK | " if bedingung else "FEHLER | ") + text + kod = sys.stdout.encoding or "utf-8" + print(zeile.encode(kod, "replace").decode(kod)) + if not bedingung: + fehler.append(text) + + +EXE = "Rechnungstool.exe" + +# ---- Versionen vergleichen --------------------------------------------- +pruefe(U.zerlegen("v1.6.0") == (1, 6, 0), "v1.6.0 wird zu (1, 6, 0)") +pruefe(U.zerlegen("") == (0,), "leere Angabe zaehlt als aelteste") +pruefe(U.zerlegen("kaputt") == (0,), "unlesbare Angabe zaehlt als aelteste") +pruefe(U.ist_neuer("v1.7.0", "1.6.9"), "1.7.0 ist neuer als 1.6.9 (nicht alphabetisch!)") +pruefe(not U.ist_neuer("v1.6.0", "1.6.0"), "Gleichstand ist NICHT neuer") +pruefe(U.ist_neuer("v1.10.0", "1.9.0"), "1.10.0 ist neuer als 1.9.0") + +# ---- Die Nummer im Dateinamen ------------------------------------------ +pruefe(U.dateiname(EXE, "v1.6.0") == "Rechnungstool-1.6.0.exe", + "aus Name und Tag wird Rechnungstool-1.6.0.exe") +pruefe(U.dateiname("Rechnungstool-1.5.0.exe", "v1.6.0") == "Rechnungstool-1.6.0.exe", + "eine schon vorhandene Nummer wird ersetzt, nicht angehaengt") +pruefe(U.basis_und_endung(EXE) == ("Rechnungstool", ".exe"), "Stamm und Endung sauber getrennt") + +# ---- Das passende Release finden --------------------------------------- +def release(tag, dateien, entwurf=False, vorab=True): + return {"tag_name": tag, "draft": entwurf, "prerelease": vorab, + "assets": [{"name": n, "size": g, + "browser_download_url": f"http://x/{tag}/{n}"} + for n, g in dateien]} + + +liste = [release("v1.5.0", [(EXE, 10)]), + release("v1.6.0", [(EXE, 20)]), + release("v1.5.5", [(EXE, 15)])] +treffer = U.passendes_release(liste, EXE) +pruefe(treffer and treffer[0]["tag_name"] == "v1.6.0", + "aus einer unsortierten Liste wird die neueste genommen") +pruefe(U.passendes_release([release("v9.9.9", [("anderes.exe", 5)])], EXE) is None, + "ein Release ohne passende Datei zaehlt nicht") +pruefe(U.passendes_release([release("v9.9.9", [(EXE, 5)], entwurf=True)], EXE) is None, + "ein Entwurf zaehlt nicht") +# Vorabversionen MUESSEN zaehlen - alle Fassungen sind so gekennzeichnet. +pruefe(U.passendes_release([release("v2.0.0", [(EXE, 30)], vorab=True)], EXE) is not None, + "Vorabversionen zaehlen mit (sonst faende der Updater nie etwas)") + +# ---- Vollstaendigkeit --------------------------------------------------- +tmp = tempfile.mkdtemp(prefix="upd_pruef_") +probe = os.path.join(tmp, "probe.bin") +with open(probe, "wb") as f: + f.write(b"A" * 1000) +summe = U.sha256(probe) + +gut, _ = U.vollstaendig(probe, 1000, summe) +pruefe(gut, "richtige Groesse und Pruefsumme werden angenommen") +gut, grund = U.vollstaendig(probe, 999, summe) +pruefe(not gut and "Groesse" in grund, f"falsche Groesse wird abgelehnt ({grund})") +gut, grund = U.vollstaendig(probe, 1000, "0" * 64) +pruefe(not gut and "Pruefsumme" in grund, f"falsche Pruefsumme wird abgelehnt ({grund})") +gut, _ = U.vollstaendig(probe, 1000, "") +pruefe(gut, "ohne Pruefsumme entscheidet die Groesse allein") +gut, _ = U.vollstaendig(os.path.join(tmp, "gibtsnicht"), 1000, "") +pruefe(not gut, "eine fehlende Datei ist nicht vollstaendig") + +# ---- Was liegt im Ordner? ---------------------------------------------- +ordner = os.path.join(tmp, "install") +os.makedirs(ordner) +for name, inhalt in (("Rechnungstool-1.5.0.exe", b"ALT"), + ("Rechnungstool-1.6.0.exe", b"AKTUELL"), + ("nichts-zu-tun.txt", b"x")): + with open(os.path.join(ordner, name), "wb") as f: + f.write(inhalt) + +fassung, pfad = U.neueste_installierte(ordner, EXE) +pruefe(fassung == "1.6.0", f"die neueste vorhandene Fassung wird am Namen erkannt ({fassung})") +pruefe(os.path.basename(pfad) == "Rechnungstool-1.6.0.exe", "und der Pfad stimmt") +pruefe(len(U.installierte(ordner, EXE)) == 2, "fremde Dateien im Ordner stoeren nicht") + +# ---- Der ganze Durchlauf, ohne Netz ------------------------------------- +meldungen = [] + + +def sagen(t): + meldungen.append(str(t)) + + +def alle_da(): + return sorted(os.path.basename(p) for _f, p in U.installierte(ordner, EXE)) + + +# 1. gleiche Fassung -> nichts laden, vorhandene starten +meldungen.clear() +start = U.durchlauf(ordner, "x/y", EXE, sagen=sagen, + holen_=lambda _r: [release("v1.6.0", [(EXE, 11)])]) +pruefe(os.path.basename(start) == "Rechnungstool-1.6.0.exe", + "gleiche Fassung: die vorhandene wird gestartet") +pruefe(alle_da() == ["Rechnungstool-1.5.0.exe", "Rechnungstool-1.6.0.exe"], + "und im Ordner hat sich nichts geaendert") + +# 2. aeltere Fassung im Netz -> nichts tun +start = U.durchlauf(ordner, "x/y", EXE, sagen=sagen, + holen_=lambda _r: [release("v1.5.0", [(EXE, 11)])]) +pruefe(os.path.basename(start) == "Rechnungstool-1.6.0.exe", + "aeltere Fassung im Netz: es wird nicht aktualisiert") + +# 3. kein Netz -> vorhandene starten und sagen, warum +meldungen.clear() + + +def wirft(_r): + raise OSError("kein Netz") + + +start = U.durchlauf(ordner, "x/y", EXE, sagen=sagen, holen_=wirft) +pruefe(os.path.basename(start) == "Rechnungstool-1.6.0.exe", + "ohne Netz wird die vorhandene Fassung gestartet") +pruefe(any("Kein Update" in m for m in meldungen), "und es wird gesagt, warum") + +# 4. neuere Fassung, aber Groesse passt nicht -> VERWERFEN +meldungen.clear() +falsch = release("v1.9.0", [(EXE, 999999)]) +falsch["assets"][0]["browser_download_url"] = "file:///" + probe.replace("\\", "/") +start = U.durchlauf(ordner, "x/y", EXE, sagen=sagen, holen_=lambda _r: [falsch]) +pruefe(any("VERWORFEN" in m for m in meldungen), "falsche Groesse: das Update wird verworfen") +pruefe(os.path.basename(start) == "Rechnungstool-1.6.0.exe", + "und die vorhandene Fassung wird gestartet") +pruefe(not os.path.exists(os.path.join(ordner, "Rechnungstool-1.9.0.exe")), + "die halbe Datei erreicht den Installationsordner NICHT - der wichtigste Punkt") + +# 5. neuere Fassung, alles stimmt -> als NEUE Datei dazu +meldungen.clear() +richtig = release("v1.9.0", [(EXE, 1000)]) +richtig["assets"][0]["browser_download_url"] = "file:///" + probe.replace("\\", "/") +start = U.durchlauf(ordner, "x/y", EXE, sagen=sagen, holen_=lambda _r: [richtig]) +pruefe(os.path.basename(start) == "Rechnungstool-1.9.0.exe", + "die neue Fassung wird gestartet") +pruefe(os.path.exists(os.path.join(ordner, "Rechnungstool-1.9.0.exe")), + "sie liegt als EIGENE Datei im Ordner") +pruefe(os.path.exists(os.path.join(ordner, "Rechnungstool-1.6.0.exe")), + "die vorige bleibt liegen - sie ist die Rueckfallebene") +pruefe(not os.path.exists(os.path.join(ordner, "Rechnungstool-1.5.0.exe")), + "die vorvorige wurde aufgeraeumt (es bleiben immer zwei)") + +buch = U.buch_lesen(ordner) +pruefe(buch.get("version") == "1.9.0", + f"der Updater fuehrt Buch ueber das, was er installiert hat ({buch})") + +# 6. gleich noch einmal -> nichts laden, es ist ja jetzt aktuell +meldungen.clear() +vorher = alle_da() +start = U.durchlauf(ordner, "x/y", EXE, sagen=sagen, holen_=lambda _r: [richtig]) +pruefe(any("Aktuell" in m for m in meldungen), + f"unmittelbar danach gilt es als aktuell ({meldungen})") +pruefe(alle_da() == vorher, "und es wird nichts noch einmal geladen") + +# 7. Buch verloren, Datei aber da -> nicht noch einmal laden +# Kommt vor, wenn jemand installiert.json loescht. Der Dateiname allein traegt +# die Entscheidung: die 1.9.0 liegt da, also wird sie nur gestartet. +meldungen.clear() +os.remove(os.path.join(ordner, U.BUCH)) +start = U.durchlauf(ordner, "x/y", EXE, sagen=sagen, holen_=lambda _r: [richtig]) +pruefe(os.path.basename(start) == "Rechnungstool-1.9.0.exe", + "auch ohne Buch wird die richtige Datei gestartet") +pruefe(not any("Wird geladen" in m for m in meldungen), + f"und nichts noch einmal geladen ({meldungen})") + +shutil.rmtree(tmp, ignore_errors=True) + +if fehler: + print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt") + sys.exit(1) +print("GRUEN | Der Updater aktualisiert - und laesst in den falschen Faellen die Finger davon") diff --git a/updater.py b/updater.py new file mode 100644 index 0000000..f06b06c --- /dev/null +++ b/updater.py @@ -0,0 +1,368 @@ +# -*- coding: utf-8 -*- +"""Der Updater - er ist der Starter. + +SEIN BILD (06.09.2026) +---------------------- + "der updater schaut beim starten erst: ist auf dem git link eine neue + version mit exe? wenn ja, erst laden, ersetzen und dann starten." + "einmal pruefen auf vollstaendigkeit." + "updater startet kurz als im ram und das main programm ist aus, sonst + kannst es nicht ersetzen." + +WARUM DAS DER RICHTIGE AUFBAU IST +--------------------------------- +Windows laesst eine laufende EXE nicht ueberschreiben. Ein Programm, das sich +selbst aktualisiert, muss deshalb Umwege gehen (Kopie im Temp, warten, bis der +eigene Prozess weg ist, tauschen). All das entfaellt, wenn der Updater das ist, +was der Nutzer startet: Er laeuft VOR dem Programm - die EXE ist gar nicht in +Benutzung, wenn sie ersetzt wird. + +DIE NUMMER STEHT IM DATEINAMEN (seine Idee, 06.09.2026) +------------------------------------------------------- + "wir werden den namen mit nummer fuehren - wenn er merkt, eh warte mal, die + heisst anders, dann neu machen" + +Die installierte Datei heisst `Rechnungstool-1.6.0.exe`. Daraus folgt dreierlei: + + * Es wird nie etwas UEBERSCHRIEBEN - eine neue Fassung ist eine neue Datei. + Das Sperrproblem von Windows gibt es damit nicht einmal theoretisch. + * Die alte Fassung bleibt liegen und ist die Rueckfallebene, ohne dass + jemand eine Sicherung anlegen muesste. + * Der Updater sieht AM NAMEN, was installiert ist. Er muss keiner Angabe + glauben, die auseinanderlaufen koennte. + +DIE REGEL, AN DER SOLCHE UPDATER SCHEITERN +------------------------------------------ +Sie schreiben einen halb geladenen Download in die Installation, und das +Programm startet nie wieder. Deshalb hier: in einen Zwischenordner laden, +GROESSE UND PRUEFSUMME gegen die Angaben des Releases halten, und erst dann in +den Installationsordner legen. + +AUFBAU +------ +Dieser Teil ist eine BIBLIOTHEK ohne Fenster und ohne Netzzwang: jede +Entscheidung ist eine eigene Funktion, die sich einzeln pruefen laesst +(pruef_updater.py). Das Startprogramm steht unten unter __main__. + +Diese Datei liegt in BEIDEN Programmen gleich. Wer sie aendert, kopiert sie +hinueber. +""" + +import hashlib +import json +import os +import re +import shutil +import subprocess +import sys +import tempfile +import urllib.request + +API = "https://git.pcore.de/api/v1/repos" + +# Wie die Buchfuehrung des Updaters heisst. Sie liegt neben der EXE und wird +# von IHM gefuehrt - nicht vom Programm. So kann nichts auseinanderlaufen: +# was hier steht, hat der Updater selbst installiert. +BUCH = "installiert.json" + + +# --------------------------------------------------------------------------- +# Versionen vergleichen +# --------------------------------------------------------------------------- +def zerlegen(fassung): + """'v1.6.0' oder '1.6.0' -> (1, 6, 0). Unlesbares wird zu (0,). + + Absichtlich nachsichtig: eine Nummer, die niemand versteht, darf kein + Update ausloesen - sie zaehlt als die aelteste denkbare. + """ + if not fassung: + return (0,) + teile = re.findall(r"\d+", str(fassung)) + return tuple(int(t) for t in teile) if teile else (0,) + + +def ist_neuer(dort, hier): + """Ist `dort` neuer als `hier`? Gleichstand ist NICHT neuer.""" + return zerlegen(dort) > zerlegen(hier) + + +# --------------------------------------------------------------------------- +# Das passende Release finden +# --------------------------------------------------------------------------- +def passendes_release(releases, exe_name): + """Das neueste Release, das eine Datei `exe_name` anhaengt. + + Vorabversionen zaehlen MIT - alle Fassungen dieser Programme sind als + Vorabversion gekennzeichnet, weil sie erprobt werden. Wer hier + `/releases/latest` benutzt, bekommt nichts: Forgejo laesst Vorabversionen + dort aus. Genau das ist beim Bauen aufgefallen. + + Entwuerfe (draft) zaehlen NICHT - die sind nicht veroeffentlicht. + """ + beste = None + for r in releases or []: + if r.get("draft"): + continue + anhang = next((a for a in r.get("assets") or [] + if a.get("name") == exe_name), None) + if not anhang: + continue + if beste is None or ist_neuer(r.get("tag_name"), beste[0].get("tag_name")): + beste = (r, anhang) + return beste + + +def summe_aus_release(release, exe_name): + """Die erwartete Pruefsumme aus dem Anhang `.sha256`, oder ''. + + Das Release haengt neben der EXE eine kleine Textdatei mit der Pruefsumme + an. Fehlt sie, wird nur die Groesse geprueft - dann sagt das der Updater + aber auch, statt so zu tun, als haette er geprueft. + """ + name = exe_name + ".sha256" + for a in release.get("assets") or []: + if a.get("name") == name: + return a.get("browser_download_url") or "" + return "" + + +# --------------------------------------------------------------------------- +# Laden und pruefen +# --------------------------------------------------------------------------- +def sha256(pfad): + h = hashlib.sha256() + with open(pfad, "rb") as f: + for brocken in iter(lambda: f.read(1 << 20), b""): + h.update(brocken) + return h.hexdigest() + + +def holen(url, ziel, melden=None): + """Datei laden. Gibt die Anzahl geladener Bytes zurueck.""" + with urllib.request.urlopen(url, timeout=60) as antwort, open(ziel, "wb") as raus: + gesamt = int(antwort.headers.get("Content-Length") or 0) + geladen = 0 + while True: + brocken = antwort.read(1 << 20) + if not brocken: + break + raus.write(brocken) + geladen += len(brocken) + if melden and gesamt: + melden(geladen, gesamt) + return geladen + + +def vollstaendig(pfad, erwartete_groesse, erwartete_summe=""): + """Ist die geladene Datei vollstaendig und unverfaelscht? + + Rueckgabe: (True, "") oder (False, Grund). Ein halb geladener Download darf + die Installation NIE erreichen - lieber gar kein Update als ein kaputtes. + """ + if not os.path.exists(pfad): + return False, "Die geladene Datei ist nicht da." + ist = os.path.getsize(pfad) + if erwartete_groesse and ist != erwartete_groesse: + return False, (f"Groesse stimmt nicht: {ist} Bytes geladen, " + f"{erwartete_groesse} erwartet.") + if erwartete_summe: + hat = sha256(pfad) + if hat.lower() != erwartete_summe.strip().lower(): + return False, "Pruefsumme stimmt nicht." + return True, "" + + +# --------------------------------------------------------------------------- +# Die Nummer steht im Dateinamen +# --------------------------------------------------------------------------- +def basis_und_endung(exe_name): + """'Rechnungstool.exe' -> ('Rechnungstool', '.exe').""" + stamm, endung = os.path.splitext(exe_name) + stamm = re.sub(r"-\d+(?:\.\d+)*$", "", stamm) # eine Nummer wieder abschneiden + return stamm, endung or ".exe" + + +def dateiname(exe_name, fassung): + """'Rechnungstool.exe' + 'v1.6.0' -> 'Rechnungstool-1.6.0.exe'.""" + stamm, endung = basis_und_endung(exe_name) + return f"{stamm}-{str(fassung).lstrip('v')}{endung}" + + +def installierte(ordner, exe_name): + """Was liegt im Ordner? Liste (Fassung, Pfad), neueste zuerst. + + Gelesen wird der DATEINAME - keine Angabe, der man glauben muesste. Eine + Datei ohne Nummer (aus der Zeit vor dieser Fassung) zaehlt als aelteste. + """ + stamm, endung = basis_und_endung(exe_name) + muster = re.compile(re.escape(stamm) + r"(?:-(\d+(?:\.\d+)*))?" + + re.escape(endung) + r"$", re.I) + gefunden = [] + try: + namen = os.listdir(ordner) + except OSError: + return [] + for name in namen: + treffer = muster.match(name) + if treffer: + gefunden.append((treffer.group(1) or "0", os.path.join(ordner, name))) + gefunden.sort(key=lambda x: zerlegen(x[0]), reverse=True) + return gefunden + + +def neueste_installierte(ordner, exe_name): + """(Fassung, Pfad) der neuesten vorhandenen Datei - oder ('0', '').""" + liste = installierte(ordner, exe_name) + return liste[0] if liste else ("0", "") + + +def aufraeumen(ordner, exe_name, behalten=2): + """Alte Fassungen wegraeumen, die neuesten `behalten` bleiben liegen. + + Nicht aus Ordnungsliebe: die vorletzte Fassung IST die Rueckfallebene. + Deshalb wird nie unter zwei geraeumt. + """ + liste = installierte(ordner, exe_name) + weg = [] + for _fassung, pfad in liste[max(2, behalten):]: + try: + os.remove(pfad) + weg.append(os.path.basename(pfad)) + except OSError: + pass + return weg + + +# --------------------------------------------------------------------------- +# Buchfuehrung +# --------------------------------------------------------------------------- +def buch_lesen(ordner): + pfad = os.path.join(ordner, BUCH) + try: + with open(pfad, encoding="utf-8") as f: + return json.load(f) + except (OSError, ValueError): + return {} + + +def buch_schreiben(ordner, daten): + pfad = os.path.join(ordner, BUCH) + try: + with open(pfad, "w", encoding="utf-8") as f: + json.dump(daten, f, ensure_ascii=False, indent=2) + return True + except OSError: + return False + + +def releases_holen(repository): + url = f"{API}/{repository}/releases?limit=20" + with urllib.request.urlopen(url, timeout=30) as antwort: + return json.loads(antwort.read().decode("utf-8")) + + +def text_holen(url): + with urllib.request.urlopen(url, timeout=30) as antwort: + return antwort.read().decode("utf-8", "replace").split()[0] + + +# --------------------------------------------------------------------------- +# Der Ablauf +# --------------------------------------------------------------------------- +def durchlauf(ordner, repository, exe_name, sagen=print, holen_=None): + """Ein Durchlauf. Gibt den Pfad der Datei zurueck, die gestartet werden soll. + + Nichts wird ueberschrieben: eine neue Fassung wird als eigene Datei + `-.exe` abgelegt. Die vorige bleibt liegen - sie ist die + Rueckfallebene, ohne dass jemand eine Sicherung anlegen muesste. + + `holen_` laesst sich fuer den Pruefstand ersetzen, damit er ohne Netz + auskommt. + """ + holen_ = holen_ or releases_holen + hier, hier_pfad = neueste_installierte(ordner, exe_name) + buch = buch_lesen(ordner) + # Buch und Dateiname zusammen: wer eine Datei umbenennt, verursacht + # hoechstens einen ueberfluessigen Download, keinen Schaden. + if ist_neuer(buch.get("version") or "0", hier): + hier = buch.get("version") + + try: + releases = holen_(repository) + except Exception as e: # noqa: BLE001 - ohne Netz wird einfach gestartet + sagen(f"Kein Update moeglich ({e}). Es wird die vorhandene Fassung gestartet.") + return hier_pfad + + treffer = passendes_release(releases, exe_name) + if not treffer: + sagen("Im Repository liegt keine passende Datei. Vorhandene Fassung wird gestartet.") + return hier_pfad + release, anhang = treffer + dort = release.get("tag_name") or "" + + if not ist_neuer(dort, hier): + sagen(f"Aktuell ({hier}). Kein Update noetig.") + return hier_pfad + + ziel = os.path.join(ordner, dateiname(exe_name, dort)) + if os.path.exists(ziel): + sagen(f"{os.path.basename(ziel)} liegt schon da. Es wird gestartet.") + return ziel + + sagen(f"Neue Fassung {dort} gefunden (vorhanden: {hier}). Wird geladen ...") + zwischen = tempfile.mkdtemp(prefix="update_") + geladen = os.path.join(zwischen, exe_name) + try: + holen(anhang["browser_download_url"], geladen) + + erwartete_summe = "" + url_summe = summe_aus_release(release, exe_name) + if url_summe: + try: + erwartete_summe = text_holen(url_summe) + except Exception: # noqa: BLE001 + sagen("Pruefsumme nicht ladbar - es wird nur die Groesse geprueft.") + else: + sagen("Dieses Release hat keine Pruefsumme - es wird nur die Groesse geprueft.") + + gut, grund = vollstaendig(geladen, anhang.get("size") or 0, erwartete_summe) + if not gut: + sagen(f"Das Update wurde VERWORFEN: {grund}") + sagen("Es wird die vorhandene Fassung gestartet.") + return hier_pfad + + # Erst jetzt in den Installationsordner - vorher lag nichts Halbes dort. + shutil.move(geladen, ziel) + buch_schreiben(ordner, {"version": dort.lstrip("v"), + "datei": os.path.basename(ziel), + "sha256": erwartete_summe, + "vorher": os.path.basename(hier_pfad) if hier_pfad else ""}) + weg = aufraeumen(ordner, exe_name) + if weg: + sagen("Alte Fassungen entfernt: " + ", ".join(weg)) + sagen(f"Aktualisiert auf {dort}.") + return ziel + finally: + shutil.rmtree(zwischen, ignore_errors=True) + + +def starten(pfad): + """Das Programm starten und den Updater beenden.""" + subprocess.Popen([pfad], close_fds=True) + + +if __name__ == "__main__": + import version + + ordner = os.path.dirname(os.path.abspath( + sys.executable if getattr(sys, "frozen", False) else __file__)) + + print(f"Updater - {version.REPOSITORY}") + zu_starten = durchlauf(ordner, version.REPOSITORY, version.EXE_NAME) + + if zu_starten and os.path.exists(zu_starten): + print("Starte", os.path.basename(zu_starten)) + starten(zu_starten) + else: + print(f"FEHLER: keine startbare Fassung von {version.EXE_NAME} in {ordner}") + input("Enter zum Schliessen ...") diff --git a/version.py b/version.py new file mode 100644 index 0000000..4da2e80 --- /dev/null +++ b/version.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +"""Die Fassung dieses Programms - an EINER Stelle. + +Wozu +---- +Ohne eine Nummer im Programm kann kein Updater vergleichen, ob das, was im +Repository liegt, neuer ist als das, was hier laeuft. Sie steht deshalb hier +und NUR hier; alles andere (Info-Dialog, Updater, Release) liest sie von hier. + +Regel +----- +Die Nummer wird beim Ausliefern erhoeht und entspricht dem Release-Tag ohne das +"v": Tag `v1.6.0` -> VERSION "1.6.0". Wer das eine ohne das andere aendert, +baut dem Updater eine Falle. +""" + +VERSION = "1.6.0" + +# Zu welchem Repository dieses Programm gehoert - der Updater sucht dort. +REPOSITORY = "TheMockTv/rechnungstool" + +# Wie die ausgelieferte Datei im Release heisst. +EXE_NAME = "Rechnungstool.exe"