From f3ef3861a99b3653d126ea7baeaf96d5a41e9f3b Mon Sep 17 00:00:00 2001 From: team3 Date: Mon, 5 Oct 2026 22:09:06 +0200 Subject: [PATCH] update --- Makefile | 2 +- README.md | 6 +++--- desktop@marha.de/files.js | 3 +-- desktop@marha.de/sync.js | 17 +++++++---------- dev/tests/sync.sh | 13 ++++++------- 5 files changed, 18 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index 6c54dcd..52d1691 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ reload: echo "Extension neu gestartet." fi -# Dateiabgleich (Claude-Sitzungen und Memory) sofort und ohne Ruhezeit, wartet bis er durch ist +# Dateiabgleich (Claude-Sitzungen und Memory) sofort anstoßen, wartet bis er durch ist sync-files: @gdbus call --session --dest org.gnome.Shell --object-path /de/marha/desktop --method de.marha.Desktop.SyncFiles --timeout 3600 \ | sed -e "s/^('//" -e "s/',)$$//" diff --git a/README.md b/README.md index e1bab27..39150d4 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ Wiederverbinden wird nachgeliefert. Eine gerade offene Notiz wird nicht von auß ### Claude-Code-Sitzungen (nur PCs) Die Extension gleicht auch `~/.claude/projects/` ab, also Sitzungen und Memory von Claude Code, -im selben Takt wie die Einträge. Hoch geht eine Datei, 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: +im selben Takt wie die Einträge. Jede Änderung geht beim nächsten Lauf hoch, also nach 1 bis 10 +Sekunden. Sitzungen wachsen nur am Ende, deshalb geht meist nur der neue Rest: der Client nennt Größe und Hash der letzten 64 KB des Standes, den der Server haben müsste, und der Server hängt an. Passt das nicht, geht die ganze Datei. Vom Server kommt eine Datei, wenn sie dort neuer ist, ebenfalls nur ab dem eigenen Stand, mit ihrer Änderungszeit. Gelöschte Dateien @@ -74,7 +74,7 @@ Auf dem Server liegen die Inhalte unter `data/files/`, in SQLite nur Pfad, Zeitp Eine Sitzung darf nur auf einem PC gleichzeitig laufen, sonst gewinnt die neuere Datei als Ganzes. Bedingung: gleicher Pfad auf beiden PCs, der Ordnername enthält ihn. `"files": false` in `sync.json` schaltet es ab, ein Pfad statt `false` ersetzt den Ordner. -`make sync-files` stößt den Abgleich sofort an, auch für Dateien, die noch keine Minute ruhen, und wartet, bis er durch ist. +`make sync-files` stößt den Abgleich sofort an und wartet, bis er durch ist. Zugang auf jedem Gerät in `~/.local/share/desktop-app/sync.json`: diff --git a/desktop@marha.de/files.js b/desktop@marha.de/files.js index e677026..b870f6d 100644 --- a/desktop@marha.de/files.js +++ b/desktop@marha.de/files.js @@ -1,8 +1,7 @@ // 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 Minute nicht angefasst wurde. -// Sitzungen wachsen nur am Ende, deshalb geht meist nur der neue Rest ("append"), zusammen mit dem +// Regeln: Eine Datei geht hoch, sobald sie sich geändert hat. 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. diff --git a/desktop@marha.de/sync.js b/desktop@marha.de/sync.js index 256aa2a..e9c47d6 100644 --- a/desktop@marha.de/sync.js +++ b/desktop@marha.de/sync.js @@ -23,11 +23,10 @@ const INTERVALS = [ // Kurze Wartezeit nach einer eigenen Änderung, damit mehrere Klicks in einem Lauf landen const SOON_MS = 500; -// Dateien (Claude-Sitzungen und Memory): gleicher Takt wie die Einträge, hoch geht, was eine Minute ruht. -// Meist nur der neue Rest einer Datei, siehe files.js. +// Dateien (Claude-Sitzungen und Memory): gleicher Takt wie die Einträge, jede Änderung geht beim +// nächsten Lauf hoch, meist nur der neue Rest einer Datei, siehe files.js. // "files" in sync.json: false schaltet ab, ein Pfad ersetzt ~/.claude/projects. const FILES_FIRST_SECONDS = 10; -const FILES_IDLE_SECONDS = 60; const FILES_BATCH_BYTES = 16 * 1024 * 1024; // Von außen anstoßbar: make sync-files ruft diese Methode über D-Bus auf @@ -84,15 +83,14 @@ export class Sync { this._dbus.export(Gio.DBus.session, '/de/marha/desktop'); } - // Dateiabgleich sofort und erzwungen: auch Dateien, die noch keine Stunde ruhen. - // Die Antwort kommt erst, wenn er durch ist. + // Dateiabgleich sofort, die Antwort kommt erst, wenn er durch ist. SyncFilesAsync(_params, invocation) { const reply = text => invocation.return_value(new GLib.Variant('(s)', [text])); if (!this._filesRoot) { reply('Dateiabgleich ist aus ("files": false in sync.json)'); return; } - this._filesCycle(true) + this._filesCycle() .then(r => reply(r ? `hoch ${r.uploaded}, gelöscht gemeldet ${r.removed}, runter ${r.written}, lokal gelöscht ${r.deleted}` : 'läuft gerade schon')) .catch(e => reply(`Fehler: ${e.message}`)); } @@ -284,8 +282,7 @@ export class Sync { // Eigener Lauf mit eigenem Stand: die Antwort enthält nur Dateien, der Takt der Einträge bleibt unberührt. // Gibt zurück, was übertragen wurde, oder null, wenn gerade ein Lauf aktiv ist. - // force: Ruhezeit ignorieren, alles Geänderte geht hoch. - async _filesCycle(force = false) { + async _filesCycle() { if (this._filesBusy || !this._filesRoot) return null; this._filesBusy = true; @@ -294,7 +291,7 @@ export class Sync { const state = this._state.files; const local = scanFiles(this._filesRoot); const now = Math.floor(GLib.get_real_time() / 1e6); - const {upload, remove} = planPush(local, state.pushed, now, force ? 0 : FILES_IDLE_SECONDS); + const {upload, remove} = planPush(local, state.pushed, now, 0); const items = []; for (const {id, updated} of upload) { const bytes = readBytes(this._path(id)); @@ -368,7 +365,7 @@ export class Sync { if (bytes) state.pushed[file.id] = {updated: mine, size: bytes.length, hash: tailHash(bytes)}; } - // Sonst ist die lokale Datei neuer und geht hoch, sobald sie eine Minute ruht. + // Sonst ist die lokale Datei neuer und geht beim nächsten Lauf hoch. } state.cursor = response.cursor; } diff --git a/dev/tests/sync.sh b/dev/tests/sync.sh index 66ff9e7..fb6d69f 100755 --- a/dev/tests/sync.sh +++ b/dev/tests/sync.sh @@ -158,25 +158,24 @@ FILES=/tmp/desktop-app-nested/claude-projects SFILES=$SERVER_DATA/files files() { api -X POST "$URL/api/sync" -d '{"since": 0, "files": []}' | python3 -c 'import json,sys; print(sorted((f["id"], bool(f.get("deleted"))) for f in json.load(sys.stdin)["files"]))'; } same() { cmp -s "$SFILES/$1" "$FILES/$1" && echo gleich || echo verschieden; } -# Lauf wie vom Timer: Ruhezeit gilt +# Lauf wie vom Timer timercycle() { "$E" "$PRE ext._sync._filesCycle(); 'ok'" >/dev/null; sleep 3; } -# Wie make sync-files, nur auf dem Bus der Test-Shell: erzwungen, wartet, bis der Lauf durch ist. +# Wie make sync-files, nur auf dem Bus der Test-Shell: wartet, bis der Lauf durch ist. filescycle() { DBUS_SESSION_BUS_ADDRESS=$(cat /tmp/desktop-app-nested/bus) gdbus call --session --dest org.gnome.Shell --object-path /de/marha/desktop --method de.marha.Desktop.SyncFiles | sed -e "s/^('//" -e "s/',)$//"; } mkdir -p "$FILES/-home-x/memory" printf '{"a":1}\n' > "$FILES/-home-x/alt.jsonl"; touch -d '2 hours ago' "$FILES/-home-x/alt.jsonl" printf '# Memo\n' > "$FILES/-home-x/memory/MEMORY.md"; touch -d '90 minutes ago' "$FILES/-home-x/memory/MEMORY.md" printf 'läuft noch\n' > "$FILES/-home-x/frisch.jsonl" timercycle -check "Timer-Lauf: ruhende Dateien auf dem Server, frische nicht" "$(files)" "[('-home-x/alt.jsonl', False), ('-home-x/memory/MEMORY.md', False)]" -check "Inhalt liegt als Datei auf dem Server" "$(same -home-x/alt.jsonl) $(same -home-x/memory/MEMORY.md)" "gleich gleich" +check "Timer-Lauf: alle Dateien sofort auf dem Server, auch die frische" "$(files)" "[('-home-x/alt.jsonl', False), ('-home-x/frisch.jsonl', False), ('-home-x/memory/MEMORY.md', False)]" +check "Inhalt liegt als Datei auf dem Server" "$(same -home-x/alt.jsonl) $(same -home-x/memory/MEMORY.md) $(same -home-x/frisch.jsonl)" "gleich gleich gleich" check "Takt nach Übertragung wie beim Kalender" "$(val 'ext._sync.interval')" "1" -check "make sync-files erzwingt auch die frische Datei" "$(filescycle)" "hoch 1, gelöscht gemeldet 0, runter 0, lokal gelöscht 0" -check "Frische Datei jetzt auf dem Server" "$(files)" "[('-home-x/alt.jsonl', False), ('-home-x/frisch.jsonl', False), ('-home-x/memory/MEMORY.md', False)]" +check "make sync-files hat danach nichts mehr zu tun" "$(filescycle)" "hoch 0, gelöscht gemeldet 0, runter 0, lokal gelöscht 0" check "Normale Anfrage bekommt keine Dateien" "$(api -X POST "$URL/api/sync" -d '{"since": 0}' | python3 -c 'import json,sys; d=json.load(sys.stdin); print("files" in d, "events" in d)')" "False True" check "Stand der Dateien mit Größe gemerkt" "$(val 'Object.keys(state().files.pushed).sort().join(",") + " " + (state().files.cursor > 0) + " " + state().files.pushed["-home-x/alt.jsonl"].size')" "-home-x/alt.jsonl,-home-x/frisch.jsonl,-home-x/memory/MEMORY.md true 8" printf '{"a":2}\n' >> "$FILES/-home-x/alt.jsonl"; touch -d '2 minutes ago' "$FILES/-home-x/alt.jsonl" timercycle -check "Angehängte Zeile nach einer Minute Ruhe auf dem Server" "$(same -home-x/alt.jsonl) $(val 'state().files.pushed["-home-x/alt.jsonl"].size')" "gleich 16" +check "Angehängte Zeile auf dem Server" "$(same -home-x/alt.jsonl) $(val 'state().files.pushed["-home-x/alt.jsonl"].size')" "gleich 16" printf 'neu\n' > "$FILES/-home-x/alt.jsonl"; touch -d '2 minutes ago' "$FILES/-home-x/alt.jsonl" timercycle check "Umgeschriebene Datei ganz ersetzt" "$(same -home-x/alt.jsonl) $(cat "$SFILES/-home-x/alt.jsonl")" "gleich neu"