feat: list the store live while a fetch runs
This commit is contained in:
parent
50299547e5
commit
7ed2889966
3 changed files with 67 additions and 2 deletions
63
src/app.rs
63
src/app.rs
|
|
@ -10,9 +10,10 @@ use std::sync::Arc;
|
|||
use fumi_core::address::Address;
|
||||
use fumi_core::crypto::{unb32, KEY_LEN};
|
||||
use fumi_core::store::Store;
|
||||
use iced::time::{self, Duration};
|
||||
use iced::widget::qr_code;
|
||||
use iced::widget::text_editor::{self, Action};
|
||||
use iced::{clipboard, Task};
|
||||
use iced::{clipboard, Subscription, Task};
|
||||
|
||||
use crate::ops::{self, Folder, OpenedMsg, Row, Snapshot};
|
||||
|
||||
|
|
@ -136,6 +137,9 @@ pub enum Message {
|
|||
MessageSelected(Folder, String),
|
||||
FetchPressed,
|
||||
FetchCancelled,
|
||||
/// A live re-list while a fetch runs: the store commits each envelope
|
||||
/// as it verifies, so the list can show mail arriving mid-fetch.
|
||||
FetchTick,
|
||||
ComposePressed,
|
||||
ComposeToEdited(String),
|
||||
ComposeSubjectEdited(String),
|
||||
|
|
@ -369,6 +373,16 @@ impl Carrier {
|
|||
self.store.account().ok().flatten()
|
||||
}
|
||||
|
||||
/// While a fetch runs, tick a few times a second so the list shows mail
|
||||
/// as it lands instead of freezing until the whole fetch returns.
|
||||
pub fn subscription(&self) -> Subscription<Message> {
|
||||
if self.busy.as_deref() == Some("fetching") {
|
||||
time::every(Duration::from_millis(250)).map(|_| Message::FetchTick)
|
||||
} else {
|
||||
Subscription::none()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
// Text editor actions must survive the busy guard: typing in the
|
||||
|
|
@ -389,6 +403,17 @@ impl Carrier {
|
|||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::FetchTick => {
|
||||
if self.busy.as_deref() != Some("fetching") {
|
||||
return Task::none();
|
||||
}
|
||||
let master = self.master();
|
||||
let read = self.marks.snapshot();
|
||||
if let Ok(lists) = ops::lists(&self.store, &master, &read) {
|
||||
self.lists = lists;
|
||||
}
|
||||
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) => {
|
||||
|
|
@ -974,3 +999,39 @@ mod tests {
|
|||
assert_eq!(app.busy.as_deref(), Some("fetching"));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tick_tests {
|
||||
use super::*;
|
||||
use fumi_core::message::{build_frontmatter, message_id, seal};
|
||||
|
||||
/// The store is committed envelope by envelope, so a tick mid-fetch
|
||||
/// sees a message that the running fetch has already verified.
|
||||
#[test]
|
||||
fn fetch_tick_lists_mail_as_it_lands() {
|
||||
let dir = tempfile::tempdir().unwrap().keep();
|
||||
let (mut app, _task) = Carrier::boot(dir.clone());
|
||||
let master = ops::new_master();
|
||||
app.master = Some(master);
|
||||
|
||||
let me = ops::account(&app.store, &master).unwrap();
|
||||
let addr = fumi_core::address::Address::parse("me@localhost").unwrap();
|
||||
app.store.set_account(&addr).unwrap();
|
||||
let body = build_frontmatter(&[("Subject", "live")], "tick");
|
||||
let envelope = seal(me.me(), &me.me().pk(), body.as_bytes(), 1000, true).unwrap();
|
||||
let id = message_id(&envelope);
|
||||
app.store
|
||||
.store_inbox(&id, &envelope, 1000, false, false)
|
||||
.unwrap();
|
||||
|
||||
app.busy = Some("fetching".into());
|
||||
let _ = app.update(Message::FetchTick);
|
||||
assert_eq!(app.busy.as_deref(), Some("fetching"));
|
||||
assert_eq!(app.lists[0].len(), 1);
|
||||
assert_eq!(app.lists[0][0].subject, "live");
|
||||
|
||||
// A tick outside a fetch is a no-op.
|
||||
app.busy = None;
|
||||
let _ = app.update(Message::FetchTick);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ fn main() -> iced::Result {
|
|||
}
|
||||
iced::application("carrier", Carrier::update, view::view)
|
||||
.theme(|_| theme::theme())
|
||||
.subscription(Carrier::subscription)
|
||||
.window_size(iced::Size::new(1080.0, 640.0))
|
||||
.run_with(move || {
|
||||
let (state, task) = Carrier::boot(dir);
|
||||
|
|
|
|||
|
|
@ -198,9 +198,12 @@ fn status<'a>(app: &'a Carrier) -> Element<'a, Message> {
|
|||
let mut line = row![].spacing(10).align_y(Alignment::Center);
|
||||
match (&app.busy, &app.status) {
|
||||
(Some(busy), _) => {
|
||||
line = line.push(muted(format!("{busy}…")));
|
||||
if busy == "fetching" {
|
||||
let inbox = app.lists.first().map(|rows| rows.len()).unwrap_or(0);
|
||||
line = line.push(muted(format!("fetching… {inbox} in inbox")));
|
||||
line = line.push(quiet_action("Cancel", Message::FetchCancelled));
|
||||
} else {
|
||||
line = line.push(muted(format!("{busy}…")));
|
||||
}
|
||||
}
|
||||
(None, Some(status)) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue