Verschwundene Dateien werden gemeldet, nicht stillschweigend gelöscht
Vorher räumte der Scan jede Datei weg, die er nicht mehr sah, und mit der letzten Datei fiel der Titel samt Sehfortschritt aller Benutzer. Für eine wirklich gelöschte Datei ist das richtig. Für eine Platte im Ruhezustand, ein noch nicht eingehängtes Volume oder einen umbenannten Ordner ist es Datenverlust — und für den Scan sehen alle vier Fälle gleich aus. Jetzt trägt MediaFile ein MissingSince. Der Eintrag bleibt stehen, steht in der Verwaltung unter "Nicht mehr gefunden", und gelöscht wird auf Ansage. Taucht die Datei wieder auf, verschwindet die Markierung von selbst. Dazu die Notbremse, und sie ist der eigentliche Grund für den ganzen Punkt: Fehlt der Wurzelpfad, oder liegt dort keine einzige Mediendatei mehr, obwohl vorher welche da waren, bricht der Lauf ab und sagt warum. Ohne sie erklärte ein nicht eingehängtes Volume beim Start des NAS den gesamten Bestand für verschwunden — bei 458 Titeln. Der Abschnitt in der Verwaltung erscheint nur, wenn es etwas zu melden gibt. Ein dauerhaft leerer wäre Rauschen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
01ff41fd71
commit
8b67a7ec88
@@ -428,6 +428,20 @@ export type Ordnerstand = {
|
||||
note: string | null;
|
||||
};
|
||||
|
||||
export type VermissteDatei = {
|
||||
fileId: string; path: string; versionLabel: string | null;
|
||||
missingSince: string; libraryName: string;
|
||||
title: string | null; movieId: string | null; episodeId: string | null;
|
||||
};
|
||||
|
||||
/** Dateien, die der letzte Scan nicht mehr gefunden hat. */
|
||||
export const vermisste = () => anfrage<VermissteDatei[]>('/libraries/missing');
|
||||
|
||||
/** Entfernt eine vermisste Datei endgültig — ohne Kennung alle. */
|
||||
export const vermisstesEntfernen = (fileId?: string) =>
|
||||
anfrage<{ removed: number }>('/libraries/missing' + (fileId ? `?fileId=${fileId}` : ''),
|
||||
{ method: 'DELETE' });
|
||||
|
||||
export type Zusammenlegungsbericht = { filme: number; serien: number; gesamt: number };
|
||||
|
||||
/** Legt Titel zusammen, die denselben TMDB-Eintrag meinen. */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
bibliotheken, bibliothekAnlegen, bibliothekUmbenennen, bibliothekLoeschen,
|
||||
scanStarten, scanstand, metadatenAuffrischen, doppelteZusammenlegen,
|
||||
vermisste, vermisstesEntfernen, type VermissteDatei,
|
||||
benutzer, benutzerAnlegen, metadatenEinstellung, metadatenSetzen,
|
||||
servernameSetzen, systemauskunft,
|
||||
laufendeWiedergaben, serverzustand, bildAdresse,
|
||||
@@ -21,7 +22,8 @@ import { icon } from '../icons';
|
||||
* Seitenleiste steht auf ihr und muss dann neu gezeichnet werden. */
|
||||
export function verwaltung(geaendert: () => void = () => {}): HTMLElement {
|
||||
const wurzel = document.createElement('div');
|
||||
wurzel.append(dashboard(), bibliotheksbereich(geaendert), benutzerbereich(), serverbereich(geaendert));
|
||||
wurzel.append(dashboard(), vermisstenbereich(geaendert), bibliotheksbereich(geaendert),
|
||||
benutzerbereich(), serverbereich(geaendert));
|
||||
return wurzel;
|
||||
}
|
||||
|
||||
@@ -149,6 +151,82 @@ function laufzeit(sekunden: number): string {
|
||||
return `${minuten} Min.`;
|
||||
}
|
||||
|
||||
/* ---------- Nicht mehr gefunden ---------- */
|
||||
|
||||
/** Was in der Mediathek steht, aber nicht mehr auf der Platte liegt.
|
||||
*
|
||||
* **Erscheint nur, wenn es etwas zu melden gibt.** Ein dauerhaft leerer
|
||||
* Abschnitt wäre Rauschen; ein gefüllter ist eine Nachricht.
|
||||
*
|
||||
* Der Scan löscht nichts mehr von selbst. Eine kurz nicht eingehängte Platte
|
||||
* sah vorher aus wie „die Filme sind weg" und kostete Titel samt
|
||||
* Sehfortschritt aller Benutzer. Beides ist für den Scan nicht zu
|
||||
* unterscheiden — also entscheidet hier ein Mensch. */
|
||||
function vermisstenbereich(geaendert: () => void): HTMLElement {
|
||||
const bereich = document.createElement('section');
|
||||
bereich.className = 'hidden';
|
||||
|
||||
const liste = document.createElement('div');
|
||||
liste.className = 'einstellungen';
|
||||
|
||||
const alle = knopf('Alle entfernen', 'btn btn-gefahr');
|
||||
alle.onclick = () => nachfragen({
|
||||
titel: 'Alle vermissten Einträge entfernen',
|
||||
text: 'Entfernt alle Dateien, die nicht mehr gefunden werden, aus der '
|
||||
+ 'Mediathek — mitsamt den Titeln, von denen keine Datei übrig bleibt. '
|
||||
+ 'Auf der Platte wird nichts gelöscht.',
|
||||
bestaetigen: 'Entfernen',
|
||||
tun: async () => { await vermisstesEntfernen(); await laden(); geaendert(); },
|
||||
});
|
||||
|
||||
bereich.append(abschnitt('Nicht mehr gefunden', [alle]), liste);
|
||||
void laden();
|
||||
|
||||
async function laden(): Promise<void> {
|
||||
let fehlend: VermissteDatei[];
|
||||
try { fehlend = await vermisste(); } catch { bereich.classList.add('hidden'); return; }
|
||||
|
||||
if (fehlend.length === 0) { bereich.classList.add('hidden'); return; }
|
||||
bereich.classList.remove('hidden');
|
||||
|
||||
liste.innerHTML = '';
|
||||
fehlend.forEach(v => {
|
||||
const d = document.createElement('div');
|
||||
d.className = 'einstellung';
|
||||
|
||||
const oben = document.createElement('div');
|
||||
oben.className = 'knopfreihe';
|
||||
const text = document.createElement('div');
|
||||
text.className = 'zeile';
|
||||
text.style.flex = '1';
|
||||
const seit = new Date(v.missingSince).toLocaleDateString('de-CH',
|
||||
{ day: '2-digit', month: 'long', year: 'numeric' });
|
||||
text.innerHTML = `<strong>${esc(v.title ?? '(ohne Titel)')}`
|
||||
+ `${v.versionLabel ? ` · ${esc(v.versionLabel)}` : ''}</strong>`
|
||||
+ `<span class="winzig">${esc(v.libraryName)} · vermisst seit ${esc(seit)}</span>`;
|
||||
oben.appendChild(text);
|
||||
|
||||
const weg = knopf('Entfernen', 'btn');
|
||||
weg.onclick = async () => {
|
||||
weg.disabled = true;
|
||||
try { await vermisstesEntfernen(v.fileId); await laden(); geaendert(); }
|
||||
catch (fehler) { d.appendChild(fehlerzeile((fehler as Error).message)); weg.disabled = false; }
|
||||
};
|
||||
oben.appendChild(weg);
|
||||
d.appendChild(oben);
|
||||
|
||||
const pfad = document.createElement('div');
|
||||
pfad.className = 'winzig';
|
||||
pfad.textContent = v.path;
|
||||
d.appendChild(pfad);
|
||||
|
||||
liste.appendChild(d);
|
||||
});
|
||||
}
|
||||
|
||||
return bereich;
|
||||
}
|
||||
|
||||
/* ---------- Bibliotheken ---------- */
|
||||
|
||||
function bibliotheksbereich(geaendert: () => void): HTMLElement {
|
||||
|
||||
Reference in New Issue
Block a user