Drei Zähler für dieselbe Sache, und keiner davon offensichtlich falsch
Während eines Scans stand in der Mediathek 20, in der Verwaltung 14 und in der Seitenleiste 0. Die Verwaltung frischte alle fünf Runden auf, die Seitenleiste erst am Ende des Laufs. Jetzt laufen beide auf demselben Takt, alle drei Runden. Dazu: während ein Lauf läuft, sind Scannen, Metadaten auffrischen, Zurücksetzen und Entfernen gesperrt — die ersten beiden prallen am Server ab und sind nur verwirrend, die anderen beiden löschen Zeilen, während der Lauf welche schreibt. Umbenennen und Prüfung bleiben bedienbar. Die Sperre richtet sich nach dem Zustand, den der Server meldet, nicht nach dem Klick: wer die Seite neu lädt, während ein Lauf läuft, findet sie ebenfalls gesperrt vor. Die Reihenfolge der Mediatheken wird jetzt in der Seitenleiste umsortiert, über „Seitenleiste bearbeiten" neben der Überschrift. Sie wirkt dort, also gehört der Griff dorthin — vorher schob man in den Einstellungen und musste danach nachsehen, ob es das war, was man wollte. Der Zuordnungsdialog zeigt die Herkunft: Ordner, Zahl der Dateien und ein paar Namen. Liegt eine Zuordnung völlig daneben, sagt der angezeigte Titel nichts mehr darüber, welche Datei gemeint ist. Und SQLite läuft jetzt im WAL-Modus. In der Vorgabe sperrt ein Schreiber die ganze Datei: solange der Scan schrieb, kam keine Leseanfrage durch. Genau so sah es aus — Bilddialog und Protokoll liefen während eines Laufs sporadisch ins Leere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e1b3625809
commit
a22fa9b58e
+73
-53
@@ -4,7 +4,7 @@ import {
|
||||
vermisste, vermisstesEntfernen, type VermissteDatei,
|
||||
protokolltage, protokollAdresse, sprachen, spracheSetzen, uebersetzungenHolen,
|
||||
meineSprache, zuPruefen, veroeffentlichen, zurueckziehen, pruefungSetzen,
|
||||
mediathekFreigeben, reihenfolgeSetzen, mediathekZuruecksetzen,
|
||||
mediathekFreigeben, mediathekZuruecksetzen,
|
||||
type Pruefeintrag,
|
||||
benutzer, benutzerAnlegen, metadatenEinstellung, metadatenSetzen,
|
||||
servernameSetzen, systemauskunft,
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from '../api';
|
||||
import { esc, dauer, groesse, knopf, fehlerzeile, leer, meldung } from '../util';
|
||||
import { dialog, nachfragen } from '../dialog';
|
||||
import { icon, icons } from '../icons';
|
||||
import { icon } from '../icons';
|
||||
|
||||
/** Die Verwaltung.
|
||||
*
|
||||
@@ -545,21 +545,6 @@ function vermisstenbereich(geaendert: () => void): HTMLElement {
|
||||
/* ---------- Bibliotheken ---------- */
|
||||
|
||||
function bibliotheksbereich(geaendert: () => void, signal: AbortSignal): HTMLElement {
|
||||
/** Die Liste, wie sie gerade steht — für das Umsortieren. */
|
||||
let alleBibliotheken: Bibliothek[] = [];
|
||||
|
||||
/** Schiebt einen Eintrag und schickt die ganze neue Reihenfolge. */
|
||||
async function schieben(von: number, nach: number): Promise<void> {
|
||||
if (nach < 0 || nach >= alleBibliotheken.length) return;
|
||||
|
||||
const neu = [...alleBibliotheken];
|
||||
const [eintrag] = neu.splice(von, 1);
|
||||
neu.splice(nach, 0, eintrag!);
|
||||
|
||||
await reihenfolgeSetzen(neu.map(b => b.id));
|
||||
await laden();
|
||||
geaendert();
|
||||
}
|
||||
|
||||
const bereich = document.createElement('section');
|
||||
|
||||
@@ -613,15 +598,14 @@ function bibliotheksbereich(geaendert: () => void, signal: AbortSignal): HTMLEle
|
||||
liste.appendChild(leer('Noch keine Mediathek. Lege oben eine an.'));
|
||||
return;
|
||||
}
|
||||
alleBibliotheken = alle;
|
||||
alle.forEach((b, i) => liste.appendChild(block(b, i)));
|
||||
alle.forEach(b => liste.appendChild(block(b)));
|
||||
} catch (fehler) {
|
||||
liste.innerHTML = '';
|
||||
liste.appendChild(fehlerzeile(meldung(fehler)));
|
||||
}
|
||||
}
|
||||
|
||||
function block(b: Bibliothek, platz: number): HTMLElement {
|
||||
function block(b: Bibliothek): HTMLElement {
|
||||
const d = document.createElement('div');
|
||||
d.className = 'einstellung';
|
||||
|
||||
@@ -649,12 +633,35 @@ function bibliotheksbereich(geaendert: () => void, signal: AbortSignal): HTMLEle
|
||||
|
||||
const los = knopf('Scannen', 'btn');
|
||||
los.onclick = async () => {
|
||||
los.disabled = true;
|
||||
laufendSetzen(true);
|
||||
try { await scanStarten(b.id); await verfolgen(true); }
|
||||
catch (fehler) { stand.textContent = meldung(fehler); }
|
||||
los.disabled = false;
|
||||
catch (fehler) { stand.textContent = meldung(fehler); laufendSetzen(false); }
|
||||
};
|
||||
|
||||
/** Sperrt alles, was einen laufenden Lauf stören würde.
|
||||
*
|
||||
* **Nicht nur „Scannen".** Ein zweiter Scan prallt am Server ab und ist
|
||||
* nur verwirrend — „Zurücksetzen" und „Entfernen" dagegen löschen Zeilen,
|
||||
* während der Lauf welche schreibt, und das endet in einem Bestand, den
|
||||
* niemand mehr erklären kann.
|
||||
*
|
||||
* **Umbenennen, Prüfung und die Pfeile bleiben bedienbar.** Sie rühren
|
||||
* den Inhalt nicht an, und ein Knopf, der ohne Not gesperrt ist, ist
|
||||
* genauso ärgerlich wie einer, der ohne Not etwas kaputtmacht.
|
||||
*/
|
||||
function laufendSetzen(an: boolean): void {
|
||||
const grund = 'Läuft gerade — bitte warten, bis der Lauf durch ist.';
|
||||
[los, auffrischen, zuruecksetzen, entfernen].forEach(k => {
|
||||
k.disabled = an;
|
||||
if (an) k.title = grund;
|
||||
else if (k.title === grund) k.title = '';
|
||||
});
|
||||
if (!an) {
|
||||
auffrischen.title = 'Holt Titel, Bilder und Besetzung erneut — '
|
||||
+ 'nötig nach einer Sprachänderung.';
|
||||
}
|
||||
}
|
||||
|
||||
const auffrischen = knopf('Metadaten auffrischen', 'btn');
|
||||
auffrischen.title = 'Holt Titel, Bilder und Besetzung erneut — '
|
||||
+ 'nötig nach einer Sprachänderung.';
|
||||
@@ -664,7 +671,11 @@ function bibliotheksbereich(geaendert: () => void, signal: AbortSignal): HTMLEle
|
||||
+ 'nicht über eine neue Suche. Zuordnungen von Hand bleiben damit stehen. '
|
||||
+ 'Bei vielen Titeln dauert es.',
|
||||
bestaetigen: 'Auffrischen',
|
||||
tun: async () => { await metadatenAuffrischen(b.id); void verfolgen(true); },
|
||||
tun: async () => {
|
||||
laufendSetzen(true);
|
||||
await metadatenAuffrischen(b.id);
|
||||
void verfolgen(true);
|
||||
},
|
||||
});
|
||||
|
||||
const umbenennen = knopf('Umbenennen', 'btn');
|
||||
@@ -721,15 +732,35 @@ function bibliotheksbereich(geaendert: () => void, signal: AbortSignal): HTMLEle
|
||||
const s = await scanstand(b.id);
|
||||
// **Was der Server sagt, nicht was wir raten.** Vorher stand hier
|
||||
// minutenlang „Wird durchsucht…", ohne Hinweis, ob etwas passiert.
|
||||
if (s.state === 'running') stand.textContent = s.step ?? 'Wird durchsucht…';
|
||||
else if (s.state === 'enriching') stand.textContent = s.step ?? 'Metadaten werden geholt…';
|
||||
else if (s.state === 'failed') { stand.textContent = s.error ?? 'Gescheitert.'; return; }
|
||||
// **Die Sperre richtet sich nach dem Server, nicht nach dem Klick.**
|
||||
// Beim Aufbau der Seite läuft vielleicht schon einer — dann müssen die
|
||||
// Knöpfe gesperrt sein, ohne dass hier jemand etwas gedrückt hat.
|
||||
if (s.state === 'running') {
|
||||
laufendSetzen(true);
|
||||
stand.textContent = s.step ?? 'Wird durchsucht…';
|
||||
} else if (s.state === 'enriching') {
|
||||
laufendSetzen(true);
|
||||
stand.textContent = s.step ?? 'Metadaten werden geholt…';
|
||||
} else if (s.state === 'failed') {
|
||||
laufendSetzen(false);
|
||||
stand.textContent = s.error ?? 'Gescheitert.';
|
||||
return;
|
||||
}
|
||||
|
||||
// **Die Zahl mitlaufen lassen, nicht erst am Ende.** Der Scan schreibt
|
||||
// laufend; wer dabei zusieht, soll das auch sehen. Alle fünf Runden,
|
||||
// nicht jede: ein Abruf je Sekunde für eine Zahl wäre Verschwendung.
|
||||
if (melden && i > 0 && i % 5 === 0) await laden();
|
||||
else if (s.state === 'finished') {
|
||||
// **Alle Zähler auf demselben Takt.**
|
||||
//
|
||||
// Vorher frischte diese Schleife nur die Zeile in der Verwaltung auf,
|
||||
// und die Seitenleiste erst am Ende des Laufs. In der Mediathek stand
|
||||
// dann 20, in der Verwaltung 14 und in der Seitenleiste 0 — drei
|
||||
// Zahlen für dieselbe Sache, und keine davon offensichtlich falsch.
|
||||
// `geaendert()` baut nur die Seitenleiste neu, nicht die Ansicht;
|
||||
// es hier zu rufen ist deshalb gefahrlos.
|
||||
//
|
||||
// Alle drei Runden statt alle fünf: nah genug am Zähler in der
|
||||
// Mediathek, ohne je Sekunde zwei Abrufe zu kosten.
|
||||
if (melden && i > 0 && i % 3 === 0) { await laden(); geaendert(); }
|
||||
|
||||
if (s.state === 'finished') {
|
||||
const teile = [`${s.filesFound} Dateien`, `${s.added} neu`, `${s.removed} entfernt`];
|
||||
// „ohne Folgennummer" stimmte nicht mehr: gezählt wird jetzt auch,
|
||||
// was eine Nummer trägt, aber zu keiner Serie gehört.
|
||||
@@ -739,11 +770,16 @@ function bibliotheksbereich(geaendert: () => void, signal: AbortSignal): HTMLEle
|
||||
if (s.metadata?.error) teile.push(s.metadata.error);
|
||||
if (s.missing) teile.push(`${s.missing} nicht mehr gefunden`);
|
||||
stand.textContent = teile.join(' · ');
|
||||
laufendSetzen(false);
|
||||
if (melden) { await laden(); geaendert(); }
|
||||
return;
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
}
|
||||
|
||||
// Nach fünfzehn Minuten ohne Ende: die Sperre fällt, sonst bliebe die
|
||||
// Mediathek bis zum Neuladen der Seite unbedienbar.
|
||||
laufendSetzen(false);
|
||||
}
|
||||
|
||||
// Die Prüfung lässt sich je Mediathek umstellen — was beim Anlegen
|
||||
@@ -760,30 +796,14 @@ function bibliotheksbereich(geaendert: () => void, signal: AbortSignal): HTMLEle
|
||||
}
|
||||
};
|
||||
|
||||
// **Umsortieren über zwei Pfeile, nicht über Ziehen.**
|
||||
// **Umsortiert wird in der Seitenleiste, nicht hier.**
|
||||
//
|
||||
// Ziehen und Ablegen ist schöner, aber mit der Tastatur unbedienbar und
|
||||
// auf dem Telefon ein Glücksspiel. Zwei Knöpfe kann jeder bedienen, und
|
||||
// der Server bekommt die ganze Liste statt eines „eins hoch" — zwei
|
||||
// Benutzer, die gleichzeitig schieben, kämen sonst in einen Zustand, den
|
||||
// keiner von beiden wollte.
|
||||
const hoch = knopf('', 'btn rund');
|
||||
hoch.innerHTML = icons.hoch;
|
||||
hoch.setAttribute('aria-label', `„${b.name}“ nach oben`);
|
||||
hoch.disabled = platz === 0;
|
||||
hoch.onclick = () => void schieben(platz, platz - 1);
|
||||
|
||||
const runter = knopf('', 'btn rund');
|
||||
runter.innerHTML = icons.hoch;
|
||||
runter.style.transform = 'rotate(180deg)';
|
||||
runter.setAttribute('aria-label', `„${b.name}“ nach unten`);
|
||||
runter.disabled = platz === alleBibliotheken.length - 1;
|
||||
runter.onclick = () => void schieben(platz, platz + 1);
|
||||
|
||||
// Die Reihenfolge wirkt dort, also gehört der Griff dorthin: man sieht
|
||||
// beim Schieben, was man verändert, statt in den Einstellungen zu raten
|
||||
// und danach nachzusehen. Der Knopf heisst „Seitenleiste bearbeiten".
|
||||
const knoepfe = document.createElement('div');
|
||||
knoepfe.className = 'knopfreihe';
|
||||
knoepfe.append(
|
||||
hoch, runter, los, auffrischen, pruefung, umbenennen, zuruecksetzen, entfernen);
|
||||
knoepfe.append(los, auffrischen, pruefung, umbenennen, zuruecksetzen, entfernen);
|
||||
|
||||
// **Nur wenn hier wirklich etwas liegt.** Ein Knopf, der nichts zu tun
|
||||
// hat, ist schlechter als keiner — und die Zahl daneben sagt, worum es
|
||||
|
||||
Reference in New Issue
Block a user