feat(windows): Arbeitsordner im Setup, Beta-Key auf Knopfdruck
Ampel / ampel (push) Successful in 1m18s
Ampel / ampel (push) Successful in 1m18s
Zwei Wuensche des Commanders vom 28.08.2026.
## 1. „kannst du noch einbauen das man den arbeitsordner usw. in den
## Einstellungen setzen kann und direkt im setup?"
In den Einstellungen ging es seit 35370fd (freies Pfadfeld plus die Laufwerke
als Ein-Klick-Wahl). Im SETUP wurde bisher nur nach Programmordner und Ablage
gefragt — der Arbeitsordner tauchte erst auf, wenn schon installiert war.
Das ist die falsche Reihenfolge: Der Roh-Rip einer 4K-UHD ist bis zu 100 GB
gross. Wer erst NACH der ersten vollen Platte erfaehrt, wo die liegen, hat es
zu spaet erfahren — genau so lief am 25.07.2026 die VM-Platte voll.
Neu ist auch ein Waechter, der alle drei Arten von halb angeschlossenem Feld
faengt (kein Eingabefeld, keine Vorbelegung, wird beim Installieren nicht
mitgeschickt). Der dritte Fall ist der gemeinste: Man waehlt etwas, es
passiert nichts, und niemand sagt warum.
## 2. „der könnte theoretisch auch automatisch ausgelesen werden, ich glaube
## das web rippy kann das"
Er hatte recht. `makemkv_key.refresh_loop()` laeuft seit jeher taeglich mit
(main.py, Startereignis). GEMESSEN am 28.08.2026: Forum antwortet in 3,3 s,
gueltiger Key mit 62 Zeichen, und in seinen Einstellungen lag bereits einer.
Es war nur nichts davon zu sehen. Kein Knopf, keine Meldung, keine Zeitangabe
— im Feld stand ein Key, und ob der von Hand kam oder von selbst, wusste
niemand. Eine Automatik, die man nicht sehen kann, ist fuer den Benutzer
keine.
Dazu kam eine echte Luecke: Die Schleife startet 60 Sekunden nach dem Server.
Wer gleich nach dem Einrichten eine Blu-ray einlegt, rippt ohne Key — MakeMKV
faellt in den 30-Tage-Testmodus. Das Setup holt ihn jetzt selbst.
Der geholte Key wandert NICHT ueber die Leitung zurueck: Er steht in den
Einstellungen, und ein zweiter Weg zu demselben Wert ist ein zweiter Weg, ihn
zu verlieren.
Rechtlich unveraendert: oeffentliche Beta-LIZENZ der Software, KEIN
Disc-Schluessel. Rippy liefert und verteilt keine Disc-Schluessel.
803 Tests gruen, ruff sauber.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d0281f7a1b
commit
678fe276f9
@@ -403,6 +403,13 @@ def vorgaben() -> dict:
|
||||
return {
|
||||
"ziel": reg.standard_installationsordner(),
|
||||
"ablage": betrieb.ablage_vorgabe({}, container=False, system="windows"),
|
||||
# Der Arbeitsordner gehoert ins Setup, nicht erst in die Einstellungen
|
||||
# (Commander-Wunsch 28.08.2026). Grund: Der Roh-Rip einer 4K-UHD ist
|
||||
# bis zu 100 GB gross. Wer das erst NACH der ersten vollen Platte
|
||||
# erfaehrt, hat es zu spaet erfahren — genau so lief am 25.07.2026
|
||||
# die VM-Platte voll.
|
||||
"arbeit": betrieb.arbeits_vorgabe({}, container=False,
|
||||
plattform_name="win32"),
|
||||
"port": STANDARD_PORT,
|
||||
"autostart": True,
|
||||
"verknuepfungen": True,
|
||||
|
||||
@@ -221,10 +221,18 @@ class Bruecke:
|
||||
starten_danach=False,
|
||||
verknuepfen=bool(optionen.get("verknuepfungen", True)),
|
||||
)
|
||||
orte = {}
|
||||
if optionen.get("ablage"):
|
||||
orte["outputDir"] = optionen["ablage"]
|
||||
# Der Arbeitsordner gehoert mit ins Setup (Commander 28.08.2026).
|
||||
# Leer heisst „nimm die Vorgabe" — dann steht hier nichts, und der
|
||||
# Betrieb rechnet ihn sich neben der Ablage aus.
|
||||
if optionen.get("arbeit"):
|
||||
orte["workDir"] = optionen["arbeit"]
|
||||
if orte:
|
||||
self._lauf.beginne("ablage")
|
||||
melden("Ablage wird gespeichert …")
|
||||
_ablage_speichern(optionen["ablage"])
|
||||
melden("Ordner werden gespeichert …")
|
||||
_einstellungen_schreiben(orte)
|
||||
self._lauf.beginne("werkzeuge")
|
||||
if optionen.get("werkzeuge_holen", True):
|
||||
melden("Werkzeuge werden geprüft …")
|
||||
@@ -235,6 +243,7 @@ class Bruecke:
|
||||
aktualisieren=bool(optionen.get("aktualisieren", False)))
|
||||
melden(werkzeug_einrichtung.bericht(bericht))
|
||||
bereit = bericht["bereit"]
|
||||
_beta_key_holen(melden)
|
||||
else:
|
||||
bereit = True
|
||||
self._lauf.beginne("fertig")
|
||||
@@ -299,16 +308,73 @@ def _ablage_speichern(pfad: str) -> None:
|
||||
Der Store wird direkt benutzt. Ein Umweg über den Suchpfad der API war
|
||||
ohnehin überflüssig.
|
||||
"""
|
||||
_einstellungen_schreiben({"outputDir": pfad})
|
||||
|
||||
|
||||
def _einstellungen_schreiben(neu: dict) -> None:
|
||||
"""Werte in die Einstellungen mischen — lesen, setzen, zurückschreiben.
|
||||
|
||||
`save_settings` überschreibt das GANZE JSON, deshalb erst lesen.
|
||||
"""
|
||||
from rippy import config, store
|
||||
|
||||
werte = config.laden()
|
||||
store.verbinden(config.datenbank_url(werte))
|
||||
store.init_db()
|
||||
aktuell = store.get_settings() or {}
|
||||
aktuell["outputDir"] = pfad
|
||||
aktuell.update(neu)
|
||||
store.save_settings(aktuell)
|
||||
|
||||
|
||||
def _beta_key_holen(melden=None) -> str:
|
||||
"""Den kostenlosen MakeMKV-Beta-Key aus dem Forum holen und ablegen.
|
||||
|
||||
## Warum das ins Setup gehört (Commander 28.08.2026)
|
||||
|
||||
> „Bezüglich des MKV Beta Keys — der könnte theoretisch auch automatisch
|
||||
> ausgelesen werden, ich glaube das web rippy kann das"
|
||||
|
||||
Er hat recht, es kann: `docker/api/makemkv_key.py` holt den Key aus dem
|
||||
Forum-Thread t=1053 und läuft seit jeher täglich mit. Nachgemessen am
|
||||
28.08.2026: Antwort in 3,3 Sekunden, gültiger Key mit 62 Zeichen.
|
||||
|
||||
Nur half das beim ERSTEN Rip nicht: Die Schleife startet 60 Sekunden nach
|
||||
dem Serverstart. Wer gleich nach dem Einrichten eine Blu-ray einlegt, rippt
|
||||
ohne Key — und MakeMKV fällt in den 30-Tage-Testmodus.
|
||||
|
||||
Ohne Key ist Rippy nicht kaputt; DVDs gehen weiterhin. Deshalb ist ein
|
||||
Fehlschlag hier auch kein Abbruch, sondern eine Zeile im Protokoll.
|
||||
|
||||
Rechtlich unverändert: Das ist die öffentliche Beta-LIZENZ der Software,
|
||||
NICHT ein Disc-Schlüssel. Rippy liefert und verteilt keine Disc-Schlüssel.
|
||||
"""
|
||||
melden = melden or (lambda *a, **k: None)
|
||||
try:
|
||||
import sys
|
||||
|
||||
from rippy.daemon import _api_pfad
|
||||
|
||||
# `_api_pfad` gibt den Pfad NUR zurueck — eingehaengt wird er in
|
||||
# daemon.starten(). Hier laeuft der Assistent, nicht der Dienst.
|
||||
pfad = _api_pfad()
|
||||
if pfad and pfad not in sys.path:
|
||||
sys.path.insert(0, pfad)
|
||||
import makemkv_key
|
||||
|
||||
key = makemkv_key.fetch_current_key()
|
||||
except Exception as ex: # noqa: BLE001
|
||||
melden(" Beta-Key nicht erreichbar (%s) — nachtragbar unter "
|
||||
"Einstellungen." % type(ex).__name__)
|
||||
return ""
|
||||
if not key:
|
||||
melden(" Kein Beta-Key im Forum gefunden — nachtragbar unter "
|
||||
"Einstellungen.")
|
||||
return ""
|
||||
_einstellungen_schreiben({"makemkvAppKey": key})
|
||||
melden(" MakeMKV-Beta-Key geholt (…%s)." % key[-6:])
|
||||
return key
|
||||
|
||||
|
||||
def starten() -> dict:
|
||||
"""Öffnet den Assistenten. Gibt zurück, was daraus geworden ist.
|
||||
|
||||
@@ -426,6 +492,15 @@ SEITE = r"""<!DOCTYPE html>
|
||||
<button onclick="waehlen('ablage')">Durchsuchen …</button>
|
||||
</div>
|
||||
</label>
|
||||
<label class="feld"><span>Arbeitsordner für die Rohdaten
|
||||
<div class="hinweis">Hier liegt die Disc zwischendurch am Stück — bei einer
|
||||
4K-UHD bis zu 100 GB. Am besten dasselbe Laufwerk wie die Ablage: dann muss
|
||||
Rippy die fertige Datei am Ende nur umhängen statt sie zu kopieren.</div></span>
|
||||
<div class="mitknopf">
|
||||
<input type="text" id="arbeit">
|
||||
<button onclick="waehlen('arbeit')">Durchsuchen …</button>
|
||||
</div>
|
||||
</label>
|
||||
<label class="feld" style="max-width:190px"><span>Port</span>
|
||||
<input type="number" id="port" min="1024" max="65535">
|
||||
</label>
|
||||
@@ -532,6 +607,7 @@ SEITE = r"""<!DOCTYPE html>
|
||||
await pywebview.api.installieren({
|
||||
ziel: $('ziel').value,
|
||||
ablage: $('ablage').value,
|
||||
arbeit: $('arbeit').value,
|
||||
port: Number($('port').value),
|
||||
autostart: $('autostart').checked,
|
||||
verknuepfungen: $('verknuepfungen').checked,
|
||||
@@ -578,6 +654,7 @@ SEITE = r"""<!DOCTYPE html>
|
||||
const v = await pywebview.api.vorgaben();
|
||||
$('ziel').value = v.ziel;
|
||||
$('ablage').value = v.ablage;
|
||||
$('arbeit').value = v.arbeit;
|
||||
$('port').value = v.port;
|
||||
$('autostart').checked = v.autostart;
|
||||
$('verknuepfungen').checked = v.verknuepfungen;
|
||||
|
||||
@@ -238,11 +238,12 @@ def test_vorgaben_sind_vollstaendig():
|
||||
import pytest
|
||||
pytest.skip("Registry-Vorgaben gibt es nur unter Windows")
|
||||
v = e.vorgaben()
|
||||
assert set(v) == {"ziel", "ablage", "port", "autostart",
|
||||
assert set(v) == {"ziel", "ablage", "arbeit", "port", "autostart",
|
||||
"verknuepfungen", "werkzeuge_holen"}
|
||||
assert v["port"] == 7788
|
||||
assert v["ziel"] and v["ablage"]
|
||||
assert "/app/" not in v["ablage"], "Container-Pfad in einer Windows-Vorgabe"
|
||||
assert v["ziel"] and v["ablage"] and v["arbeit"]
|
||||
for feld in ("ablage", "arbeit"):
|
||||
assert "/app/" not in v[feld], "Container-Pfad in einer Windows-Vorgabe"
|
||||
|
||||
|
||||
def test_netzwerkpfad_als_administrator_wird_erklaert():
|
||||
|
||||
@@ -284,3 +284,96 @@ def test_updates_melden_was_offen_ist(monkeypatch):
|
||||
assert [o["titel"] for o in stand["offen"]] == ["MakeMKV"]
|
||||
assert stand["offen"][0]["installiert"] == "1.18.2"
|
||||
assert stand["offen"][0]["neueste"] == "1.18.4"
|
||||
|
||||
|
||||
# ── Der Arbeitsordner im Setup (Commander 28.08.2026) ───────────────────
|
||||
#
|
||||
# > „kannst du noch einbauen das man den arbeitsordner usw. in den
|
||||
# > Einstellungen setzen kann und direkt im setup?"
|
||||
#
|
||||
# Warum das nicht Kosmetik ist: Der Roh-Rip einer 4K-UHD ist bis zu 100 GB
|
||||
# gross. Wer erst NACH der ersten vollen Platte erfaehrt, wo die liegen, hat
|
||||
# es zu spaet erfahren — am 25.07.2026 lief genau so die VM-Platte voll.
|
||||
|
||||
|
||||
def test_die_seite_fragt_nach_dem_arbeitsordner():
|
||||
assert setup_fenster.seite_enthaelt('id="arbeit"')
|
||||
assert setup_fenster.seite_enthaelt("Arbeitsordner für die Rohdaten")
|
||||
|
||||
|
||||
def test_der_arbeitsordner_hat_einen_durchsuchen_knopf():
|
||||
"""Einen Pfad abzutippen ist die Sorte Bedienung, die niemand macht."""
|
||||
assert setup_fenster.seite_enthaelt("waehlen('arbeit')")
|
||||
|
||||
|
||||
def test_jede_vorgabe_hat_ein_feld_UND_wird_mitgeschickt():
|
||||
"""Der Waechter gegen halb angeschlossene Felder.
|
||||
|
||||
Ein Feld kann auf drei Arten kaputt sein, und alle drei sehen im Fenster
|
||||
gleich aus: kein Eingabefeld, keine Vorbelegung, oder es wird beim
|
||||
Installieren nicht mitgeschickt. Der dritte Fall ist der gemeinste — man
|
||||
waehlt etwas, es passiert nichts, und niemand sagt warum.
|
||||
"""
|
||||
from rippy import einrichtung
|
||||
|
||||
# Schalter heissen im HTML anders (checkbox) und werden separat geprueft.
|
||||
pfade_und_zahlen = {"ziel", "ablage", "arbeit", "port"}
|
||||
for name in pfade_und_zahlen:
|
||||
assert name in einrichtung.vorgaben(), "%s fehlt in vorgaben()" % name
|
||||
assert setup_fenster.seite_enthaelt('id="%s"' % name), \
|
||||
"kein Eingabefeld fuer %s" % name
|
||||
assert setup_fenster.seite_enthaelt("$('%s').value = v.%s" % (name, name)), \
|
||||
"%s wird nicht vorbelegt" % name
|
||||
assert setup_fenster.seite_enthaelt("%s: " % name), \
|
||||
"%s wird beim Installieren nicht mitgeschickt" % name
|
||||
|
||||
|
||||
def test_der_arbeitsordner_landet_als_workDir_in_den_einstellungen(monkeypatch):
|
||||
"""Der Name muss stimmen: Die Oberflaeche und der Worker lesen `workDir`."""
|
||||
geschrieben = {}
|
||||
monkeypatch.setattr(setup_fenster, "_einstellungen_schreiben",
|
||||
lambda neu: geschrieben.update(neu))
|
||||
monkeypatch.setattr(setup_fenster, "_beta_key_holen", lambda *a: "")
|
||||
|
||||
class StilleWerkzeuge:
|
||||
@staticmethod
|
||||
def sicherstellen(**k):
|
||||
return {"bereit": True}
|
||||
|
||||
@staticmethod
|
||||
def bericht(b):
|
||||
return "ok"
|
||||
|
||||
import sys
|
||||
import types
|
||||
|
||||
modul = types.ModuleType("rippy.tools.einrichten")
|
||||
modul.sicherstellen = StilleWerkzeuge.sicherstellen
|
||||
modul.bericht = StilleWerkzeuge.bericht
|
||||
monkeypatch.setitem(sys.modules, "rippy.tools.einrichten", modul)
|
||||
|
||||
from rippy import windows_app
|
||||
monkeypatch.setattr(windows_app, "installieren", lambda **k: r"C:\Rippy")
|
||||
|
||||
b = setup_fenster.Bruecke()
|
||||
b._installieren_wirklich({"ablage": r"D:\Filme", "arbeit": r"E:\Roh",
|
||||
"werkzeuge_holen": True})
|
||||
|
||||
assert geschrieben.get("workDir") == r"E:\Roh"
|
||||
assert geschrieben.get("outputDir") == r"D:\Filme"
|
||||
assert b.ergebnis["ok"] is True
|
||||
|
||||
|
||||
def test_ohne_angabe_wird_NICHTS_geschrieben(monkeypatch):
|
||||
"""Leer heisst „nimm die Vorgabe" — dann darf kein leerer Pfad in die
|
||||
Einstellungen wandern und die Berechnung ueberschreiben."""
|
||||
geschrieben = {}
|
||||
monkeypatch.setattr(setup_fenster, "_einstellungen_schreiben",
|
||||
lambda neu: geschrieben.update(neu))
|
||||
monkeypatch.setattr(setup_fenster, "_beta_key_holen", lambda *a: "")
|
||||
from rippy import windows_app
|
||||
monkeypatch.setattr(windows_app, "installieren", lambda **k: r"C:\Rippy")
|
||||
|
||||
b = setup_fenster.Bruecke()
|
||||
b._installieren_wirklich({"ablage": "", "arbeit": "", "werkzeuge_holen": False})
|
||||
assert geschrieben == {}
|
||||
|
||||
Reference in New Issue
Block a user