From 98bce1b0da675715b2e5482c0ad61ba21135f62f Mon Sep 17 00:00:00 2001
From: Server-Bibliothek <server-bibliothek@lokal>
Date: Di, 04 Aug 2026 07:00:04 +0200
Subject: [PATCH] Add a desktop client that knows what is installed locally
---
packages/bibliothek-server/src/bezugsquellen/svn.ts | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 53 insertions(+), 0 deletions(-)
diff --git a/packages/bibliothek-server/src/bezugsquellen/svn.ts b/packages/bibliothek-server/src/bezugsquellen/svn.ts
new file mode 100644
index 0000000..0be2f6f
--- /dev/null
+++ b/packages/bibliothek-server/src/bezugsquellen/svn.ts
@@ -0,0 +1,53 @@
+import { execFile } from 'child_process';
+import { promisify } from 'util';
+import * as path from 'path';
+import { abgleichMisserfolg, alsIsoDatum, dateiVorhanden, mitTemporaeremOrdner, type Abgleichauftrag, type Bezugsquelle, type QuellenAbgleich } from './basis';
+
+const execFileAsync = promisify(execFile);
+
+/**
+ * SVN: exportiert eine Datei aus einem Repository, erkennt Änderungen über die Revision.
+ * Anmeldedaten kommen aus dem Windows-Anmeldespeicher des Dienstbenutzers —
+ * die Erstanmeldung muss einmal interaktiv als dieser Benutzer erfolgt sein.
+ */
+export class SvnQuelle implements Bezugsquelle {
+ async gleicheAb(auftrag: Abgleichauftrag): Promise<QuellenAbgleich> {
+ const dateiname = decodeURIComponent(auftrag.wert.split('/').pop() ?? 'datei');
+ let revision = '';
+ let veroeffentlichtAm = '';
+ try {
+ const svnInfo = (element: string) =>
+ execFileAsync('svn', ['info', '--show-item', element, '--non-interactive', auftrag.wert], { timeout: 60000, windowsHide: true });
+ revision = (await svnInfo('last-changed-revision')).stdout.trim();
+ veroeffentlichtAm = alsIsoDatum((await svnInfo('last-changed-date')).stdout.trim());
+ } catch (fehler) {
+ return abgleichMisserfolg(auftrag.aktuelleVersion, svnFehlertext(fehler));
+ }
+
+ if (revision && revision === auftrag.aktuelleVersion && dateiVorhanden(auftrag.zielordner, dateiname)) {
+ return { erfolg: true, geaendert: false, version: revision, dateiname, meldung: 'Bereits aktuell.', veroeffentlichtAm };
+ }
+
+ const ablage = mitTemporaeremOrdner(auftrag.zielordner);
+ try {
+ await execFileAsync('svn', ['export', '--force', '--non-interactive', auftrag.wert, path.join(ablage.ordner, dateiname)], {
+ timeout: 300000,
+ windowsHide: true,
+ });
+ ablage.uebernehmen();
+ return { erfolg: true, geaendert: true, version: revision || new Date().toISOString(), dateiname, meldung: `SVN-Revision ${revision} exportiert.`, veroeffentlichtAm };
+ } catch (fehler) {
+ ablage.verwerfen();
+ return abgleichMisserfolg(auftrag.aktuelleVersion, svnFehlertext(fehler));
+ }
+ }
+}
+
+/** Macht fehlende Anmeldedaten als häufigste Ursache kenntlich. */
+function svnFehlertext(fehler: unknown): string {
+ const text = fehler instanceof Error ? fehler.message : String(fehler);
+ if (/authoriz|authentic|E170001|credentials/i.test(text)) {
+ return `SVN-Anmeldung fehlt oder ist ungültig. Einmalig als Dienstbenutzer anmelden (die Anmeldung wird dann im Windows-Speicher hinterlegt). Ursprüngliche Meldung: ${text}`;
+ }
+ return `SVN fehlgeschlagen: ${text}`;
+}
--
Gitblit v1.9.3