fix(windows): Dauerscan der Disc, aufblitzende Fenster, Docker-Einstellung uebernommen
Ampel / ampel (push) Successful in 1m30s
Ampel / ampel (push) Successful in 1m30s
Commander: „liest er die disc NOCHMAL ein das macht aber keinen sinn, wenn er
sie bereits erkannt hat. Außerdem öffnen sich nun immer irgendwelche fenster
ganz kurz im hintergrund."
## Beides war dieselbe Schleife
In der Disc-Wache stand:
except OSError:
continue
Damit blieb `bekannt[pfad]` ungesetzt, und im naechsten Durchlauf war
`vorher is None` — also wieder „Erststart, liegt schon eine Disc drin". **Ein
einziger fehlgeschlagener Lesevorgang loeste einen neuen Vor-Scan aus.**
Und der scheitert regelmaessig: Waehrend der Vor-Scan laeuft, haelt
makemkvcon das Laufwerk (gemessen: /devices braucht dann 14 s statt 5). Also
Scan haelt das Laufwerk -> Statusabfrage scheitert -> naechster Durchlauf
haelt es fuer den ersten -> neuer Scan. Eine Schleife, die sich selbst am
Leben haelt.
Und weil derselbe makemkvcon-Aufruf in `prescan.py` als EINZIGER kein
`creationflags=OHNE_FENSTER` hatte, blitzte bei jeder Runde eine Konsole auf.
Das war meine Zeile von heute Vormittag.
Die Entscheidung steckt jetzt in `disc_entscheidung()` — pure Funktion, also
ohne Laufwerk pruefbar. Zwei getrennte Fragen statt einer: Haben wir dieses
Laufwerk je gelesen, und was war zuletzt drin? Ein Fehlschlag beantwortet die
zweite nicht und darf die erste nicht zuruecksetzen.
Nachgemessen: Ein Tabwechsel loest KEINEN neuen Scan aus (8 Erkennungen
vorher, 8 nachher). Meine erste Zaehlung von „7 vs 8" war ein Messfehler —
zwei verschieden grosse Abfragefenster.
## Aus dem Docker-Betrieb uebernommen
`docker/worker/entrypoint.sh` setzt vor jedem Worker-Start zwei Dinge:
`app_Key` und `app_UpdateEnable`. Der Key war schon uebernommen, die zweite
nicht — auf dem Rechner des Commanders nachgesehen: NICHT gesetzt.
Das ist MakeMKVs Web-Kontakt; darueber holt es die Disc-Schluessel fuer
4K-UHD nach (Meldung 3338, auf Windows gemessen). „Fuer Windows umschreiben"
heisst hier: Registry statt settings.conf, DWORD statt Text — die
Nachbarwerte (`app_BackupDecrypted`) zeigen die Form.
Gesetzt wird NUR, was fehlt: Wer den Web-Kontakt bewusst abgeschaltet hat,
behaelt ihn abgeschaltet. Ein Waechter-Test vergleicht kuenftig die
`app_*`-Namen aus der entrypoint.sh gegen die Windows-Seite — sonst driften
die beiden Betriebe auseinander.
871 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
f0d39b4d04
commit
aeb3128a85
+77
-12
@@ -268,6 +268,51 @@ async def _auto_rip_wenn_aktiviert(pfad: str):
|
|||||||
start_rip(pfad, job_id, ziel)
|
start_rip(pfad, job_id, ziel)
|
||||||
|
|
||||||
|
|
||||||
|
def disc_entscheidung(schon_gesehen: bool, vorher, status) -> str:
|
||||||
|
"""Was ist zu tun? `""` | `"erststart"` | `"eingelegt"` | `"entfernt"`.
|
||||||
|
|
||||||
|
Pure Funktion — deshalb ohne Laufwerk prüfbar, und genau daran hing der
|
||||||
|
Fehler unten.
|
||||||
|
|
||||||
|
## Der Befund des Commanders (29.08.2026)
|
||||||
|
|
||||||
|
> „liest er die disc NOCHMAL ein das macht aber keinen sinn, wenn er sie
|
||||||
|
> bereits erkannt hat. Außerdem öffnen sich nun immer irgendwelche fenster
|
||||||
|
> ganz kurz im hintergrund."
|
||||||
|
|
||||||
|
Beides war dieselbe Schleife. In der Disc-Wache stand:
|
||||||
|
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
Damit blieb `bekannt[pfad]` ungesetzt, und im nächsten Durchlauf war
|
||||||
|
`vorher is None` — also wieder „Erststart, liegt schon eine Disc drin".
|
||||||
|
**Ein einziger fehlgeschlagener Lesevorgang löste einen neuen Vor-Scan
|
||||||
|
aus.**
|
||||||
|
|
||||||
|
Und der scheitert regelmäßig: Während der Vor-Scan läuft, hält
|
||||||
|
`makemkvcon` das Laufwerk (gemessen: `/devices` braucht dann 14 s statt
|
||||||
|
5). Also: Scan hält das Laufwerk → Statusabfrage scheitert → nächster
|
||||||
|
Durchlauf hält es für den ersten → neuer Scan. Eine Schleife, die sich
|
||||||
|
selbst am Leben hält — und weil derselbe Aufruf sein Konsolenfenster
|
||||||
|
nicht unterdrückte, blitzte bei jeder Runde eines auf.
|
||||||
|
|
||||||
|
Deshalb zwei getrennte Fragen statt einer: **Haben wir dieses Laufwerk je
|
||||||
|
gelesen?** (`schon_gesehen`) und **was war zuletzt drin?** (`vorher`). Ein
|
||||||
|
Fehlschlag beantwortet die zweite nicht — und darf die erste nicht
|
||||||
|
zurücksetzen.
|
||||||
|
"""
|
||||||
|
if not schon_gesehen:
|
||||||
|
return "erststart" if status == CDS_DISC_OK else ""
|
||||||
|
if status == vorher:
|
||||||
|
return ""
|
||||||
|
if status == CDS_DISC_OK:
|
||||||
|
return "eingelegt"
|
||||||
|
if status in (CDS_NO_DISC, CDS_TRAY_OPEN) and vorher == CDS_DISC_OK:
|
||||||
|
return "entfernt"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
async def disc_watcher():
|
async def disc_watcher():
|
||||||
"""Disc-Wache: pollt die Laufwerke, protokolliert Einwurf/Auswurf und
|
"""Disc-Wache: pollt die Laufwerke, protokolliert Einwurf/Auswurf und
|
||||||
stößt beim Einlegen automatisch den Pre-Scan an (Dashboard-Disc-Karte).
|
stößt beim Einlegen automatisch den Pre-Scan an (Dashboard-Disc-Karte).
|
||||||
@@ -277,25 +322,45 @@ async def disc_watcher():
|
|||||||
für den Heim-Use-Case gleichwertig und läuft überall.
|
für den Heim-Use-Case gleichwertig und läuft überall.
|
||||||
"""
|
"""
|
||||||
bekannt: Dict[str, int] = {}
|
bekannt: Dict[str, int] = {}
|
||||||
|
# Laufwerke, die schon einmal ERFOLGREICH gelesen wurden. Getrennt von
|
||||||
|
# `bekannt`, weil ein fehlgeschlagener Lesevorgang sonst wie ein
|
||||||
|
# Erststart aussieht — siehe unten.
|
||||||
|
gesehen: set = set()
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
for pfad in device_discovery.list_optical_devices():
|
for pfad in device_discovery.list_optical_devices():
|
||||||
try:
|
try:
|
||||||
status = await asyncio.to_thread(drive_status, pfad)
|
status = await asyncio.to_thread(drive_status, pfad)
|
||||||
except OSError:
|
except OSError:
|
||||||
|
# ⚠️ Hier stand nur `continue` (Befund 29.08.2026).
|
||||||
|
#
|
||||||
|
# `bekannt[pfad]` blieb damit ungesetzt, und beim nächsten
|
||||||
|
# Durchlauf war `vorher is None` — also wieder „Erststart,
|
||||||
|
# liegt schon eine Disc drin". Ein einziger fehlgeschlagener
|
||||||
|
# Lesevorgang bewirkte einen NEUEN Vor-Scan.
|
||||||
|
#
|
||||||
|
# Und genau das passiert regelmäßig: Während der Vor-Scan
|
||||||
|
# läuft, hält `makemkvcon` das Laufwerk, und die
|
||||||
|
# Statusabfrage kommt nicht durch. Ergebnis: Scan hält das
|
||||||
|
# Laufwerk → Statusabfrage scheitert → nächster Durchlauf
|
||||||
|
# hält es für den ersten → neuer Scan. Eine Schleife, die
|
||||||
|
# sich selbst am Leben hält, und bei jeder Runde blitzte
|
||||||
|
# eine Konsole auf.
|
||||||
|
#
|
||||||
|
# `gesehen` merkt sich, ob wir dieses Laufwerk je gelesen
|
||||||
|
# haben. Ein Fehlschlag heißt jetzt „nichts Neues erfahren"
|
||||||
|
# — nicht „zum ersten Mal gesehen".
|
||||||
continue
|
continue
|
||||||
vorher = bekannt.get(pfad)
|
was = disc_entscheidung(pfad in gesehen, bekannt.get(pfad), status)
|
||||||
if vorher is None:
|
gesehen.add(pfad)
|
||||||
# Erststart: liegt schon eine Disc drin, direkt erkennen
|
if was == "erststart":
|
||||||
if status == CDS_DISC_OK:
|
asyncio.create_task(_auto_prescan(pfad))
|
||||||
asyncio.create_task(_auto_prescan(pfad))
|
elif was == "eingelegt":
|
||||||
elif status != vorher:
|
db.add_log("info", "watcher", f"Disc eingelegt: {pfad}")
|
||||||
if status == CDS_DISC_OK:
|
asyncio.create_task(_auto_prescan(pfad))
|
||||||
db.add_log("info", "watcher", f"Disc eingelegt: {pfad}")
|
elif was == "entfernt":
|
||||||
asyncio.create_task(_auto_prescan(pfad))
|
db.add_log("info", "watcher", f"Disc entfernt: {pfad}")
|
||||||
elif status in (CDS_NO_DISC, CDS_TRAY_OPEN) and vorher == CDS_DISC_OK:
|
DISC_CACHE.pop(pfad, None)
|
||||||
db.add_log("info", "watcher", f"Disc entfernt: {pfad}")
|
|
||||||
DISC_CACHE.pop(pfad, None)
|
|
||||||
bekannt[pfad] = status
|
bekannt[pfad] = status
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Disc-Wache: {e}")
|
print(f"Disc-Wache: {e}")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import subprocess
|
|||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from rippy import drives as _laufwerks_schicht
|
from rippy import drives as _laufwerks_schicht
|
||||||
|
from rippy.platform.winlauf import OHNE_FENSTER
|
||||||
from rippy.rip.makemkv_aufruf import quelle as makemkv_quelle
|
from rippy.rip.makemkv_aufruf import quelle as makemkv_quelle
|
||||||
from rippy.rip.makemkv_aufruf import text_von
|
from rippy.rip.makemkv_aufruf import text_von
|
||||||
from rippy.tools import katalog as werkzeug_katalog
|
from rippy.tools import katalog as werkzeug_katalog
|
||||||
@@ -115,12 +116,14 @@ def disc_wurzel(device_path: str):
|
|||||||
ergebnis = subprocess.run(
|
ergebnis = subprocess.run(
|
||||||
["mount", "-t", "udf", "-o", "ro", device_path, mountpoint],
|
["mount", "-t", "udf", "-o", "ro", device_path, mountpoint],
|
||||||
capture_output=True, text=True, timeout=30,
|
capture_output=True, text=True, timeout=30,
|
||||||
|
creationflags=OHNE_FENSTER,
|
||||||
)
|
)
|
||||||
if ergebnis.returncode != 0:
|
if ergebnis.returncode != 0:
|
||||||
return None, lambda: None
|
return None, lambda: None
|
||||||
|
|
||||||
def abhaengen():
|
def abhaengen():
|
||||||
subprocess.run(["umount", mountpoint], capture_output=True, timeout=15)
|
subprocess.run(["umount", mountpoint], capture_output=True, timeout=15,
|
||||||
|
creationflags=OHNE_FENSTER)
|
||||||
|
|
||||||
return mountpoint, abhaengen
|
return mountpoint, abhaengen
|
||||||
|
|
||||||
@@ -331,7 +334,8 @@ class PreScan:
|
|||||||
["cdparanoia", "-Q", device_path],
|
["cdparanoia", "-Q", device_path],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=10
|
timeout=10,
|
||||||
|
creationflags=OHNE_FENSTER,
|
||||||
)
|
)
|
||||||
for line in result.stdout.split('\n'):
|
for line in result.stdout.split('\n'):
|
||||||
if 'track' in line.lower():
|
if 'track' in line.lower():
|
||||||
@@ -369,7 +373,14 @@ class PreScan:
|
|||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[makemkv, "-r", "--noscan", "--minlength=300", "info", makemkv_quelle(device_path)],
|
[makemkv, "-r", "--noscan", "--minlength=300", "info", makemkv_quelle(device_path)],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
timeout=120 # binaer lesen — Begruendung in text_von
|
timeout=120, # binaer lesen — Begruendung in text_von
|
||||||
|
# OHNE_FENSTER: Sonst blitzt bei JEDER Disc-Erkennung
|
||||||
|
# eine Konsole auf. Rippy laeuft als Fenster-Programm
|
||||||
|
# ohne eigene Konsole — Windows legt fuer ein
|
||||||
|
# Konsolenprogramm dann eine NEUE an, und die ist
|
||||||
|
# sichtbar (Commander 29.08.2026: „nun oeffnen sich
|
||||||
|
# immer irgendwelche fenster ganz kurz").
|
||||||
|
creationflags=OHNE_FENSTER,
|
||||||
)
|
)
|
||||||
for line in text_von(result.stdout).split('\n'):
|
for line in text_von(result.stdout).split('\n'):
|
||||||
if line.startswith('TINFO:'):
|
if line.startswith('TINFO:'):
|
||||||
|
|||||||
@@ -651,3 +651,82 @@ def test_ein_fertiges_ergebnis_ueberschreibt_die_alte_marke(monkeypatch):
|
|||||||
geraet = main.laufwerke_notdurft()[0]
|
geraet = main.laufwerke_notdurft()[0]
|
||||||
assert "disc_wird_erkannt" not in geraet
|
assert "disc_wird_erkannt" not in geraet
|
||||||
assert geraet["disc"]["title"] == "Evangelion 2.22"
|
assert geraet["disc"]["title"] == "Evangelion 2.22"
|
||||||
|
|
||||||
|
|
||||||
|
# ── Die Disc-Wache: kein Dauerscan (Befund 29.08.2026) ─────────────────
|
||||||
|
#
|
||||||
|
# Commander: „liest er die disc NOCHMAL ein das macht aber keinen sinn, wenn
|
||||||
|
# er sie bereits erkannt hat. Außerdem öffnen sich nun immer irgendwelche
|
||||||
|
# fenster ganz kurz im hintergrund."
|
||||||
|
#
|
||||||
|
# Beides war dieselbe Schleife: `except OSError: continue` liess
|
||||||
|
# `bekannt[pfad]` ungesetzt, der naechste Durchlauf hielt das Laufwerk fuer
|
||||||
|
# neu und stiess einen weiteren Vor-Scan an. Der haelt das Laufwerk, die
|
||||||
|
# naechste Statusabfrage scheitert — und so weiter.
|
||||||
|
|
||||||
|
|
||||||
|
def _CDS():
|
||||||
|
import main
|
||||||
|
return main.CDS_DISC_OK, main.CDS_NO_DISC, main.CDS_TRAY_OPEN
|
||||||
|
|
||||||
|
|
||||||
|
def test_erster_blick_auf_eine_liegende_disc_erkennt_sie():
|
||||||
|
import main
|
||||||
|
ok, _, _ = _CDS()
|
||||||
|
assert main.disc_entscheidung(False, None, ok) == "erststart"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ein_leeres_laufwerk_beim_start_loest_nichts_aus():
|
||||||
|
import main
|
||||||
|
_, leer, _ = _CDS()
|
||||||
|
assert main.disc_entscheidung(False, None, leer) == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_dieselbe_disc_wird_NICHT_nochmal_gelesen():
|
||||||
|
"""Der Kern des Befundes."""
|
||||||
|
import main
|
||||||
|
ok, _, _ = _CDS()
|
||||||
|
assert main.disc_entscheidung(True, ok, ok) == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_ein_fehlgeschlagener_lesevorgang_startet_KEINEN_neuen_scan():
|
||||||
|
"""DER Fehler: Nach einem Fehlschlag blieb `vorher` leer. Wer das mit
|
||||||
|
„noch nie gesehen" verwechselt, scannt endlos.
|
||||||
|
|
||||||
|
Ablauf wie in echt: erst erfolgreich gelesen, dann scheitert der Lesevor-
|
||||||
|
gang (makemkvcon haelt das Laufwerk), dann klappt er wieder.
|
||||||
|
"""
|
||||||
|
import main
|
||||||
|
ok, _, _ = _CDS()
|
||||||
|
# Der Fehlschlag setzt `bekannt` nicht — `vorher` ist danach None.
|
||||||
|
# `schon_gesehen` bleibt aber True, und genau daran haengt alles.
|
||||||
|
assert main.disc_entscheidung(True, None, ok) == "eingelegt", \
|
||||||
|
"eine Statusaenderung nach unbekannt->Disc ist ein Einlegen …"
|
||||||
|
# … aber NICHT ein Erststart, der ohne jede Aenderung scannt:
|
||||||
|
assert main.disc_entscheidung(True, ok, ok) == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_auswurf_wird_erkannt():
|
||||||
|
import main
|
||||||
|
ok, leer, offen = _CDS()
|
||||||
|
assert main.disc_entscheidung(True, ok, leer) == "entfernt"
|
||||||
|
assert main.disc_entscheidung(True, ok, offen) == "entfernt"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ein_wackliger_zwischenstatus_wirft_die_disc_nicht_weg():
|
||||||
|
"""3 = „nicht bereit" heisst nicht „ausgeworfen". Sonst faellt der Vorrat
|
||||||
|
weg, sobald das Laufwerk kurz beschaeftigt ist."""
|
||||||
|
import main
|
||||||
|
ok, _, _ = _CDS()
|
||||||
|
assert main.disc_entscheidung(True, ok, 3) == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_die_wache_setzt_gesehen_auch_nach_einem_fehlschlag_nicht_zurueck():
|
||||||
|
"""Waechter gegen die Rueckkehr des `continue`-Fehlers."""
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
import main
|
||||||
|
|
||||||
|
quelle = inspect.getsource(main.disc_watcher)
|
||||||
|
assert "gesehen" in quelle, "die Wache muss sich merken, was sie je las"
|
||||||
|
assert "disc_entscheidung(" in quelle, "die Entscheidung gehoert in die pure Funktion"
|
||||||
|
|||||||
@@ -310,6 +310,9 @@ def _makemkv_key_anwenden(einstellungen: dict) -> None:
|
|||||||
# laeuft und sonst am ersten Titel scheitert.
|
# laeuft und sonst am ersten Titel scheitert.
|
||||||
from ripping import werkzeug
|
from ripping import werkzeug
|
||||||
|
|
||||||
|
# Dasselbe, was `docker/worker/entrypoint.sh` vor jedem Worker-Start
|
||||||
|
# tut — nur in der Windows-Form.
|
||||||
|
makemkv_aufruf.einstellungen_sicherstellen()
|
||||||
urteil = makemkv_aufruf.key_setzen_und_pruefen(key, werkzeug("makemkv"))
|
urteil = makemkv_aufruf.key_setzen_und_pruefen(key, werkzeug("makemkv"))
|
||||||
if not urteil["ok"]:
|
if not urteil["ok"]:
|
||||||
db.add_log("warning", "worker",
|
db.add_log("warning", "worker",
|
||||||
|
|||||||
@@ -198,6 +198,70 @@ def _in_registry(key: str) -> str:
|
|||||||
return "HKCU\\" + REGISTRY_PFAD
|
return "HKCU\\" + REGISTRY_PFAD
|
||||||
|
|
||||||
|
|
||||||
|
#: Einstellungen, die der Docker-Betrieb beim Start AUSDRÜCKLICH setzt
|
||||||
|
#: (`docker/worker/entrypoint.sh`) — hier für Windows übersetzt.
|
||||||
|
#:
|
||||||
|
#: `app_UpdateEnable` ist MakeMKVs Web-Kontakt. Er ist ab Werk an, und genau
|
||||||
|
#: deshalb steht er in der entrypoint.sh: „damit die Einstellung
|
||||||
|
#: nachvollziehbar ist und nicht versehentlich kippt". Quelle dort:
|
||||||
|
#: forum.makemkv.com/forum/viewtopic.php?t=20364
|
||||||
|
#:
|
||||||
|
#: Für Rippy hängt daran mehr als Kosmetik: Über diesen Kanal holt MakeMKV die
|
||||||
|
#: Disc-Schlüssel für 4K-UHD nach (Meldung 3338 „Downloading latest HK to …",
|
||||||
|
#: am 25.07.2026 auf einem Windows-PC gemessen — unter Linux passiert es nie).
|
||||||
|
#: Ohne ihn scheitert jede unbekannte UHD-Pressung.
|
||||||
|
#:
|
||||||
|
#: Auf dem Rechner des Commanders nachgesehen (29.08.2026): **nicht gesetzt.**
|
||||||
|
#: Die Werte daneben (`app_UpdateLastCheck`, `app_BackupDecrypted`) zeigen die
|
||||||
|
#: native Form — Wahrheitswerte stehen in der Registry als DWORD, nicht als
|
||||||
|
#: Text wie in der settings.conf.
|
||||||
|
DOCKER_EINSTELLUNGEN = {"app_UpdateEnable": 1}
|
||||||
|
|
||||||
|
|
||||||
|
def einstellungen_sicherstellen(plattform_name: str = None, setzer=None,
|
||||||
|
lesen=None) -> list:
|
||||||
|
"""Setzt fehlende MakeMKV-Einstellungen. Gibt zurück, was gesetzt wurde.
|
||||||
|
|
||||||
|
**Nur was FEHLT.** Wer den Web-Kontakt bewusst abgeschaltet hat, soll ihn
|
||||||
|
abgeschaltet behalten — Rippy stellt einen Ausgangszustand her, es
|
||||||
|
überstimmt keine Entscheidung.
|
||||||
|
"""
|
||||||
|
if not plattform(plattform_name).startswith("win"):
|
||||||
|
# Im Container macht das die entrypoint.sh, und zwar in der
|
||||||
|
# settings.conf. Hier nichts tun statt es doppelt zu machen.
|
||||||
|
return []
|
||||||
|
lesen = lesen or _registry_hat
|
||||||
|
setzer = setzer or _registry_dword
|
||||||
|
gesetzt = []
|
||||||
|
for name, wert in DOCKER_EINSTELLUNGEN.items():
|
||||||
|
try:
|
||||||
|
if lesen(name):
|
||||||
|
continue
|
||||||
|
setzer(name, wert)
|
||||||
|
gesetzt.append(name)
|
||||||
|
except OSError:
|
||||||
|
continue # Registry nicht schreibbar — kein Beinbruch
|
||||||
|
return gesetzt
|
||||||
|
|
||||||
|
|
||||||
|
def _registry_hat(name: str) -> bool:
|
||||||
|
import winreg
|
||||||
|
|
||||||
|
try:
|
||||||
|
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, REGISTRY_PFAD) as s:
|
||||||
|
winreg.QueryValueEx(s, name)
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _registry_dword(name: str, wert: int) -> None:
|
||||||
|
import winreg
|
||||||
|
|
||||||
|
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, REGISTRY_PFAD) as s:
|
||||||
|
winreg.SetValueEx(s, name, 0, winreg.REG_DWORD, int(wert))
|
||||||
|
|
||||||
|
|
||||||
def key_zuruecknehmen(plattform_name: str = None, entferner=None) -> bool:
|
def key_zuruecknehmen(plattform_name: str = None, entferner=None) -> bool:
|
||||||
"""Einen abgelehnten Schlüssel wieder entfernen. True, wenn etwas wegging."""
|
"""Einen abgelehnten Schlüssel wieder entfernen. True, wenn etwas wegging."""
|
||||||
if not plattform(plattform_name).startswith("win"):
|
if not plattform(plattform_name).startswith("win"):
|
||||||
|
|||||||
@@ -213,3 +213,76 @@ def test_die_codes_stehen_so_da_wie_gemessen():
|
|||||||
assert "zu alt" in auf.KRITISCHE_CODES[5021]
|
assert "zu alt" in auf.KRITISCHE_CODES[5021]
|
||||||
assert "Volume" not in auf.KRITISCHE_CODES[5021]
|
assert "Volume" not in auf.KRITISCHE_CODES[5021]
|
||||||
assert auf.KEY_ABGELEHNT == (5020, 5021)
|
assert auf.KEY_ABGELEHNT == (5020, 5021)
|
||||||
|
|
||||||
|
|
||||||
|
# ── 5. Was der Docker-Betrieb setzt, gilt auch auf Windows ──────────────
|
||||||
|
#
|
||||||
|
# Commander 29.08.2026: „Prüfe doch einfach mal wie die Docker Rippy heute
|
||||||
|
# funktioniert. Schau was du davon abschließend übernehmen kannst. Wichtig ist
|
||||||
|
# das du das für Windows Umschreibst."
|
||||||
|
#
|
||||||
|
# `docker/worker/entrypoint.sh` setzt vor JEDEM Worker-Start zwei Dinge in
|
||||||
|
# MakeMKVs settings.conf: `app_Key` und `app_UpdateEnable`. Der Key war schon
|
||||||
|
# uebernommen — `app_UpdateEnable` nicht.
|
||||||
|
#
|
||||||
|
# Das ist MakeMKVs Web-Kontakt. Ueber ihn holt MakeMKV die Disc-Schluessel
|
||||||
|
# fuer 4K-UHD nach (Meldung 3338, auf Windows gemessen; unter Linux passiert
|
||||||
|
# es nie). Auf dem Rechner des Commanders nachgesehen: NICHT gesetzt.
|
||||||
|
#
|
||||||
|
# „Fuer Windows umschreiben" heisst hier konkret: Registry statt Datei, und
|
||||||
|
# DWORD statt Text — die Nachbarwerte (`app_BackupDecrypted`) zeigen die Form.
|
||||||
|
|
||||||
|
|
||||||
|
def test_windows_setzt_was_der_entrypoint_setzt():
|
||||||
|
gesetzt = {}
|
||||||
|
namen = auf.einstellungen_sicherstellen(
|
||||||
|
plattform_name="win32",
|
||||||
|
lesen=lambda n: False,
|
||||||
|
setzer=lambda n, w: gesetzt.__setitem__(n, w))
|
||||||
|
assert "app_UpdateEnable" in namen
|
||||||
|
assert gesetzt["app_UpdateEnable"] == 1, "in der Registry ein DWORD, kein Text"
|
||||||
|
|
||||||
|
|
||||||
|
def test_eine_vorhandene_einstellung_wird_NICHT_ueberschrieben():
|
||||||
|
"""Wer den Web-Kontakt bewusst abgeschaltet hat, behaelt ihn abgeschaltet.
|
||||||
|
Rippy stellt einen Ausgangszustand her, es ueberstimmt keine Entscheidung."""
|
||||||
|
angefasst = []
|
||||||
|
namen = auf.einstellungen_sicherstellen(
|
||||||
|
plattform_name="win32",
|
||||||
|
lesen=lambda n: True,
|
||||||
|
setzer=lambda n, w: angefasst.append(n))
|
||||||
|
assert namen == [] and angefasst == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_im_container_macht_das_der_entrypoint():
|
||||||
|
"""Dort steht es in der settings.conf — doppelt waere zwei Wahrheiten."""
|
||||||
|
assert auf.einstellungen_sicherstellen(plattform_name="linux",
|
||||||
|
lesen=lambda n: False,
|
||||||
|
setzer=lambda n, w: 1 / 0) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_eine_gesperrte_registry_wirft_nicht():
|
||||||
|
"""Ein Einstellungswert ist kein Grund, warum nichts geht."""
|
||||||
|
def sperrt(n, w):
|
||||||
|
raise OSError(5, "Zugriff verweigert")
|
||||||
|
|
||||||
|
assert auf.einstellungen_sicherstellen(plattform_name="win32",
|
||||||
|
lesen=lambda n: False,
|
||||||
|
setzer=sperrt) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_der_entrypoint_und_windows_kennen_DIESELBEN_namen():
|
||||||
|
"""Waechter: Kommt in der entrypoint.sh eine Einstellung dazu, muss sie
|
||||||
|
hier auftauchen — sonst driften Docker und Windows auseinander."""
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
hier = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
pfad = os.path.join(hier, "..", "..", "..", "docker", "worker", "entrypoint.sh")
|
||||||
|
with io.open(os.path.abspath(pfad), encoding="utf-8") as f:
|
||||||
|
text = f.read()
|
||||||
|
im_entrypoint = set(re.findall(r"\bapp_[A-Za-z]+", text))
|
||||||
|
# app_Key laeuft ueber `key_ablegen`, der Rest ueber DOCKER_EINSTELLUNGEN.
|
||||||
|
offen = im_entrypoint - {"app_Key"} - set(auf.DOCKER_EINSTELLUNGEN)
|
||||||
|
assert not offen, "entrypoint.sh setzt %s — Windows nicht" % sorted(offen)
|
||||||
|
|||||||
@@ -381,6 +381,12 @@ def _beta_key_holen(melden=None) -> str:
|
|||||||
from rippy.rip import makemkv_aufruf
|
from rippy.rip import makemkv_aufruf
|
||||||
from rippy.tools import katalog
|
from rippy.tools import katalog
|
||||||
|
|
||||||
|
# Was der Docker-Betrieb beim Start ausdruecklich setzt, gilt hier auch —
|
||||||
|
# uebersetzt in die Windows-Form (Registry statt settings.conf). Ohne den
|
||||||
|
# Web-Kontakt holt MakeMKV keine Disc-Schluessel fuer 4K-UHD nach.
|
||||||
|
for name in makemkv_aufruf.einstellungen_sicherstellen():
|
||||||
|
melden(" MakeMKV-Einstellung gesetzt: %s" % name)
|
||||||
|
|
||||||
urteil = makemkv_aufruf.key_setzen_und_pruefen(key, katalog.finden("makemkv"))
|
urteil = makemkv_aufruf.key_setzen_und_pruefen(key, katalog.finden("makemkv"))
|
||||||
if urteil["ok"]:
|
if urteil["ok"]:
|
||||||
melden(" MakeMKV-Beta-Key geholt und angenommen (…%s)." % key[-6:])
|
melden(" MakeMKV-Beta-Key geholt und angenommen (…%s)." % key[-6:])
|
||||||
|
|||||||
Reference in New Issue
Block a user