fix(windows): die Datenbank lag AUSSERHALB der Installation — daher fehlte
Ampel / ampel (push) Failing after 54s
Ampel / ampel (push) Failing after 54s
der Ersteinrichtungs-Assistent
Der Befund des Commanders: "Ausserdem fehlt der 1st run wizzard wenn man es
installiert." Meine erste Reparatur (App.tsx las einen gescheiterten Abruf
als "erledigt") war richtig, aber nicht die Ursache. Nachgemessen:
Installation %LOCALAPPDATA%\Rippy wird deinstalliert
Datenbank %ProgramData%\Rippy BLEIBT LIEGEN
Eine "frische" Installation erbte damit die alte Datenbank samt
`setup.done = true` -- der Assistent erschien nie wieder. Gegengeprueft:
nach dem Aufraeumen meldet /api/setup wieder {"done": false}, und das
Fenster zeigt "Willkommen bei Rippy".
Der Grund fuer den alten Ort stand im Docstring: "Ein Programmordner ist
unter Windows fuer einen DIENST nicht zuverlaessig beschreibbar." Das stimmte
-- fuer den Windows-Dienst, den es nie gab. Rippy laeuft als der angemeldete
Benutzer (Autostart unter HKCU). Jetzt liegt alles, was Rippy gehoert, in
EINEM Ordner: Programm, UI, Werkzeuge, Protokoll, WebView2-Zwischenspeicher
und die Datenbank.
Dazu:
* `datenbank_umziehen()` holt eine vorhandene Datenbank vom alten Ort ab --
wer Rippy schon benutzt hat, behaelt Jobs und Einstellungen. Gibt es am
neuen Ort schon eine, bleibt sie unangetastet: Die BENUTZTE gewinnt.
* `deinstallieren()` raeumt den alten Ort mit weg, und `alte_orte()` fasst
nur einen Ordner an, der wirklich "Rippy" heisst.
* Der Ersteinrichtungs-Assistent spricht nicht mehr von "Encoding-Worker",
wenn es keine gibt -- dort steht jetzt "Kompression". Gegengeprueft: kein
"docker", kein "Container", kein "Worker" mehr im Text.
Ampel lokal: 709 gruen, ruff sauber.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
082e8b8b55
commit
8e65411189
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Disc, Cpu, Globe, Tv, CheckCircle, ArrowRight, HardDrive, AlertTriangle, KeyRound } from 'lucide-react'
|
||||
import { api } from '../lib/api'
|
||||
import { useBetrieb } from '../lib/useBetrieb'
|
||||
import { MEDIA_SERVER_OPTIONEN } from '../lib/mediaServer'
|
||||
import { PRESET_KEINE, schwacheEncoderCpu } from '../lib/encoder'
|
||||
import { Button } from './ui/Button'
|
||||
@@ -74,6 +75,7 @@ export default function FirstRunWizard({ onDone }: { onDone: () => void }) {
|
||||
const [makemkvKey, setMakemkvKey] = useState('')
|
||||
const [mediaServer, setMediaServer] = useState('jellyfin')
|
||||
const [transcodeEnabled, setTranscodeEnabled] = useState(true)
|
||||
const betrieb = useBetrieb()
|
||||
const [workers, setWorkers] = useState<WorkerInfo[]>([])
|
||||
const [geraete, setGeraete] = useState<GeraetInfo[]>([])
|
||||
const [plaetze, setPlaetze] = useState<PlatzInfo[]>([])
|
||||
@@ -197,7 +199,11 @@ export default function FirstRunWizard({ onDone }: { onDone: () => void }) {
|
||||
schlechtText="Kein optisches Laufwerk gefunden. Rippy kann dann nur komprimieren, nicht rippen. Prüfe, ob das Laufwerk angeschlossen und (bei einer VM) durchgereicht ist."
|
||||
/>
|
||||
<Zeile
|
||||
titel="Encoding-Worker"
|
||||
/* Auf einem Windows-PC gibt es keinen "Encoding-Worker" und
|
||||
keinen Worker-Container -- Rippy komprimiert selbst. Der
|
||||
Commander am 28.08.2026: "Das gilt fuer die ganze
|
||||
standalone version fuer Windows." */
|
||||
titel={betrieb.kann.externe_worker ? 'Encoding-Worker' : 'Kompression'}
|
||||
geladen={geladen}
|
||||
gut={encoderWorker.length > 0}
|
||||
gutText={encoderWorker.map(w => {
|
||||
@@ -207,7 +213,9 @@ export default function FirstRunWizard({ onDone }: { onDone: () => void }) {
|
||||
.filter(Boolean).join(', ')
|
||||
return zusatz ? `${w.name} (${zusatz})` : w.name
|
||||
}).join(' · ')}
|
||||
schlechtText="Noch kein Worker gemeldet. Beim ersten Start dauert das bis zu einer Minute — diese Anzeige aktualisiert sich selbst. Bleibt es dabei, läuft der Worker-Container nicht."
|
||||
schlechtText={betrieb.kann.externe_worker
|
||||
? 'Noch kein Worker gemeldet. Beim ersten Start dauert das bis zu einer Minute — diese Anzeige aktualisiert sich selbst. Bleibt es dabei, läuft der Worker-Container nicht.'
|
||||
: 'Rippy meldet noch keine Encoder. Beim ersten Start dauert das bis zu einer Minute — diese Anzeige aktualisiert sich selbst.'}
|
||||
/>
|
||||
<Zeile
|
||||
titel="Freier Platz"
|
||||
@@ -351,7 +359,7 @@ export default function FirstRunWizard({ onDone }: { onDone: () => void }) {
|
||||
<span>Rippy hat deine Rechenleistung gemessen und passt die Wahl an.</span>
|
||||
</p>
|
||||
<p className="text-xs mt-2 text-amber-700/90 dark:text-amber-300/90">
|
||||
Keiner deiner Worker kann AVX2 — die Vektorbefehle, von denen H.265
|
||||
{betrieb.kann.externe_worker ? 'Keiner deiner Worker' : 'Dieser Rechner'} kann kein AVX2 — die Vektorbefehle, von denen H.265
|
||||
lebt. 4K in H.265 würde hier <strong>ein bis zwei Tage pro Film</strong>
|
||||
{' '}dauern. Deshalb wird eingestellt:
|
||||
</p>
|
||||
|
||||
+39
-5
@@ -223,13 +223,47 @@ def datenbank_url(werte: dict, standard_pfad: str = None) -> str:
|
||||
|
||||
|
||||
def standard_datenbankpfad() -> str:
|
||||
"""Wo die SQLite-Datei liegt, wenn niemand etwas anderes sagt.
|
||||
r"""Wo die SQLite-Datei liegt, wenn niemand etwas anderes sagt.
|
||||
|
||||
Je Plattform dort, wo Dienste ihre Daten ablegen — nicht neben dem
|
||||
Programm. Ein Programmordner ist unter Windows für einen Dienst nicht
|
||||
zuverlässig beschreibbar, und unter Linux widerspräche es dem FHS.
|
||||
## Warum unter Windows NICHT mehr %ProgramData% (28.08.2026)
|
||||
|
||||
Hier stand `%ProgramData%\Rippy\rippy.db`, mit der Begründung: „Ein
|
||||
Programmordner ist unter Windows für einen DIENST nicht zuverlässig
|
||||
beschreibbar." Das stimmte — für den Windows-Dienst, den es nie gab.
|
||||
Rippy läuft als der angemeldete Benutzer (Autostart unter `HKCU`, siehe
|
||||
`windows_app.py`), und der Grund ist damit hinfällig.
|
||||
|
||||
Geblieben war der Schaden. Der Commander meldete: **„Es fehlt der 1st run
|
||||
wizzard wenn man es installiert."** Nachgemessen:
|
||||
|
||||
Installation %LOCALAPPDATA%\Rippy (wird deinstalliert)
|
||||
Datenbank %ProgramData%\Rippy (bleibt liegen)
|
||||
|
||||
Eine „frische" Installation erbte also die alte Datenbank samt
|
||||
`setup.done = true`, und der Ersteinrichtungs-Assistent erschien nie
|
||||
wieder. Ein Deinstallieren, das den Zustand stehen lässt, ist kein
|
||||
Deinstallieren.
|
||||
|
||||
Jetzt liegt alles, was Rippy gehört, in EINEM Ordner: Programm, UI,
|
||||
Werkzeuge, Protokoll, WebView2-Zwischenspeicher — und die Datenbank.
|
||||
|
||||
Unter Linux bleibt `/var/lib/rippy`: Dort läuft Rippy wirklich als
|
||||
Systemdienst, und der FHS sagt genau das.
|
||||
"""
|
||||
if os.name == "nt":
|
||||
basis = os.environ.get("PROGRAMDATA") or os.path.expanduser("~")
|
||||
basis = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~")
|
||||
return os.path.join(basis, "Rippy", "rippy.db")
|
||||
return "/var/lib/rippy/rippy.db"
|
||||
|
||||
|
||||
def alter_datenbankpfad() -> str:
|
||||
r"""Der Ort VOR dem 28.08.2026 — nur noch zum Umziehen und Aufräumen.
|
||||
|
||||
Wer Rippy schon installiert hatte, hat seine Daten dort. Sie kommentarlos
|
||||
liegen zu lassen wäre ein stiller Verlust; sie zu löschen wäre schlimmer.
|
||||
`windows_app.datenbank_umziehen()` holt sie ab.
|
||||
"""
|
||||
if os.name != "nt":
|
||||
return ""
|
||||
basis = os.environ.get("PROGRAMDATA") or ""
|
||||
return os.path.join(basis, "Rippy", "rippy.db") if basis else ""
|
||||
|
||||
@@ -233,6 +233,80 @@ def test_die_testsuite_ruehrt_den_ECHTEN_eintrag_nicht_an(tmp_path, monkeypatch)
|
||||
)
|
||||
|
||||
|
||||
# ── Die Datenbank am richtigen Ort ──────────────────────────────────────
|
||||
def test_datenbank_liegt_im_installationsordner():
|
||||
"""DER Befund vom 28.08.2026: „Es fehlt der 1st run wizzard wenn man es
|
||||
installiert."
|
||||
|
||||
Die Datenbank lag unter %ProgramData%\\Rippy, die Installation unter
|
||||
%LOCALAPPDATA%\\Rippy. Beim Deinstallieren blieb sie liegen, und die
|
||||
naechste Installation erbte `setup.done = true` — der
|
||||
Ersteinrichtungs-Assistent erschien nie wieder.
|
||||
|
||||
Der Grund fuer den alten Ort war ein Windows-DIENST, den es nie gab.
|
||||
"""
|
||||
from rippy import config
|
||||
|
||||
pfad = config.standard_datenbankpfad()
|
||||
if os.name != "nt":
|
||||
assert pfad == "/var/lib/rippy/rippy.db"
|
||||
return
|
||||
assert "ProgramData" not in pfad, (
|
||||
"Die Datenbank liegt wieder ausserhalb des Installationsordners — "
|
||||
"dann ueberlebt sie jede Deinstallation."
|
||||
)
|
||||
assert pfad.lower().startswith(
|
||||
(os.environ.get("LOCALAPPDATA") or "").lower())
|
||||
|
||||
|
||||
@nur_windows
|
||||
def test_alte_datenbank_wird_uebernommen(tmp_path, monkeypatch):
|
||||
"""Wer Rippy schon benutzt hat, soll Jobs und Einstellungen behalten.
|
||||
Die Datei wird verschoben, nicht ignoriert und nicht geloescht."""
|
||||
from rippy import config
|
||||
|
||||
alt = tmp_path / "alt" / "Rippy" / "rippy.db"
|
||||
alt.parent.mkdir(parents=True)
|
||||
alt.write_bytes(b"alte-daten")
|
||||
neu = tmp_path / "neu" / "Rippy" / "rippy.db"
|
||||
|
||||
monkeypatch.setattr(config, "alter_datenbankpfad", lambda: str(alt))
|
||||
monkeypatch.setattr(config, "standard_datenbankpfad", lambda: str(neu))
|
||||
|
||||
assert windows_app.datenbank_umziehen() == str(neu)
|
||||
assert neu.read_bytes() == b"alte-daten"
|
||||
assert not alt.exists()
|
||||
|
||||
|
||||
@nur_windows
|
||||
def test_vorhandene_datenbank_wird_NICHT_ueberschrieben(tmp_path, monkeypatch):
|
||||
"""Die BENUTZTE gewinnt, nicht die aeltere."""
|
||||
from rippy import config
|
||||
|
||||
alt = tmp_path / "alt" / "rippy.db"
|
||||
alt.parent.mkdir(parents=True)
|
||||
alt.write_bytes(b"alt")
|
||||
neu = tmp_path / "neu" / "rippy.db"
|
||||
neu.parent.mkdir(parents=True)
|
||||
neu.write_bytes(b"in-benutzung")
|
||||
|
||||
monkeypatch.setattr(config, "alter_datenbankpfad", lambda: str(alt))
|
||||
monkeypatch.setattr(config, "standard_datenbankpfad", lambda: str(neu))
|
||||
|
||||
assert windows_app.datenbank_umziehen() == ""
|
||||
assert neu.read_bytes() == b"in-benutzung"
|
||||
|
||||
|
||||
def test_alte_orte_fassen_nur_rippy_an(monkeypatch):
|
||||
"""Ein `%ProgramData%` ohne unseren Namen darin waere ein fremder Ordner."""
|
||||
from rippy import config
|
||||
|
||||
monkeypatch.setattr(config, "alter_datenbankpfad",
|
||||
lambda: "C:" + BACKSLASH + "ProgramData" + BACKSLASH
|
||||
+ "Fremd" + BACKSLASH + "rippy.db")
|
||||
assert windows_app.alte_orte() == []
|
||||
|
||||
|
||||
# ── Rueckmeldung ohne Konsole ───────────────────────────────────────────
|
||||
def test_ohne_konsole_kommt_ein_fenster(monkeypatch):
|
||||
"""Seit die EXE ohne Konsole gebaut wird (Wunsch des Commanders am
|
||||
|
||||
@@ -147,6 +147,7 @@ def installieren(ziel: str = None, autostart: bool = True,
|
||||
pass
|
||||
|
||||
ui_auspacken(ziel)
|
||||
datenbank_umziehen()
|
||||
|
||||
# Mitgelieferte Werkzeuge (HandBrakeCLI) neben das Programm legen. Dort
|
||||
# sucht katalog.kandidaten() VOR allen Systempfaden.
|
||||
@@ -183,6 +184,55 @@ def installieren(ziel: str = None, autostart: bool = True,
|
||||
return ziel
|
||||
|
||||
|
||||
def alte_orte() -> list:
|
||||
"""Ordner ausserhalb des Installationsordners, die Rippy einmal angelegt hat.
|
||||
|
||||
Reine Aufzaehlung, damit `deinstallieren()` pruefbar bleibt und niemand
|
||||
versehentlich einen fremden Ordner in die Liste schreibt.
|
||||
"""
|
||||
from rippy import config
|
||||
|
||||
alt = config.alter_datenbankpfad()
|
||||
if not alt:
|
||||
return []
|
||||
ordner = os.path.dirname(alt)
|
||||
# Nur wenn er wirklich Rippy gehoert. Ein `%ProgramData%` ohne unseren
|
||||
# Namen darin waere ein fremder Ordner, und den fasst niemand an.
|
||||
if os.path.basename(ordner).lower() != "rippy" or not os.path.isdir(ordner):
|
||||
return []
|
||||
return [ordner]
|
||||
|
||||
|
||||
def datenbank_umziehen() -> str:
|
||||
r"""Holt eine Datenbank vom alten Ort ab. Gibt den Zielpfad zurück, sonst "".
|
||||
|
||||
Bis zum 28.08.2026 lag sie unter `%ProgramData%\Rippy\rippy.db` — also
|
||||
AUSSERHALB des Installationsordners. Die Deinstallation räumte sie damit
|
||||
nicht weg, und eine „frische" Installation erbte den alten Zustand samt
|
||||
`setup.done = true`. Genau deshalb fehlte dem Commander der
|
||||
Ersteinrichtungs-Assistent.
|
||||
|
||||
Wer Rippy schon benutzt hat, soll seine Jobs und Einstellungen behalten:
|
||||
Die Datei wird verschoben, nicht ignoriert und nicht gelöscht. Gibt es am
|
||||
neuen Ort schon eine, bleibt sie unangetastet — die neuere gewinnt nicht
|
||||
automatisch, aber die BENUTZTE.
|
||||
"""
|
||||
from rippy import config
|
||||
|
||||
alt = config.alter_datenbankpfad()
|
||||
neu = config.standard_datenbankpfad()
|
||||
if not alt or not os.path.isfile(alt) or os.path.isfile(neu):
|
||||
return ""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(neu), exist_ok=True)
|
||||
shutil.move(alt, neu)
|
||||
except OSError as e:
|
||||
melden(" Alte Datenbank konnte nicht umgezogen werden (%s)." % e)
|
||||
return ""
|
||||
melden(" Datenbank uebernommen: %s -> %s" % (alt, neu))
|
||||
return neu
|
||||
|
||||
|
||||
def ui_auspacken(ziel: str) -> str:
|
||||
r"""Legt die Weboberfläche NEBEN das Programm. Gibt den Ordner zurück.
|
||||
|
||||
@@ -282,6 +332,19 @@ def deinstallieren(still: bool = False) -> None:
|
||||
|
||||
fenster_modul.helfer_beenden(os.path.join(ordner, "fenster"))
|
||||
|
||||
# Der ALTE Datenbank-Ort (bis 28.08.2026 %ProgramData%\Rippy) liegt
|
||||
# ausserhalb des Installationsordners. Blieb er stehen, erbte die naechste
|
||||
# Installation den alten Zustand — genau daran fehlte dem Commander der
|
||||
# Ersteinrichtungs-Assistent. Ein Deinstallieren, das Zustand stehen
|
||||
# laesst, ist kein Deinstallieren.
|
||||
for rest in alte_orte():
|
||||
try:
|
||||
shutil.rmtree(rest)
|
||||
if not still:
|
||||
melden(" Alter Datenordner entfernt: %s" % rest)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
laeuft_von_hier = os.path.abspath(_eigene_datei()).lower().startswith(
|
||||
os.path.abspath(ordner).lower())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user