fix: handle operation results before the busy guard so they clear it
This commit is contained in:
parent
e9e40bca36
commit
96f3748086
1 changed files with 114 additions and 72 deletions
182
src/app.rs
182
src/app.rs
|
|
@ -389,6 +389,80 @@ impl Carrier {
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
// Results pass regardless of the busy flag: each one clears the
|
||||||
|
// operation it belongs to, so swallowing it would deadlock.
|
||||||
|
Message::PinOffered(result) => {
|
||||||
|
self.done();
|
||||||
|
match result {
|
||||||
|
Ok(Some((addr, key))) => {
|
||||||
|
self.pin_offer = Some((addr, key));
|
||||||
|
self.note(
|
||||||
|
"The server presented this key. Pin it only if the fingerprint \
|
||||||
|
matches what the operator published."
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
self.note("This server's key is already pinned.".into());
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.fail(e);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::Pinned(result) => {
|
||||||
|
self.done();
|
||||||
|
match result {
|
||||||
|
Ok(()) => self.note("server key pinned".into()),
|
||||||
|
Err(e) => self.fail(e),
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::Exported(result) => {
|
||||||
|
self.done();
|
||||||
|
match result {
|
||||||
|
Ok((note, text)) => {
|
||||||
|
self.backup_text = Some(text);
|
||||||
|
self.note(note);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.fail(e);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::SnapshotLoaded(result) => {
|
||||||
|
self.done();
|
||||||
|
match result {
|
||||||
|
Ok(snap) => {
|
||||||
|
self.share_qr = qr_code::Data::new(
|
||||||
|
format!("smol://{}/{}", snap.who.address, snap.who.key).as_bytes(),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
self.lists = snap.lists.clone();
|
||||||
|
self.snapshot = Some(snap);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.fail(e);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::Generic(result) => {
|
||||||
|
self.done();
|
||||||
|
match result {
|
||||||
|
Ok(note) => self.refresh_after(note),
|
||||||
|
Err(e) => {
|
||||||
|
self.fail(e);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ if self.busy.is_some() => Task::none(),
|
_ if self.busy.is_some() => Task::none(),
|
||||||
Message::OnboardCreate => {
|
Message::OnboardCreate => {
|
||||||
let master = ops::new_master();
|
let master = ops::new_master();
|
||||||
|
|
@ -477,28 +551,6 @@ impl Carrier {
|
||||||
self.pin_offer = None;
|
self.pin_offer = None;
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::PinOffered(result) => {
|
|
||||||
self.done();
|
|
||||||
match result {
|
|
||||||
Ok(Some((addr, key))) => {
|
|
||||||
self.pin_offer = Some((addr, key));
|
|
||||||
self.note(
|
|
||||||
"The server presented this key. Pin it only if the fingerprint \
|
|
||||||
matches what the operator published."
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
Ok(None) => {
|
|
||||||
self.note("This server's key is already pinned.".into());
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
self.fail(e);
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::PinAccept => {
|
Message::PinAccept => {
|
||||||
let Some((addr, key)) = self.pin_offer.take() else {
|
let Some((addr, key)) = self.pin_offer.take() else {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
|
|
@ -507,14 +559,6 @@ impl Carrier {
|
||||||
ops::pin(&store, &addr, &key)
|
ops::pin(&store, &addr, &key)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Message::Pinned(result) => {
|
|
||||||
self.done();
|
|
||||||
match result {
|
|
||||||
Ok(()) => self.note("server key pinned".into()),
|
|
||||||
Err(e) => self.fail(e),
|
|
||||||
}
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
Message::SignupRegister => {
|
Message::SignupRegister => {
|
||||||
let Some(addr) = self.signup_address() else {
|
let Some(addr) = self.signup_address() else {
|
||||||
self.fail("enter user and host".into());
|
self.fail("enter user and host".into());
|
||||||
|
|
@ -878,20 +922,6 @@ impl Carrier {
|
||||||
.map(|(path, text)| (format!("backup written to {}", path.display()), text))
|
.map(|(path, text)| (format!("backup written to {}", path.display()), text))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Message::Exported(result) => {
|
|
||||||
self.done();
|
|
||||||
match result {
|
|
||||||
Ok((note, text)) => {
|
|
||||||
self.backup_text = Some(text);
|
|
||||||
self.note(note);
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
self.fail(e);
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::ImportPressed => {
|
Message::ImportPressed => {
|
||||||
let text = self.import_body.text();
|
let text = self.import_body.text();
|
||||||
if text.trim().is_empty() {
|
if text.trim().is_empty() {
|
||||||
|
|
@ -901,34 +931,46 @@ impl Carrier {
|
||||||
ops::import_backup(&store, &master, &text)
|
ops::import_backup(&store, &master, &text)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Message::SnapshotLoaded(result) => {
|
|
||||||
self.done();
|
|
||||||
match result {
|
|
||||||
Ok(snap) => {
|
|
||||||
self.share_qr = qr_code::Data::new(
|
|
||||||
format!("smol://{}/{}", snap.who.address, snap.who.key).as_bytes(),
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
self.lists = snap.lists.clone();
|
|
||||||
self.snapshot = Some(snap);
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
self.fail(e);
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::Generic(result) => {
|
|
||||||
self.done();
|
|
||||||
match result {
|
|
||||||
Ok(note) => self.refresh_after(note),
|
|
||||||
Err(e) => {
|
|
||||||
self.fail(e);
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn app() -> Carrier {
|
||||||
|
let dir = tempfile::tempdir().unwrap().keep();
|
||||||
|
let (app, _task) = Carrier::boot(dir);
|
||||||
|
app
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn results_clear_the_busy_flag_instead_of_deadlocking() {
|
||||||
|
let mut app = app();
|
||||||
|
app.busy = Some("registering".into());
|
||||||
|
let _ = app.update(Message::Generic(Err("boom".into())));
|
||||||
|
assert!(app.busy.is_none());
|
||||||
|
assert!(app.status_error);
|
||||||
|
|
||||||
|
app.busy = Some("registering".into());
|
||||||
|
let _ = app.update(Message::SnapshotLoaded(Err("boom".into())));
|
||||||
|
assert!(app.busy.is_none());
|
||||||
|
|
||||||
|
app.busy = Some("registering".into());
|
||||||
|
let _ = app.update(Message::Generic(Ok("registered".into())));
|
||||||
|
// The result was handled: the note landed and the refresh it
|
||||||
|
// triggers is the new busy operation.
|
||||||
|
assert_eq!(app.status.as_deref(), Some("registered"));
|
||||||
|
assert_eq!(app.busy.as_deref(), Some("refreshing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn actions_are_swallowed_while_busy() {
|
||||||
|
let mut app = app();
|
||||||
|
app.master = Some([0u8; KEY_LEN]);
|
||||||
|
app.busy = Some("fetching".into());
|
||||||
|
let _ = app.update(Message::FetchPressed);
|
||||||
|
assert_eq!(app.busy.as_deref(), Some("fetching"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue