From 59f39729fca32683f4129aafb394dfe51f22d1d8 Mon Sep 17 00:00:00 2001 From: Team3 Date: Wed, 7 Oct 2026 01:08:18 +0200 Subject: [PATCH] update --- desktop@marha.de/sync.js | 60 ++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/desktop@marha.de/sync.js b/desktop@marha.de/sync.js index e9c47d6..9c95792 100644 --- a/desktop@marha.de/sync.js +++ b/desktop@marha.de/sync.js @@ -28,6 +28,8 @@ const SOON_MS = 500; // "files" in sync.json: false schaltet ab, ein Pfad ersetzt ~/.claude/projects. const FILES_FIRST_SECONDS = 10; const FILES_BATCH_BYTES = 16 * 1024 * 1024; +// So lange über das Soup-Timeout hinaus warten wir auf eine Antwort, siehe request() +const REQUEST_GRACE_SECONDS = 10; // Von außen anstoßbar: make sync-files ruft diese Methode über D-Bus auf const DBUS_IFACE = ` @@ -201,18 +203,7 @@ export class Sync { const message = Soup.Message.new('POST', `${this._config.url}/api/sync`); message.request_headers.append('Authorization', `Bearer ${this._config.token}`); message.set_request_body_from_bytes('application/json', new GLib.Bytes(new TextEncoder().encode(JSON.stringify(payload)))); - return new Promise((resolve, reject) => { - session.send_and_read_async(message, GLib.PRIORITY_DEFAULT, null, (session, result) => { - try { - const bytes = session.send_and_read_finish(result); - if (message.get_status() !== Soup.Status.OK) - throw new Error(`HTTP ${message.get_status()} ${message.get_reason_phrase()}`); - resolve(JSON.parse(new TextDecoder().decode(bytes.get_data()))); - } catch (e) { - reject(e); - } - }); - }); + return request(session, message, bytes => JSON.parse(new TextDecoder().decode(bytes.get_data()))); } // Antwort einarbeiten. Was der Server zurückschickt und wir gerade hochgeladen haben, kennt er jetzt. @@ -375,18 +366,10 @@ export class Sync { const path = id.split('/').map(encodeURIComponent).join('/'); const message = Soup.Message.new('GET', `${this._config.url}/api/files/${path}?from=${from}&hash=${hash}`); message.request_headers.append('Authorization', `Bearer ${this._config.token}`); - return new Promise((resolve, reject) => { - this._filesSession.send_and_read_async(message, GLib.PRIORITY_DEFAULT, null, (session, result) => { - try { - const bytes = session.send_and_read_finish(result); - if (message.get_status() !== Soup.Status.OK) - throw new Error(`HTTP ${message.get_status()} ${message.get_reason_phrase()}`); - resolve({bytes: bytes.get_data() ?? new Uint8Array(0), from: Number(message.get_response_headers().get_one('X-From'))}); - } catch (e) { - reject(e); - } - }); - }); + return request(this._filesSession, message, bytes => ({ + bytes: bytes.get_data() ?? new Uint8Array(0), + from: Number(message.get_response_headers().get_one('X-From')), + })); } _fail(message) { @@ -423,6 +406,33 @@ function scanFiles(root) { return local; } +// Anfrage schicken und die Antwort lesen. GJS verwirft den Soup-Callback gelegentlich, wenn er +// während der Garbage Collection fällig wird. Dann käme nie eine Antwort, der Lauf bliebe für immer +// "beschäftigt" und der Takt stünde still. Deshalb eine eigene Frist über dem Soup-Timeout: +// danach gilt die Anfrage als gescheitert, der nächste Lauf kommt wie geplant. +function request(session, message, read) { + return new Promise((resolve, reject) => { + let watchdog = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, session.timeout + REQUEST_GRACE_SECONDS, () => { + watchdog = null; + reject(new Error('keine Antwort, Anfrage verworfen')); + return GLib.SOURCE_REMOVE; + }); + session.send_and_read_async(message, GLib.PRIORITY_DEFAULT, null, (_session, result) => { + if (watchdog) + GLib.source_remove(watchdog); + watchdog = null; + try { + const bytes = session.send_and_read_finish(result); + if (message.get_status() !== Soup.Status.OK) + throw new Error(`HTTP ${message.get_status()} ${message.get_reason_phrase()}`); + resolve(read(bytes)); + } catch (e) { + reject(e); + } + }); + }); +} + // Datei als Bytes lesen. null, wenn sie fehlt. function readBytes(path) { try { @@ -434,6 +444,8 @@ function readBytes(path) { } function appendBytes(path, bytes) { + if (!bytes.length) + return; const stream = Gio.File.new_for_path(path).append_to(Gio.FileCreateFlags.NONE, null); stream.write_bytes(new GLib.Bytes(bytes), null); stream.close(null);