diff --git a/README.md b/README.md index cab2d3a..e2debe1 100644 --- a/README.md +++ b/README.md @@ -178,10 +178,16 @@ Wiki die Kopie. Wer dort tippt, verliert es beim nächsten Lauf. tar xzf d4rk_media-JJJJ-MM-TT-HHMM.tar.gz ``` -Ergibt `media.db` und `files/`. Beides ins Volume legen, während der Container -steht. Vorschaubilder und WebP-Fassungen sind **nicht** im Archiv — beides ist -abgeleitet und wird mit zwei Knöpfen unter *Speicher → Wartung* neu gerechnet. -Der Papierkorb ist ebenfalls nicht drin: was dort liegt, ist gelöscht. +Ergibt `media.db`, `files/` und `papierkorb/`. Alles drei ins Volume legen, +während der Container steht. Vorschaubilder und WebP-Fassungen sind **nicht** im +Archiv — beides ist abgeleitet und wird mit zwei Knöpfen unter +*Speicher → Wartung* neu gerechnet. + +> Der Papierkorb war anfangs **nicht** dabei, und das war ein Fehler: seine +> Einträge stehen in `media.db` und waren damit gesichert, die Dateien lagen +> daneben und waren es nicht. Nach einem Zurückspielen hätte der Papierkorb +> Zeilen gezeigt, deren „zurückholen" ins Leere greift. Er kostet fast nichts — +> was darin liegt, ist höchstens 30 Tage alt. --- diff --git a/server/src/routes/dash.ts b/server/src/routes/dash.ts index db72ff6..e6caad1 100644 --- a/server/src/routes/dash.ts +++ b/server/src/routes/dash.ts @@ -1746,14 +1746,41 @@ export async function papierkorbAusleeren(): Promise { return alt.length } -dashRoutes.get('/papierkorb', (c) => { - const items = db +dashRoutes.get('/papierkorb', async (c) => { + const roh = db .prepare('SELECT * FROM papierkorb ORDER BY geloescht DESC LIMIT 500') .all() as PapierkorbEintrag[] + + // OB DIE DATEI WIRKLICH NOCH DA IST -- ein stat je Zeile, hoechstens 500. + // + // Der Eintrag steht in media.db, die Datei liegt daneben auf der Platte: + // zwei Wahrheiten, die auseinanderlaufen koennen. Genau das ist passiert, + // solange die Sicherung /data/papierkorb ausliess -- nach einem + // Zurueckspielen zeigte der Papierkorb Zeilen, deren "zurueckholen" ins + // Leere griff. Die Sicherung nimmt ihn inzwischen mit; DIESE Pruefung + // bleibt trotzdem, weil ein Papierkorb, der etwas verspricht, es auch + // halten koennen muss -- und wenn nicht, es sagen soll, statt es erst beim + // Druecken herauszufinden. + const items = await Promise.all( + roh.map(async (e) => { + try { + await stat(papierkorbPfad(e.ablage)) + return { ...e, dateiDa: true } + } catch { + return { ...e, dateiDa: false } + } + }), + ) + const summe = db .prepare('SELECT COUNT(*) AS n, COALESCE(SUM(size),0) AS bytes FROM papierkorb') .get() as { n: number; bytes: number } - return c.json({ items, ...summe, haltbarTage: PAPIERKORB_TAGE }) + return c.json({ + items, + ...summe, + haltbarTage: PAPIERKORB_TAGE, + verwaist: items.filter((e) => !e.dateiDa).length, + }) }) /** Zurueck an ihren Platz. diff --git a/server/ui/src/api.ts b/server/ui/src/api.ts index d12608d..b313445 100644 --- a/server/ui/src/api.ts +++ b/server/ui/src/api.ts @@ -140,6 +140,10 @@ export type PapierkorbEintrag = { mime: string geloescht: number von: string + /** Liegt die Datei wirklich noch im Papierkorb? Der Eintrag steht in der + * Datenbank, die Datei daneben auf der Platte — zwei Wahrheiten, die + * auseinanderlaufen koennen. */ + dateiDa: boolean } export type PapierkorbSeite = { @@ -147,6 +151,8 @@ export type PapierkorbSeite = { n: number bytes: number haltbarTage: number + /** Eintraege ohne Datei. Sollte 0 sein. */ + verwaist: number } export type Speicher = { diff --git a/server/ui/src/pages/Papierkorb.tsx b/server/ui/src/pages/Papierkorb.tsx index 4a69fbd..6fdf1d4 100644 --- a/server/ui/src/pages/Papierkorb.tsx +++ b/server/ui/src/pages/Papierkorb.tsx @@ -105,6 +105,20 @@ export function Papierkorb() { )} + {daten.verwaist > 0 && ( +

+ + + Bei {daten.verwaist} {daten.verwaist === 1 ? 'Eintrag' : 'Einträgen'}{' '} + fehlt die Datei auf der Platte — zurückholen geht dort nicht. Das kommt + vor, wenn eine Sicherung zurückgespielt wurde, die den Papierkorb nicht + enthielt. Sie stehen unten durchgestrichen; „endgültig löschen" räumt sie + auf. + +

+ )} + {items.length === 0 ? ( {items.map((e) => ( - + {e.path} diff --git a/tools/sichern.ps1 b/tools/sichern.ps1 index 80ad498..578ebb4 100644 --- a/tools/sichern.ps1 +++ b/tools/sichern.ps1 @@ -114,13 +114,29 @@ $erwartet = if ($ausgabe -match '(\d+) Medieneintraege') { [int]$Matches[1] } el # -- 2. Alles in ein Archiv ---------------------------------------------------- Schritt 'Bilder und Datenbank packen' -# Zwei -C: die Bilder kommen als files/... in das Archiv, die Aufnahme als -# media.db daneben. Bewusst das eigene Abbild statt alpine -- dort ist GNU tar -# drin, und das kann mehrere -C. +# Drei -C: die Bilder kommen als files/... in das Archiv, die Aufnahme als +# media.db daneben, und der Papierkorb als papierkorb/. Bewusst das eigene +# Abbild statt alpine -- dort ist GNU tar drin, und das kann mehrere -C. +# +# DER PAPIERKORB MUSS MIT, und das ist nachgerechnet und nicht Vorsicht: seine +# EINTRAEGE stehen in media.db und waren damit laengst gesichert -- die DATEIEN +# lagen unter /data/papierkorb und waren es nicht. Nach einem Zurueckspielen +# haette der Papierkorb also Zeilen gezeigt, deren "zurueckholen" ins Leere +# greift. Ein Papierkorb, der ein Versprechen gibt, das er nach einer +# Wiederherstellung nicht halten kann, ist schlimmer als keiner. +# +# Er kostet fast nichts: was darin liegt, ist hoechstens 30 Tage alt, danach +# raeumt der Dienst selbst auf. +# +# Die Abfrage davor ist noetig: hat noch nie jemand etwas geloescht, gibt es den +# Ordner gar nicht, und tar braeche mit "Cannot stat" ab. Eine Sicherung, die +# daran scheitert, dass nichts im Papierkorb liegt, waere absurd. Das Volume +# haengt NUR LESEND -- ein `mkdir` als Ausweg ginge hier also ohnehin nicht. +$packen = "if [ -d /data/papierkorb ]; then tar czf '/out/$name' -C /data files -C /data/.sicherung media.db -C /data papierkorb; else tar czf '/out/$name' -C /data files -C /data/.sicherung media.db; fi" docker run --rm ` -v "${Volume}:/data:ro" ` -v "${Ziel}:/out" ` - --entrypoint sh $Abbild -c "tar czf '/out/$name' -C /data files -C /data/.sicherung media.db" + --entrypoint sh $Abbild -c $packen if ($LASTEXITCODE -ne 0) { Schlecht 'Packen fehlgeschlagen.'; exit 1 } # Die Zwischenaufnahme im Volume wieder weg: sie gehoert nicht zum Bestand und @@ -315,5 +331,6 @@ Melde $true $zahlen.media $groesse $inDerWolke $null Write-Host '' Gut "Fertig: $archiv" -Write-Host " Zurueckspielen: tar xzf , dann media.db und files/ ins Volume legen," -ForegroundColor DarkGray +Write-Host " Zurueckspielen: tar xzf , dann media.db, files/ und papierkorb/" -ForegroundColor DarkGray +Write-Host " ins Volume legen," -ForegroundColor DarkGray Write-Host " waehrend der Container steht. Siehe ROADMAP." -ForegroundColor DarkGray