Skip to content

Commit ab64db2

Browse files
committed
Resume live mode after stale remote drift pause
1 parent 2e2fd0e commit ab64db2

1 file changed

Lines changed: 160 additions & 10 deletions

File tree

apps/desktop/src-tauri/src/main.rs

Lines changed: 160 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1968,19 +1968,62 @@ fn live_mode_has_mounted_folder(snapshot: &DesktopSnapshot) -> bool {
19681968
}
19691969

19701970
fn live_mode_enabled_mount(state_root: &Path) -> Result<Option<MountConfig>, String> {
1971-
let store = SqliteStateStore::open(state_root.to_path_buf())
1971+
let mut store = SqliteStateStore::open(state_root.to_path_buf())
19721972
.map_err(|error| format!("Live Mode could not open Locality state: {error}"))?;
19731973
let mounts = store
19741974
.load_mounts()
19751975
.map_err(|error| format!("Live Mode could not inspect mounted folders: {error}"))?;
19761976
let Some(mount) = choose_mount(&mounts) else {
19771977
return Ok(None);
19781978
};
1979-
let enabled = store
1979+
let Some(record) = store
19801980
.get_mount_live_mode(&mount.mount_id)
19811981
.map_err(|error| format!("Live Mode could not inspect its state: {error}"))?
1982-
.is_some_and(|record| record.enabled);
1983-
Ok(enabled.then_some(mount))
1982+
else {
1983+
return Ok(None);
1984+
};
1985+
if record.enabled {
1986+
return Ok(Some(mount));
1987+
}
1988+
if live_mode_resume_stale_disabled_pause_if_safe(&mut store, state_root, &mount, &record)? {
1989+
return Ok(Some(mount));
1990+
}
1991+
Ok(None)
1992+
}
1993+
1994+
fn live_mode_resume_stale_disabled_pause_if_safe(
1995+
store: &mut SqliteStateStore,
1996+
state_root: &Path,
1997+
mount: &MountConfig,
1998+
record: &MountLiveModeRecord,
1999+
) -> Result<bool, String> {
2000+
if !live_mode_disabled_pause_can_resume(record) {
2001+
return Ok(false);
2002+
}
2003+
let pending_changes = pending_changes_for_mount(store, state_root, &mount.mount_id)?;
2004+
if pending_changes.is_empty() || pending_changes.iter().any(|change| change.state != "safe") {
2005+
return Ok(false);
2006+
}
2007+
2008+
let now = live_mode_timestamp();
2009+
let resumed = record.clone().active(
2010+
Some("Live Mode resumed after remote drift cleared.".to_string()),
2011+
now.clone(),
2012+
now,
2013+
);
2014+
store.save_mount_live_mode(resumed).map_err(|error| {
2015+
format!("Live Mode could not resume after remote drift cleared: {error}")
2016+
})?;
2017+
Ok(true)
2018+
}
2019+
2020+
fn live_mode_disabled_pause_can_resume(record: &MountLiveModeRecord) -> bool {
2021+
!record.enabled
2022+
&& record.state == MountLiveModeState::Error
2023+
&& record.last_reason.as_deref().is_some_and(|reason| {
2024+
live_mode_failure_should_pause(reason)
2025+
&& reason.contains("remote changed while local edits are pending")
2026+
})
19842027
}
19852028

