C14.15 r1: failed recovery and session writes show in the window, once each, with the last kept time
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e1b889cb7f
commit
906d64cf52
6 files changed
+155
-12
No files matched your search
@@ -51,6 +51,9 @@ pub struct Extra {
|
||||
/// The last recovery point's mark, and one in flight.
|
||||
pub pointed: Option<String>,
|
||||
pub pointing: bool,
|
||||
/// When a point last landed, and the failure last shown.
|
||||
pub pointed_at: Option<std::time::SystemTime>,
|
||||
pub point_failed: Option<String>,
|
||||
/// A kept tab's mode, place and text came back once.
|
||||
pub restored: bool,
|
||||
/// Disk reads started; only the newest lands.
|
||||
@@ -79,6 +82,8 @@ impl Default for Extra {
|
||||
slot: String::new(),
|
||||
pointed: None,
|
||||
pointing: false,
|
||||
pointed_at: None,
|
||||
point_failed: None,
|
||||
restored: false,
|
||||
reads: 0,
|
||||
looked: false,
|
||||
|
||||
@@ -46,6 +46,8 @@ pub struct Keeping {
|
||||
crash: Option<Crash>,
|
||||
waiting: Vec<Waiting>,
|
||||
written: Option<String>,
|
||||
/// The session write failure last shown.
|
||||
unwritten: Option<String>,
|
||||
/// A quit began: the kept list stays as then.
|
||||
pub leaving: bool,
|
||||
/// The list as kept when the quit began.
|
||||
@@ -129,6 +131,7 @@ impl Keeping {
|
||||
crash: Some(crash),
|
||||
waiting,
|
||||
written: None,
|
||||
unwritten: None,
|
||||
leaving: false,
|
||||
frozen: Vec::new(),
|
||||
};
|
||||
@@ -284,7 +287,7 @@ impl Desk {
|
||||
recovery.write_once(current.as_bytes(), None, now)
|
||||
})
|
||||
.await;
|
||||
desk.update(cx, |desk, _| desk.pointed(id, point, wrote, &slot))
|
||||
desk.update(cx, |desk, cx| desk.pointed(id, point, wrote, &slot, cx))
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
@@ -304,11 +307,18 @@ impl Desk {
|
||||
}
|
||||
if !self.keeping.leaving {
|
||||
self.keeping.frozen = kept.clone();
|
||||
self.write_session(Saved { tabs: kept });
|
||||
self.write_session(Saved { tabs: kept }, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn pointed(&mut self, id: u64, point: String, wrote: io::Result<Option<PathBuf>>, slot: &Path) {
|
||||
fn pointed(
|
||||
&mut self,
|
||||
id: u64,
|
||||
point: String,
|
||||
wrote: io::Result<Option<PathBuf>>,
|
||||
slot: &Path,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
// The tab closed while it wrote: the point goes too.
|
||||
let Some(tab) = self.tab_mut(id) else {
|
||||
if let Err(error) = clear(slot) {
|
||||
@@ -317,21 +327,50 @@ impl Desk {
|
||||
return;
|
||||
};
|
||||
tab.extra.pointing = false;
|
||||
match wrote {
|
||||
Ok(_) => tab.extra.pointed = Some(point),
|
||||
Err(error) => log::error!("mark: a recovery point for tab {id} failed: {error}"),
|
||||
let error = match wrote {
|
||||
Ok(_) => {
|
||||
tab.extra.pointed = Some(point);
|
||||
tab.extra.pointed_at = Some(SystemTime::now());
|
||||
tab.extra.point_failed = None;
|
||||
return;
|
||||
}
|
||||
Err(error) => error.to_string(),
|
||||
};
|
||||
log::error!("mark: a recovery point for tab {id} failed: {error}");
|
||||
// Each failure shows once; the next tick retries.
|
||||
if tab.extra.point_failed.as_ref() == Some(&error) {
|
||||
return;
|
||||
}
|
||||
tab.extra.point_failed = Some(error.clone());
|
||||
let at = match tab.extra.pointed_at {
|
||||
Some(at) => humantime(at),
|
||||
None => text(cx, "office.stopped.never", &[]).to_string(),
|
||||
};
|
||||
let name = tab.name.to_string();
|
||||
let said = text(cx, "mark.recovery.failed", &[("name", &name), ("error", &error), ("at", &at)]);
|
||||
self.toast_unlogged(said, ely_gpui_component::primitives::Severity::Danger, cx);
|
||||
}
|
||||
|
||||
fn write_session(&mut self, saved: Saved) {
|
||||
fn write_session(&mut self, saved: Saved, cx: &mut Context<Self>) {
|
||||
let json = serde_json::to_string_pretty(&saved).expect("the session serializes");
|
||||
if self.keeping.written.as_ref() == Some(&json) {
|
||||
return;
|
||||
}
|
||||
let file = self.keeping.dir.join("session.json");
|
||||
match ely_files::save_atomic(&file, json.as_bytes(), 0) {
|
||||
Ok(_) => self.keeping.written = Some(json),
|
||||
Err(error) => log::error!("mark: the session did not save: {error}"),
|
||||
let error = match ely_files::save_atomic(&file, json.as_bytes(), 0) {
|
||||
Ok(_) => {
|
||||
self.keeping.written = Some(json);
|
||||
self.keeping.unwritten = None;
|
||||
return;
|
||||
}
|
||||
Err(error) => error.to_string(),
|
||||
};
|
||||
log::error!("mark: the session did not save: {error}");
|
||||
// Each failure shows once; the next tick retries.
|
||||
if self.keeping.unwritten.as_ref() != Some(&error) {
|
||||
self.keeping.unwritten = Some(error.clone());
|
||||
let said = text(cx, "mark.session.failed", &[("error", &error)]);
|
||||
self.toast(said, ely_gpui_component::primitives::Severity::Danger, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +418,7 @@ impl Desk {
|
||||
}
|
||||
}
|
||||
let tabs = self.keeping.frozen.clone();
|
||||
self.write_session(Saved { tabs });
|
||||
self.write_session(Saved { tabs }, cx);
|
||||
}
|
||||
|
||||
/// A quit or close begins: the tabs now stay kept.
|
||||
|
||||
@@ -219,4 +219,94 @@ mod window {
|
||||
);
|
||||
assert!(read.is_err(), "an unknown mode is no default");
|
||||
}
|
||||
|
||||
/// The window's toasts that hold `words`.
|
||||
fn toasted(desk: &Entity<Desk>, words: &str, cx: &mut VisualTestContext) -> usize {
|
||||
let root = desk.read_with(cx, |desk, _| desk.core.root.upgrade().expect("the window"));
|
||||
let toasts = root.read_with(cx, |root, cx| root.toasts(cx));
|
||||
toasts
|
||||
.iter()
|
||||
.filter(|(said, _)| said.contains(words))
|
||||
.count()
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_failed_recovery_write_shows_once_with_the_last_kept(cx: &mut TestAppContext) {
|
||||
let (data, files) = (
|
||||
tempfile::tempdir().expect("data"),
|
||||
tempfile::tempdir().expect("files"),
|
||||
);
|
||||
let a = files.path().join("a.md");
|
||||
std::fs::write(
|
||||
&a, "Text.
|
||||
",
|
||||
)
|
||||
.expect("a");
|
||||
let (desk, cx) = start(cx, data.path());
|
||||
open(&desk, &a, cx);
|
||||
let view = show(&desk, "a.md", cx);
|
||||
for (typed, broken) in [
|
||||
(
|
||||
"One.
|
||||
", false,
|
||||
),
|
||||
(
|
||||
"Two.
|
||||
", true,
|
||||
),
|
||||
(
|
||||
"Three.
|
||||
", true,
|
||||
),
|
||||
] {
|
||||
if broken && data.path().join("recovery").is_dir() {
|
||||
std::fs::remove_dir_all(data.path().join("recovery")).expect("removes");
|
||||
std::fs::write(data.path().join("recovery"), "not a folder").expect("blocks");
|
||||
}
|
||||
view.update(cx, |view, cx| view.adopt(typed.into(), cx));
|
||||
cx.executor()
|
||||
.advance_clock(std::time::Duration::from_secs(1));
|
||||
cx.run_until_parked();
|
||||
desk.update(cx, |desk, cx| desk.keep(cx));
|
||||
cx.run_until_parked();
|
||||
}
|
||||
assert_eq!(toasted(&desk, "was not kept for recovery", cx), 1, "once");
|
||||
let never = toasted(&desk, "none yet", cx);
|
||||
assert_eq!(never, 0, "the first point landed, so its time shows");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_failed_session_write_shows_once(cx: &mut TestAppContext) {
|
||||
let (data, files) = (
|
||||
tempfile::tempdir().expect("data"),
|
||||
tempfile::tempdir().expect("files"),
|
||||
);
|
||||
let a = files.path().join("a.md");
|
||||
std::fs::write(
|
||||
&a, "Text.
|
||||
",
|
||||
)
|
||||
.expect("a");
|
||||
let (desk, cx) = start(cx, data.path());
|
||||
open(&desk, &a, cx);
|
||||
let session = data.path().join("session.json");
|
||||
if session.exists() {
|
||||
std::fs::remove_file(&session).expect("removes");
|
||||
}
|
||||
std::fs::create_dir(&session).expect("blocks");
|
||||
for line in [1, 2] {
|
||||
let view = show(&desk, "a.md", cx);
|
||||
view.update(cx, |view, cx| {
|
||||
view.document()
|
||||
.update(cx, |document, cx| document.set_caret(line, 0, cx))
|
||||
});
|
||||
desk.update(cx, |desk, cx| desk.keep(cx));
|
||||
cx.run_until_parked();
|
||||
}
|
||||
assert_eq!(
|
||||
toasted(&desk, "were not saved for the next launch", cx),
|
||||
1,
|
||||
"once"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user