update
This commit is contained in:
@@ -28,6 +28,8 @@ const SOON_MS = 500;
|
|||||||
// "files" in sync.json: false schaltet ab, ein Pfad ersetzt ~/.claude/projects.
|
// "files" in sync.json: false schaltet ab, ein Pfad ersetzt ~/.claude/projects.
|
||||||
const FILES_FIRST_SECONDS = 10;
|
const FILES_FIRST_SECONDS = 10;
|
||||||
const FILES_BATCH_BYTES = 16 * 1024 * 1024;
|
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
|
// Von außen anstoßbar: make sync-files ruft diese Methode über D-Bus auf
|
||||||
const DBUS_IFACE = `
|
const DBUS_IFACE = `
|
||||||
@@ -201,18 +203,7 @@ export class Sync {
|
|||||||
const message = Soup.Message.new('POST', `${this._config.url}/api/sync`);
|
const message = Soup.Message.new('POST', `${this._config.url}/api/sync`);
|
||||||
message.request_headers.append('Authorization', `Bearer ${this._config.token}`);
|
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))));
|
message.set_request_body_from_bytes('application/json', new GLib.Bytes(new TextEncoder().encode(JSON.stringify(payload))));
|
||||||
return new Promise((resolve, reject) => {
|
return request(session, message, bytes => JSON.parse(new TextDecoder().decode(bytes.get_data())));
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Antwort einarbeiten. Was der Server zurückschickt und wir gerade hochgeladen haben, kennt er jetzt.
|
// 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 path = id.split('/').map(encodeURIComponent).join('/');
|
||||||
const message = Soup.Message.new('GET', `${this._config.url}/api/files/${path}?from=${from}&hash=${hash}`);
|
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}`);
|
message.request_headers.append('Authorization', `Bearer ${this._config.token}`);
|
||||||
return new Promise((resolve, reject) => {
|
return request(this._filesSession, message, bytes => ({
|
||||||
this._filesSession.send_and_read_async(message, GLib.PRIORITY_DEFAULT, null, (session, result) => {
|
bytes: bytes.get_data() ?? new Uint8Array(0),
|
||||||
try {
|
from: Number(message.get_response_headers().get_one('X-From')),
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_fail(message) {
|
_fail(message) {
|
||||||
@@ -423,6 +406,33 @@ function scanFiles(root) {
|
|||||||
return local;
|
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.
|
// Datei als Bytes lesen. null, wenn sie fehlt.
|
||||||
function readBytes(path) {
|
function readBytes(path) {
|
||||||
try {
|
try {
|
||||||
@@ -434,6 +444,8 @@ function readBytes(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function appendBytes(path, bytes) {
|
function appendBytes(path, bytes) {
|
||||||
|
if (!bytes.length)
|
||||||
|
return;
|
||||||
const stream = Gio.File.new_for_path(path).append_to(Gio.FileCreateFlags.NONE, null);
|
const stream = Gio.File.new_for_path(path).append_to(Gio.FileCreateFlags.NONE, null);
|
||||||
stream.write_bytes(new GLib.Bytes(bytes), null);
|
stream.write_bytes(new GLib.Bytes(bytes), null);
|
||||||
stream.close(null);
|
stream.close(null);
|
||||||
|
|||||||
Reference in New Issue
Block a user