19862029
#[derive(Debug, Default)]
@@ -10295,12 +10338,13 @@ mod tests {
1029510338
hydration_after_editor_write, inspect_install_state, install_terminal_cli_link_at,
1029610339
install_terminal_cli_link_in_path_dirs, is_notion_access_lost_message,
1029710340
is_unsupported_schema_version_message, live_mode_claim_remote_fast_forward_key,
10298-
live_mode_local_reconcile_targets_for_mount_at, live_mode_merge_remote_drift_markdown,
10299-
live_mode_release_remote_fast_forward_key, live_mode_remote_check_page_budget_for_rate,
10300-
live_mode_remote_pull_candidates, live_mode_remote_pull_scan_is_due_for_key,
10301-
live_mode_runner_should_tick, live_mode_should_reconcile_local_target_for_key,
10302-
live_mode_target, live_mode_tick_from_snapshot, live_mode_wake_generation,
10303-
load_desktop_activity, macos_app_bundle_for_exe, macos_file_provider_child_item_count,
10341+
live_mode_enabled_mount, live_mode_local_reconcile_targets_for_mount_at,
10342+
live_mode_merge_remote_drift_markdown, live_mode_release_remote_fast_forward_key,
10343+
live_mode_remote_check_page_budget_for_rate, live_mode_remote_pull_candidates,
10344+
live_mode_remote_pull_scan_is_due_for_key, live_mode_runner_should_tick,
10345+
live_mode_should_reconcile_local_target_for_key, live_mode_target,
10346+
live_mode_tick_from_snapshot, live_mode_wake_generation, load_desktop_activity,
10347+
macos_app_bundle_for_exe, macos_file_provider_child_item_count,
1030410348
macos_file_provider_mount_root_health_error,
1030510349
macos_file_provider_mount_root_recovery_reason, mark_mount_live_mode_syncing,
1030610350
mount_has_pending_local_changes, mount_has_unfinished_journals, notion_id_from_url,
@@ -11371,6 +11415,112 @@ mod tests {
1137111415
assert_eq!(summary.reason, None);
1137211416
}
1137311417

11418+
#[test]
11419+
fn live_mode_disabled_remote_drift_pause_can_resume() {
11420+
let record = MountLiveModeRecord::new(MountId::new("notion-main"), true, "1").error(
11421+
"Live Mode paused for `Roadmap`: remote changed while local edits are pending.",
11422+
"2",
11423+
"2",
11424+
);
11425+
11426+
assert!(super::live_mode_disabled_pause_can_resume(&record));
11427+
}
11428+
11429+
#[test]
11430+
fn live_mode_disabled_non_remote_drift_pause_stays_paused() {
11431+
let record = MountLiveModeRecord::new(MountId::new("notion-main"), true, "1").error(
11432+
"Live Mode paused for `Roadmap`: conflict.",
11433+
"2",
11434+
"2",
11435+
);
11436+
11437+
assert!(!super::live_mode_disabled_pause_can_resume(&record));
11438+
}
11439+
11440+
#[test]
11441+
fn live_mode_enabled_mount_resumes_stale_remote_drift_pause_for_safe_pending_change() {
11442+
let temp = TestTempDir::new("live-mode-resume-stale-remote-drift");
11443+
let state_root = temp.path();
11444+
let mount_root = state_root.join("notion");
11445+
let mut store = SqliteStateStore::open(state_root.to_path_buf()).expect("open store");
11446+
let mount_id = MountId::new("notion-main");
11447+
let remote_id = RemoteId::new("page-1");
11448+
let relative_path = Path::new("Roadmap/page.md");
11449+
let body = "# Roadmap\n\nOriginal body.\n";
11450+
let mount = MountConfig::new(mount_id.clone(), "notion", &mount_root)
11451+
.projection(ProjectionMode::MacosFileProvider);
11452+
store.save_mount(mount.clone()).expect("save mount");
11453+
store
11454+
.save_mount_live_mode(MountLiveModeRecord::new(mount_id.clone(), true, "1").error(
11455+
"Live Mode paused for `Roadmap`: remote changed while local edits are pending.",
11456+
"2",
11457+
"2",
11458+
))
11459+
.expect("save paused live mode");
11460+
store
11461+
.save_entity(
11462+
EntityRecord::new(
11463+
mount_id.clone(),
11464+
remote_id.clone(),
11465+
EntityKind::Page,
11466+
"Roadmap",
11467+
relative_path,
11468+
)
11469+
.with_hydration(HydrationState::Dirty)
11470+
.with_remote_edited_at("remote-v1"),
11471+
)
11472+
.expect("save entity");
11473+
store
11474+
.save_shadow(
11475+
&mount_id,
11476+
ShadowDocument::from_synced_body(
11477+
remote_id.clone(),
11478+
body,
11479+
7,
11480+
[RemoteId::new("heading-1"), RemoteId::new("paragraph-1")],
11481+
)
11482+
.expect("shadow"),
11483+
)
11484+
.expect("save shadow");
11485+
store
11486+
.save_remote_observation(
11487+
RemoteObservationRecord::new(
11488+
mount_id.clone(),
11489+
remote_id.clone(),
11490+
EntityKind::Page,
11491+
"Roadmap",
11492+
relative_path,
11493+
"unix_ms:10",
11494+
)
11495+
.with_remote_version(RemoteVersion::new("remote-v1")),
11496+
)
11497+
.expect("save observation");
11498+
store
11499+
.save_freshness_state(
11500+
FreshnessStateRecord::new(mount_id.clone(), remote_id.clone(), FreshnessTier::Hot)
11501+
.checked_at("unix_ms:10"),
11502+
)
11503+
.expect("save freshness");
11504+
11505+
let content_path =
11506+
localityd::virtual_fs::virtual_fs_content_path(state_root, &mount_id, relative_path)
11507+
.expect("content path");
11508+
fs::create_dir_all(content_path.parent().expect("content parent"))
11509+
.expect("create content parent");
11510+
fs::write(&content_path, "# Roadmap\n\nLocal edit.\n").expect("write dirty content");
11511+
11512+
let selected = live_mode_enabled_mount(state_root)
11513+
.expect("enabled mount")
11514+
.expect("resumed mount");
11515+
assert_eq!(selected.mount_id, mount_id);
11516+
let record = store
11517+
.get_mount_live_mode(&mount_id)
11518+
.expect("load live mode")
11519+
.expect("live mode record");
11520+
assert!(record.enabled);
11521+
assert_eq!(record.state, MountLiveModeState::Active);
11522+
}
11523+
1137411524
#[test]
1137511525
fn live_mode_transient_failures_remain_enabled_for_retry() {
1137611526
let temp = TestTempDir::new("live-mode-transient-failure");

0 commit comments

Comments
 (0)