desktop: syncs on its own — at launch, soon after an edit, every few minutes and on focus
Android / Build, or is the channel already serving this? (push) Successful in 5s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 13s
CI & Build / Python tests (push) Successful in 14s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / integration (push) Successful in 47s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 3m8s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m53s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m47s
Desktop (Tauri) / Update manifest (push) Successful in 6s
Android / Kotlin + Rust (APK) (push) Successful in 11m34s
Android / Build, or is the channel already serving this? (push) Successful in 5s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 13s
CI & Build / Python tests (push) Successful in 14s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / integration (push) Successful in 47s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 3m8s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m53s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m47s
Desktop (Tauri) / Update manifest (push) Successful in 6s
Android / Kotlin + Rust (APK) (push) Successful in 11m34s
Until now the only caller of the sync engine was the "Sync now" button. A worker thread now owns every cycle (the button's included, so two never overlap): - launch: one cycle as the app opens; - edit: every 10s it reads a fingerprint of the pending set and sends when that moved. A fingerprint rather than "anything pending", because a rejected change stays pending and would otherwise be resent every tick forever; - timer: a pull every 5 minutes with nothing to send; - focus: at most once per 30s. Failed automatic cycles back off (doubling from 10s to 5 minutes). A panicking cycle counts as a failed one rather than ending the thread. Every cycle is emitted as inkwell://synced: the board reloads when the pull changed something, and the Sync screen shows the last automatic failure. No final push on quit; the launch cycle sends whatever was left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,16 +4,17 @@
|
||||
//! any of this. The Settings UI (M10.7e) drives these.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::State;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
use inkwell_core::local::Db;
|
||||
use inkwell_core::sync::blobs::BlobStore;
|
||||
use inkwell_core::sync::client::{self, Identity, ProbeResult};
|
||||
use inkwell_core::sync::compat::Compatibility;
|
||||
use inkwell_core::sync::engine;
|
||||
use inkwell_core::sync::push;
|
||||
use inkwell_core::sync::state;
|
||||
|
||||
use crate::autosync::{self, AutoSync, LastCycle};
|
||||
|
||||
/// Ask a server who it is, without committing to anything. The UI calls this as the
|
||||
/// user finishes typing an address, so they see what answered before handing over
|
||||
/// credentials.
|
||||
@@ -156,29 +157,24 @@ pub fn sync_status(db: State<'_, Db>) -> Result<state::Status, String> {
|
||||
state::status(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// The server URL + token, or a plain "not linked" error. Every networked sync
|
||||
/// command needs exactly this, and none of them may hold the lock past it.
|
||||
fn credentials(db: &State<'_, Db>) -> Result<(String, String), String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let current = state::read(&conn).map_err(|e| e.to_string())?;
|
||||
match (current.server_url, current.device_token) {
|
||||
(Some(url), Some(token)) => Ok((url, token)),
|
||||
_ => Err("This app isn't linked to a server yet.".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Run one full sync: push local changes, then pull the server's.
|
||||
///
|
||||
/// The only sync entry point exposed to the UI, on purpose. Push and pull exist
|
||||
/// separately inside the crate, but offering a bare "pull" would let the UI overwrite
|
||||
/// unsent local edits — the ordering isn't a suggestion, it's what keeps them.
|
||||
///
|
||||
/// Runs on the background worker (autosync.rs) and waits for it, so a button press
|
||||
/// can never overlap an automatic cycle.
|
||||
#[tauri::command]
|
||||
pub async fn sync_now(
|
||||
db: State<'_, Db>,
|
||||
blobs: State<'_, BlobStore>,
|
||||
) -> Result<engine::SyncOutcome, String> {
|
||||
let (base_url, token) = credentials(&db)?;
|
||||
engine::run_cycle(db.inner(), blobs.inner(), &base_url, &token).await
|
||||
pub async fn sync_now(app: AppHandle) -> Result<engine::SyncOutcome, String> {
|
||||
autosync::run_now(&app).await
|
||||
}
|
||||
|
||||
/// The most recent cycle, automatic or not — what the Sync screen shows now that
|
||||
/// most cycles happen without anyone pressing anything. None until the first one.
|
||||
#[tauri::command]
|
||||
pub fn sync_last(autosync: State<'_, AutoSync>) -> Option<LastCycle> {
|
||||
autosync.last()
|
||||
}
|
||||
|
||||
/// Whether anything is waiting to be sent. Lets the UI show an honest "unsynced
|
||||
|
||||
Reference in New Issue
Block a user