fix: die Sicherung liess den Papierkorb aus -- ein Versprechen, das sie nicht halten konnte

GEFUNDEN BEIM NACHSEHEN, nicht beim Suchen: das Sicherungsskript packte

    tar czf ... -C /data files -C /data/.sicherung media.db

also die Bilder und die Datenbank. Die EINTRAEGE des Papierkorbs 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: die Oberflaeche verspricht etwas, das die
Platte nicht hergibt. Genau die Sorte stiller Abweichung, wegen der dieses
Projekt Sicherungen ueberhaupt gegenprueft.

Er ist jetzt im Archiv, und er kostet fast nichts: was darin liegt, ist
hoechstens 30 Tage alt, danach raeumt der Dienst selbst auf. Die Abfrage
`if [ -d /data/papierkorb ]` davor ist noetig -- hat noch nie jemand etwas
geloescht, gibt es den Ordner gar nicht, und tar braeche mit "Cannot stat" ab.
Ein `mkdir` als Ausweg ginge nicht: das Volume haengt nur lesend.

Gemessen an einem echten Lauf, nicht angenommen:

    d4rk_media-2026-08-11-2307.tar.gz -- 311,9 MB
    in der Sicherung: 4452 Medieneintraege, 4452 Dateien, 2 Token, 2 Benutzer
    hochgeladen (201), zurueckgeholt und verglichen: identisch
    Inhalt:  4459 files/   1 media.db   1 papierkorb/

UND DER PAPIERKORB SAGT ES JETZT SELBST. Die Liste prueft je Zeile, ob die
Datei wirklich noch daliegt (hoechstens 500 stat-Aufrufe), und zeigt fehlende
durchgestrichen samt Erklaerung. Die Sicherung nimmt ihn zwar inzwischen mit --
aber ein Papierkorb, der etwas verspricht, muss es halten koennen, und wenn
nicht, soll er es SAGEN statt es beim Druecken herauszufinden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 23:11:38 +02:00
co-authored by Claude Opus 5
parent 92b49a59a5
commit 06e924daf5
5 changed files with 94 additions and 15 deletions
+30 -3
View File
@@ -1746,14 +1746,41 @@ export async function papierkorbAusleeren(): Promise<number> {
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.
+6
View File
@@ -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 = {
+26 -3
View File
@@ -105,6 +105,20 @@ export function Papierkorb() {
)}
</div>
{daten.verwaist > 0 && (
<p className="flex items-start gap-2 rounded-md border border-warn/40 bg-warn/10
px-3 py-2 text-xs text-warn">
<AlertTriangle size={14} className="mt-0.5 shrink-0" />
<span>
Bei <strong>{daten.verwaist}</strong> {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.
</span>
</p>
)}
<Karte>
{items.length === 0 ? (
<Leer
@@ -131,7 +145,10 @@ export function Papierkorb() {
</thead>
<tbody>
{items.map((e) => (
<tr key={e.id} className="border-t border-rand">
<tr
key={e.id}
className={`border-t border-rand ${e.dateiDa ? '' : 'opacity-50'}`}
>
<td className="px-3 py-1.5">
<input
type="checkbox"
@@ -148,8 +165,14 @@ export function Papierkorb() {
/>
</td>
<td
className="max-w-0 truncate px-3 py-1.5 font-mono text-xs"
title={e.path}
className={`max-w-0 truncate px-3 py-1.5 font-mono text-xs ${
e.dateiDa ? '' : 'line-through'
}`}
title={
e.dateiDa
? e.path
: `${e.path} — die Datei fehlt auf der Platte`
}
>
{e.path}
</td>