update
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
// Dateiabgleich für ~/.claude/projects (Claude-Code-Sitzungen und Memory), nur zwischen PCs.
|
||||
// Reine Logik ohne Dateisystem, damit sie sich testen lässt. sync.js liest und schreibt die Dateien.
|
||||
//
|
||||
// Regeln: Eine Datei geht hoch, wenn sie sich geändert hat und seit einer Stunde nicht angefasst wurde.
|
||||
// Vom Server kommt sie, wenn sie dort neuer ist. Bei Konflikten gewinnt die neuere Änderungszeit.
|
||||
// Verschwundene Dateien werden als Tombstone gemeldet, gelöschte vom Server lokal entfernt.
|
||||
// Regeln: Eine Datei geht hoch, wenn sie sich geändert hat und seit einer Minute nicht angefasst wurde.
|
||||
// Sitzungen wachsen nur am Ende, deshalb geht meist nur der neue Rest ("append"), zusammen mit dem
|
||||
// Stand, den der Server haben müsste (base: Größe und Hash der letzten 64 KB). Vom Server kommt eine
|
||||
// Datei, wenn sie dort neuer ist, ebenfalls nur ab dem eigenen Stand. Bei Konflikten gewinnt die neuere
|
||||
// Änderungszeit. Verschwundene Dateien werden als Tombstone gemeldet, gelöschte vom Server lokal entfernt.
|
||||
import GLib from 'gi://GLib';
|
||||
|
||||
export const TAIL = 64 * 1024;
|
||||
|
||||
// Zeitstempel aus der Änderungszeit der Datei, gleiche Form wie updated bei Terminen
|
||||
export function fileStamp(mtimeSeconds) {
|
||||
@@ -14,7 +19,19 @@ export function stampToSeconds(stamp) {
|
||||
return Math.floor(Date.parse(stamp) / 1000);
|
||||
}
|
||||
|
||||
// Was hochgeladen wird. local: Map id → mtime in Sekunden. pushed: id → updated, null = als gelöscht gemeldet.
|
||||
// Hash der letzten 64 KB, gleiche Rechnung wie auf dem Server
|
||||
export function tailHash(bytes) {
|
||||
const tail = bytes.length > TAIL ? bytes.subarray(bytes.length - TAIL) : bytes;
|
||||
return GLib.compute_checksum_for_bytes(GLib.ChecksumType.SHA256, new GLib.Bytes(tail));
|
||||
}
|
||||
|
||||
// pushed[id] ist der Stand auf dem Server: {updated, size, hash}. Ältere Stände sind nur der
|
||||
// Zeitstempel als String, null heißt als gelöscht gemeldet.
|
||||
export function knownUpdated(known) {
|
||||
return known && typeof known === 'object' ? known.updated : known;
|
||||
}
|
||||
|
||||
// Was hochgeladen wird. local: Map id → mtime in Sekunden.
|
||||
// upload: Einträge ohne Inhalt, remove: IDs, die es lokal nicht mehr gibt.
|
||||
export function planPush(local, pushed, nowSeconds, idleSeconds) {
|
||||
const upload = [];
|
||||
@@ -22,13 +39,31 @@ export function planPush(local, pushed, nowSeconds, idleSeconds) {
|
||||
if (nowSeconds - mtime < idleSeconds)
|
||||
continue;
|
||||
const updated = fileStamp(mtime);
|
||||
if (pushed[id] !== updated)
|
||||
if (knownUpdated(pushed[id]) !== updated)
|
||||
upload.push({id, updated});
|
||||
}
|
||||
const remove = Object.keys(pushed).filter(id => pushed[id] !== null && !local.has(id));
|
||||
return {upload, remove};
|
||||
}
|
||||
|
||||
// Eintrag für den Server aus den Bytes der Datei: nur der Rest, wenn der bekannte Anfang noch stimmt,
|
||||
// sonst die ganze Datei. null, wenn sie kein UTF-8 ist.
|
||||
export function uploadItem(id, updated, bytes, known) {
|
||||
const decoder = new TextDecoder('utf-8', {fatal: true});
|
||||
if (known && typeof known === 'object' && known.size < bytes.length && tailHash(bytes.subarray(0, known.size)) === known.hash) {
|
||||
try {
|
||||
return {id, updated, base: {size: known.size, hash: known.hash}, append: decoder.decode(bytes.subarray(known.size))};
|
||||
} catch {
|
||||
// Schnitt mitten im Zeichen: dann eben ganz
|
||||
}
|
||||
}
|
||||
try {
|
||||
return {id, updated, content: decoder.decode(bytes)};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Was mit Einträgen vom Server passiert: write, delete oder skip.
|
||||
// mine: der lokale Zeitstempel oder null, damit sync.js den Stand nachhalten kann.
|
||||
export function planPull(remote, local) {
|
||||
@@ -63,3 +98,10 @@ export function batches(items, sizeOf, maxBytes) {
|
||||
out.push(current);
|
||||
return out;
|
||||
}
|
||||
|
||||
export function concat(a, b) {
|
||||
const out = new Uint8Array(a.length + b.length);
|
||||
out.set(a, 0);
|
||||
out.set(b, a.length);
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user