C12.7: POP3 fetch by UIDL
This commit is contained in:
18 files changed
+394
-47
No files matched your search
@@ -4,7 +4,7 @@ use ely_gpui_component::{
|
||||
buttons::{Button, ButtonVariant, SegmentedControl},
|
||||
data_display::DescriptionList,
|
||||
feedback::{Banner, InlineMessage},
|
||||
forms::{Input, PasswordInput},
|
||||
forms::{Input, PasswordInput, Switch},
|
||||
overlays::Dialog,
|
||||
primitives::Severity,
|
||||
};
|
||||
@@ -207,6 +207,21 @@ impl Mail {
|
||||
.item(label("mail.add.user"), field(&fields.user))
|
||||
.item(label("mail.add.password"), password);
|
||||
}
|
||||
if adding.kind == Kind::Pop {
|
||||
let this = this.clone();
|
||||
let keep = Switch::new("mail-add-keep", adding.keep)
|
||||
.name(label("mail.add.keep"))
|
||||
.on_change(move |on, _, cx| {
|
||||
this.update(cx, |mail, cx| {
|
||||
if let Some(adding) = &mut mail.adding {
|
||||
adding.set_keep(on);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
rows = rows.item(label("mail.add.keep"), keep);
|
||||
}
|
||||
rows.item(label("mail.add.incoming"), field(&fields.incoming))
|
||||
.item(label("mail.add.port"), field(&fields.incoming_port))
|
||||
.item(
|
||||
|
||||
@@ -42,6 +42,8 @@ pub struct Adding {
|
||||
pub outgoing_security: Security,
|
||||
/// Typed-in accounts choose; providers fix it.
|
||||
pub auth: Auth,
|
||||
/// POP3 leaves a copy on the server (§12.3).
|
||||
pub keep: bool,
|
||||
pub step: Step,
|
||||
/// The ports last filled in; any other was typed.
|
||||
filled: [String; 2],
|
||||
@@ -108,6 +110,7 @@ impl Adding {
|
||||
incoming_security: Security::Tls,
|
||||
outgoing_security: Security::StartTls,
|
||||
auth: Auth::Password,
|
||||
keep: true,
|
||||
step: Step::Editing,
|
||||
filled: Default::default(),
|
||||
asked: None,
|
||||
@@ -144,10 +147,16 @@ impl Adding {
|
||||
self.set_ports(cx);
|
||||
}
|
||||
|
||||
pub fn set_keep(&mut self, keep: bool) {
|
||||
self.stop();
|
||||
self.step = Step::Editing;
|
||||
self.keep = keep;
|
||||
}
|
||||
|
||||
/// The usual ports, unless one was typed.
|
||||
fn set_ports(&mut self, cx: &mut App) {
|
||||
let ports = [
|
||||
kinds::port(Some(self.kind.protocol()), self.incoming_security),
|
||||
kinds::port(Some(self.kind.protocol(self.keep)), self.incoming_security),
|
||||
kinds::port(None, self.outgoing_security),
|
||||
];
|
||||
let fields = [&self.fields.incoming_port, &self.fields.outgoing_port];
|
||||
@@ -229,7 +238,7 @@ impl Adding {
|
||||
address,
|
||||
user,
|
||||
auth: self.auth(),
|
||||
protocol: self.kind.protocol(),
|
||||
protocol: self.kind.protocol(self.keep),
|
||||
incoming,
|
||||
outgoing,
|
||||
};
|
||||
|
||||
@@ -37,9 +37,10 @@ impl Kind {
|
||||
KINDS.into_iter().find(|kind| kind.key() == key)
|
||||
}
|
||||
|
||||
pub fn protocol(self) -> Protocol {
|
||||
/// `keep` is POP3's server copy.
|
||||
pub fn protocol(self, keep: bool) -> Protocol {
|
||||
match self {
|
||||
Kind::Pop => Protocol::Pop,
|
||||
Kind::Pop => Protocol::Pop { keep },
|
||||
_ => Protocol::Imap,
|
||||
}
|
||||
}
|
||||
@@ -78,8 +79,8 @@ pub fn port(protocol: Option<Protocol>, security: Security) -> u16 {
|
||||
match (protocol, security) {
|
||||
(Some(Protocol::Imap), Security::Tls) => 993,
|
||||
(Some(Protocol::Imap), Security::StartTls) => 143,
|
||||
(Some(Protocol::Pop), Security::Tls) => 995,
|
||||
(Some(Protocol::Pop), Security::StartTls) => 110,
|
||||
(Some(Protocol::Pop { .. }), Security::Tls) => 995,
|
||||
(Some(Protocol::Pop { .. }), Security::StartTls) => 110,
|
||||
(None, Security::Tls) => 465,
|
||||
(None, Security::StartTls) => 587,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! IMAP accounts sync on open and once added.
|
||||
//! Accounts sync on open and once added.
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@@ -26,9 +26,6 @@ impl Mail {
|
||||
let Some((_, account)) = self.accounts().iter().find(|(each, _)| *each == id) else {
|
||||
return log::info!("mail: sync asked of a gone account");
|
||||
};
|
||||
if account.protocol != Protocol::Imap {
|
||||
return;
|
||||
}
|
||||
if self.syncs.get(&id).is_some_and(|each| each.task.is_some()) {
|
||||
return log::info!("mail: account {} is already syncing", id.0);
|
||||
}
|
||||
@@ -50,7 +47,10 @@ impl Mail {
|
||||
.map_err(|err| ely_mail_engine::Error::Store(format!("the clock: {err}")))?
|
||||
.as_secs() as i64;
|
||||
let secret = sync::secret(&account, &vault).await?;
|
||||
sync::imap(&account, id, secret, &tls, &store, now).await
|
||||
match account.protocol {
|
||||
Protocol::Imap => sync::imap(&account, id, secret, &tls, &store, now).await,
|
||||
Protocol::Pop { .. } => sync::pop(&account, id, secret, &tls, &store, now).await,
|
||||
}
|
||||
};
|
||||
let task = self.on_runtime(cx, work, move |mail, done, cx| {
|
||||
let failed = match done {
|
||||
|
||||
@@ -1,41 +1,45 @@
|
||||
use ely_mail_engine::{Protocol, Store};
|
||||
use gpui::TestAppContext;
|
||||
|
||||
use super::{Keys, account, closed_port, settle, window, window_with};
|
||||
use super::{Keys, account, adding, closed_port, settle, type_in, window, window_with};
|
||||
use crate::kinds::Kind;
|
||||
|
||||
#[test]
|
||||
fn an_imap_account_syncs_on_open_and_says_why_it_failed() {
|
||||
fn every_account_syncs_on_open_and_says_why_it_failed() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = Store::open(dir.path()).unwrap();
|
||||
let mut ann = account("ann@example.com");
|
||||
ann.incoming.port = closed_port();
|
||||
let ann = store.add_account(&ann).unwrap();
|
||||
let pop = ely_mail_engine::Account {
|
||||
protocol: Protocol::Pop,
|
||||
let mut pop = ely_mail_engine::Account {
|
||||
protocol: Protocol::Pop { keep: true },
|
||||
..account("bo@example.com")
|
||||
};
|
||||
pop.incoming.port = closed_port();
|
||||
let bo = store.add_account(&pop).unwrap();
|
||||
drop(store);
|
||||
let keys = Keys::default();
|
||||
keys.kept
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert("account:ann@example.com".into(), "pw".into());
|
||||
.extend(["ann", "bo"].map(|name| (format!("account:{name}@example.com"), "pw".into())));
|
||||
let mut cx = TestAppContext::single();
|
||||
let (mail, cx) = window(&mut cx, dir.path(), keys);
|
||||
settle(cx, |app| {
|
||||
mail.read(app)
|
||||
.syncs
|
||||
.get(&ann)
|
||||
.is_some_and(|each| each.task.is_none() && each.synced.is_some())
|
||||
[ann, bo].iter().all(|id| {
|
||||
mail.read(app)
|
||||
.syncs
|
||||
.get(id)
|
||||
.is_some_and(|each| each.task.is_none() && each.synced.is_some())
|
||||
})
|
||||
});
|
||||
cx.update(|_, app| {
|
||||
let mail = mail.read(app);
|
||||
let syncing = &mail.syncs[&ann];
|
||||
let failed = syncing.failed.as_deref().unwrap();
|
||||
assert!(failed.starts_with("cannot connect"), "{failed}");
|
||||
assert_eq!(syncing.synced.unwrap().at, None);
|
||||
assert!(!mail.syncs.contains_key(&bo), "POP3 is collected apart");
|
||||
for id in [ann, bo] {
|
||||
let syncing = &mail.read(app).syncs[&id];
|
||||
let failed = syncing.failed.as_deref().unwrap();
|
||||
assert!(failed.starts_with("cannot connect"), "{failed}");
|
||||
assert_eq!(syncing.synced.unwrap().at, None);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -88,3 +92,24 @@ fn a_missing_tls_is_the_sync_failure() {
|
||||
assert!(syncing.task.is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pop_account_keeps_a_server_copy_unless_told() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let mut cx = TestAppContext::single();
|
||||
let (mail, cx) = window(&mut cx, dir.path(), Keys::default());
|
||||
adding(&mail, Kind::Pop, cx);
|
||||
type_in(&mail, cx, |f| &f.address, "bo@example.com");
|
||||
type_in(&mail, cx, |f| &f.user, "bo");
|
||||
type_in(&mail, cx, |f| &f.incoming, "pop.example.com");
|
||||
type_in(&mail, cx, |f| &f.outgoing, "smtp.example.com");
|
||||
let protocol = |cx: &mut gpui::VisualTestContext| {
|
||||
cx.update(|_, app| {
|
||||
let adding = mail.read(app).adding.as_ref().unwrap();
|
||||
adding.account(None, app).unwrap().protocol
|
||||
})
|
||||
};
|
||||
assert_eq!(protocol(cx), Protocol::Pop { keep: true });
|
||||
cx.update(|_, app| mail.update(app, |mail, _| mail.adding.as_mut().unwrap().set_keep(false)));
|
||||
assert_eq!(protocol(cx), Protocol::Pop { keep: false });
|
||||
}
|
||||
@@ -97,7 +97,7 @@ impl Mail {
|
||||
server(&account.outgoing, cx),
|
||||
)
|
||||
.text(label("mail.account.auth"), auth(account.auth, cx));
|
||||
let (details, failed) = self.sync_rows(*id, details, cx);
|
||||
let (details, failed) = self.sync_rows(*id, account.protocol, details, cx);
|
||||
let remove = Button::new("mail-remove", label("mail.remove.open"))
|
||||
.on_click(|_, window, cx| window.dispatch_action(Box::new(menu::RemoveAccount), cx));
|
||||
div()
|
||||
@@ -116,6 +116,7 @@ impl Mail {
|
||||
fn sync_rows(
|
||||
&self,
|
||||
id: AccountId,
|
||||
protocol: Protocol,
|
||||
details: DescriptionList,
|
||||
cx: &App,
|
||||
) -> (DescriptionList, Option<Banner>) {
|
||||
@@ -128,12 +129,24 @@ impl Mail {
|
||||
(None, Some(at)) => local_time(at).into(),
|
||||
(None, None) => label("mail.sync.never"),
|
||||
};
|
||||
let days = sync::BODY_DAYS.to_string();
|
||||
let mut details = details.text(label("mail.sync.last"), last).text(
|
||||
label("mail.sync.range"),
|
||||
text(cx, "mail.sync.range_value", &[("days", &days)]),
|
||||
);
|
||||
if let Some(synced) = syncing.synced {
|
||||
let mut details = details.text(label("mail.sync.last"), last);
|
||||
if let Protocol::Pop { keep } = protocol {
|
||||
let server = label(if keep {
|
||||
"mail.sync.kept"
|
||||
} else {
|
||||
"mail.sync.deleted"
|
||||
});
|
||||
details = details
|
||||
.text(label("mail.sync.range"), label("mail.sync.range_pop"))
|
||||
.text(label("mail.sync.server"), server);
|
||||
} else {
|
||||
let days = sync::BODY_DAYS.to_string();
|
||||
details = details.text(
|
||||
label("mail.sync.range"),
|
||||
text(cx, "mail.sync.range_value", &[("days", &days)]),
|
||||
);
|
||||
}
|
||||
if let (Some(synced), Protocol::Imap) = (syncing.synced, protocol) {
|
||||
for (key, n) in [
|
||||
("mail.sync.uncached", synced.uncached),
|
||||
("mail.sync.partial", synced.partial),
|
||||
@@ -196,7 +209,7 @@ impl Mail {
|
||||
pub fn protocol_name(protocol: Protocol) -> &'static str {
|
||||
match protocol {
|
||||
Protocol::Imap => "IMAP",
|
||||
Protocol::Pop => "POP3",
|
||||
Protocol::Pop { .. } => "POP3",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user