mirror of
https://github.com/FliegendeWurst/inboxid.git
synced 2024-11-24 09:55:09 +00:00
Store flags in the index DB + 'U' unread flag
This commit is contained in:
parent
efd5575346
commit
95b6abc572
@ -6,24 +6,34 @@ use mailparse::MailHeaderMap;
|
||||
use rusqlite::params;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let db = get_db()?;
|
||||
let mut delete_mail = db.prepare("DELETE FROM mail WHERE mailbox = ?")?;
|
||||
let mut save_mail = db.prepare("INSERT INTO mail VALUES (?,?,?)")?;
|
||||
let mut db = get_db()?;
|
||||
let tx = db.transaction()?;
|
||||
{
|
||||
let mut delete_mail = tx.prepare("DELETE FROM mail WHERE mailbox = ?")?;
|
||||
let mut save_mail = tx.prepare("INSERT INTO mail VALUES (?,?,?,?)")?;
|
||||
let mailboxes = env::args().skip(1).collect_vec();
|
||||
for mailbox in mailboxes {
|
||||
println!("reading {}..", mailbox);
|
||||
let maildir = get_maildir(&mailbox)?;
|
||||
delete_mail.execute(params![&mailbox])?;
|
||||
let mut mails = Vec::new();
|
||||
for x in maildir.list_cur() {
|
||||
mails.push(x?);
|
||||
}
|
||||
println!("acquired {} mails", mails.len());
|
||||
let mut mails = maildir.get_mails(&mut mails)?;
|
||||
mails.sort_by_key(|x| x.date);
|
||||
for mail in mails {
|
||||
let headers = mail.get_headers();
|
||||
let message_id = headers.get_all_values("Message-ID").join(" ");
|
||||
save_mail.execute(params![&mailbox, mail.id.to_i64(), message_id])?;
|
||||
let mut message_id = headers.get_all_values("Message-ID").join(" ");
|
||||
if message_id.is_empty() {
|
||||
message_id = format!("<{}_{}_{}@no-message-id>", mailbox, mail.id.uid_validity, mail.id.uid);
|
||||
}
|
||||
save_mail.execute(params![&mailbox, mail.id.to_i64(), message_id, mail.flags])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
tx.commit()?;
|
||||
db.execute("VACUUM", params![])?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -64,45 +64,60 @@ fn sync(
|
||||
remote.insert(mailbox, mails);
|
||||
}
|
||||
|
||||
let mut have_mail = db.prepare("SELECT mailbox, uid FROM mail WHERE message_id = ?")?;
|
||||
let mut have_mail = db.prepare("SELECT mailbox, uid, flags FROM mail WHERE message_id = ?")?;
|
||||
let mut delete_mail = db.prepare("DELETE FROM mail WHERE mailbox = ? AND uid = ?")?;
|
||||
let mut all_mail = db.prepare("SELECT uid, message_id FROM mail WHERE mailbox = ?")?;
|
||||
let mut save_mail = db.prepare("INSERT INTO mail VALUES (?,?,?)")?;
|
||||
let mut save_mail = db.prepare("INSERT INTO mail VALUES (?,?,?,?)")?;
|
||||
let mut maildirs: HashMap<&str, Maildir> = names.iter().map(|&x| (x.name(), get_maildir(x.name()).unwrap())).collect();
|
||||
let mut to_remove: HashMap<&str, _> = HashMap::new();
|
||||
for &name in &names {
|
||||
let mailbox = name.name();
|
||||
let remote_mails = &remote[mailbox];
|
||||
let remote_mails = remote.get_mut(mailbox).unwrap();
|
||||
let resp = imap_session.examine(mailbox)?;
|
||||
let uid_validity = resp.uid_validity.unwrap();
|
||||
|
||||
let mut to_fetch = Vec::new();
|
||||
for message_id in remote_mails.keys() {
|
||||
let (uid1, uid2, full_uid, ref _flags) = remote_mails[message_id];
|
||||
let local = have_mail.query_map(params![message_id], |row| Ok((row.get::<_, String>(0)?, load_i64(row.get::<_, i64>(1)?))))?.map(|x| x.unwrap()).collect_vec();
|
||||
if local.iter().any(|x| x.0 == mailbox && x.1 == full_uid) {
|
||||
for (message_id, entry) in remote_mails.iter_mut() {
|
||||
let (uid1, uid2, full_uid, remote_flags) = entry;
|
||||
let local = have_mail.query_map(params![message_id], |row| Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
load_i64(row.get::<_, i64>(1)?),
|
||||
row.get::<_, String>(2)?
|
||||
)))?.map(|x| x.unwrap()).collect_vec();
|
||||
if let Some((_, full_uid, flags)) = local.iter().filter(|x| x.0 == mailbox && x.1 == *full_uid).next() {
|
||||
let uid = (full_uid << 32) >> 32;
|
||||
let local_s = flags.contains('S');
|
||||
let local_u = flags.contains('U');
|
||||
let remote_s = remote_flags.contains(&Flag::Seen);
|
||||
if local_s && !remote_s {
|
||||
imap_session.uid_store(uid.to_string(), "+FLAGS.SILENT (\\Seen)")?;
|
||||
remote_flags.push(Flag::Seen);
|
||||
} else if local_u && remote_s {
|
||||
imap_session.uid_store(uid.to_string(), "-FLAGS.SILENT (\\Seen)")?;
|
||||
remote_flags.remove(remote_flags.iter().position(|x| x == &Flag::Seen).unwrap());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if !local.is_empty() {
|
||||
let (inbox, full_uid) = &local[0];
|
||||
let (inbox, full_uid, flags) = &local[0];
|
||||
let local_uid1 = (full_uid >> 32) as u32;
|
||||
let local_uid2 = ((full_uid << 32) >> 32) as u32;
|
||||
let local_id = gen_id(local_uid1, local_uid2);
|
||||
let new_uid = MaildirID::new(uid1, uid2);
|
||||
let new_uid = MaildirID::new(*uid1, *uid2);
|
||||
let new_id = new_uid.to_string();
|
||||
// hardlink mail
|
||||
let maildir1 = &maildirs[&**inbox];
|
||||
println!("hardlinking: {}/{} -> {}/{}", inbox, local_id, mailbox, new_id);
|
||||
let name = maildir1.find_filename(&local_id).unwrap();
|
||||
let maildir2 = &maildirs[mailbox];
|
||||
maildir2.store_cur_from_path(&new_id, name)?;
|
||||
save_mail.execute(params![mailbox, new_uid.to_i64(), message_id])?;
|
||||
maildir2.store_cur_from_path(&new_id, flags, name)?;
|
||||
save_mail.execute(params![mailbox, new_uid.to_i64(), message_id, flags])?;
|
||||
} else if !name.attributes().iter().any(|x| *x == TRASH) { // do not fetch trashed mail
|
||||
to_fetch.push(uid2);
|
||||
}
|
||||
}
|
||||
if !to_fetch.is_empty() {
|
||||
let maildir = &maildirs[mailbox];
|
||||
let resp = imap_session.examine(mailbox)?;
|
||||
let uid_validity = resp.uid_validity.unwrap();
|
||||
|
||||
let fetch_range = to_fetch.into_iter().map(|x| x.to_string()).join(",");
|
||||
let fetch = imap_session.uid_fetch(fetch_range, "RFC822")?;
|
||||
@ -113,7 +128,8 @@ fn sync(
|
||||
let id = gen_id(uid_validity, uid);
|
||||
if !maildir.exists(&id) {
|
||||
let mail_data = mail.body().unwrap_or_default();
|
||||
maildir.store_cur_with_id(&id, mail_data)?;
|
||||
let flags = imap_flags_to_maildir("".into(), mail.flags());
|
||||
maildir.store_cur_with_id_flags(&id, &flags, mail_data)?;
|
||||
|
||||
let headers = parse_headers(&mail_data)?.0;
|
||||
let mut message_id = headers.get_all_values("Message-ID").join(" ");
|
||||
@ -121,7 +137,7 @@ fn sync(
|
||||
message_id = format!("<{}_{}_{}@no-message-id>", mailbox, uid_validity, uid);
|
||||
}
|
||||
let full_uid = ((uid_validity as u64) << 32) | uid as u64;
|
||||
save_mail.execute(params![mailbox, store_i64(full_uid), message_id])?;
|
||||
save_mail.execute(params![mailbox, store_i64(full_uid), message_id, flags])?;
|
||||
} else {
|
||||
println!("warning: DB outdated, downloaded mail again");
|
||||
}
|
||||
@ -132,22 +148,8 @@ fn sync(
|
||||
let (uid1, uid2, _, ref flags) = remote_mails[message_id];
|
||||
let id = gen_id(uid1, uid2);
|
||||
let _ = maildir.update_flags(&id, |f| {
|
||||
let mut f = f.to_owned();
|
||||
if flags.contains(&Flag::Seen) {
|
||||
f.push('S');
|
||||
} else {
|
||||
f = f.replace('S', "");
|
||||
}
|
||||
if flags.contains(&Flag::Answered) {
|
||||
f.push('R');
|
||||
} else {
|
||||
f = f.replace('R', "");
|
||||
}
|
||||
if flags.contains(&Flag::Flagged) {
|
||||
f.push('F');
|
||||
} else {
|
||||
f = f.replace('F', "");
|
||||
}
|
||||
let f = f.replace('U', "");
|
||||
let f = imap_flags_to_maildir(f, flags);
|
||||
Maildir::normalize_flags(&f)
|
||||
});
|
||||
}
|
||||
@ -175,7 +177,8 @@ fn sync(
|
||||
}
|
||||
let maildir = &maildirs[mailbox];
|
||||
let name = maildir.find_filename(&uid_name).unwrap();
|
||||
maildirs[".gone"].store_new_from_path(&format!("{}_{}", mailbox, uid_name), name)?;
|
||||
let _ = maildirs[".gone"].store_new_from_path(&format!("{}_{}", mailbox, uid_name), name);
|
||||
// hardlink should only fail if the mail was already deleted
|
||||
maildir.delete(&uid_name)?;
|
||||
delete_mail.execute(params![mailbox, store_i64(uid)])?;
|
||||
}
|
||||
|
55
src/lib.rs
55
src/lib.rs
@ -52,7 +52,8 @@ pub fn get_db() -> Result<Connection> {
|
||||
CREATE TABLE IF NOT EXISTS mail(
|
||||
mailbox STRING NOT NULL,
|
||||
uid INTEGER NOT NULL,
|
||||
message_id STRING NOT NULL
|
||||
message_id STRING NOT NULL,
|
||||
flags STRING NOT NULL
|
||||
)", params![])?;
|
||||
|
||||
Ok(conn)
|
||||
@ -64,7 +65,7 @@ pub fn gen_id(uid_validity: u32, uid: u32) -> String {
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct MaildirID {
|
||||
uid_validity: u32,
|
||||
pub uid_validity: u32,
|
||||
pub uid: u32,
|
||||
}
|
||||
|
||||
@ -105,7 +106,8 @@ pub struct EasyMail<'a> {
|
||||
mail: Option<ParsedMail<'a>>,
|
||||
pub id: MaildirID,
|
||||
pub flags: String,
|
||||
from: SingleInfo,
|
||||
from: Option<SingleInfo>,
|
||||
from_raw: String,
|
||||
pub subject: String,
|
||||
pub date: DateTime<Local>,
|
||||
pub date_iso: String,
|
||||
@ -117,10 +119,8 @@ impl EasyMail<'_> {
|
||||
mail: None,
|
||||
id: MaildirID::new(0, 0),
|
||||
flags: "S".to_owned(),
|
||||
from: SingleInfo {
|
||||
display_name: None,
|
||||
addr: String::new()
|
||||
},
|
||||
from: None,
|
||||
from_raw: String::new(),
|
||||
subject,
|
||||
date: Local.from_utc_datetime(&NaiveDateTime::from_timestamp(0, 0)),
|
||||
date_iso: "????-??-??".to_owned()
|
||||
@ -132,13 +132,17 @@ impl EasyMail<'_> {
|
||||
}
|
||||
|
||||
pub fn from(&self) -> String {
|
||||
let name = self.from.display_name.as_deref().unwrap_or_default();
|
||||
if let Some(from) = self.from.as_ref() {
|
||||
let name = from.display_name.as_deref().unwrap_or_default();
|
||||
if let Some(config) = CONFIG.get() {
|
||||
if config.read().browse.show_email_addresses {
|
||||
return format!("{} <{}>", name, self.from.addr);
|
||||
return format!("{} <{}>", name, from.addr);
|
||||
}
|
||||
}
|
||||
name.to_owned()
|
||||
} else {
|
||||
self.from_raw.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_header(&self, header: &str) -> String {
|
||||
@ -173,8 +177,10 @@ impl Eq for EasyMail<'_> {}
|
||||
impl Hash for EasyMail<'_> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
self.from.display_name.hash(state);
|
||||
self.from.addr.hash(state);
|
||||
if let Some(from) = self.from.as_ref() {
|
||||
from.display_name.hash(state);
|
||||
from.addr.hash(state);
|
||||
}
|
||||
self.subject.hash(state);
|
||||
}
|
||||
}
|
||||
@ -326,7 +332,8 @@ impl MaildirExtension for Maildir {
|
||||
let flags = maile.flags().to_owned();
|
||||
let mail = maile.parsed()?;
|
||||
let headers = mail.get_headers();
|
||||
let from = addrparse(&headers.get_all_values("From").join(" "))?.extract_single_info().context("failed to extract from")?;
|
||||
let from_raw = headers.get_all_values("From").join(" ");
|
||||
let from = addrparse(&from_raw).map(|x| x.extract_single_info()).ok().flatten();
|
||||
let subject = headers.get_all_values("Subject").join(" ");
|
||||
let date = headers.get_all_values("Date").join(" ");
|
||||
let date = dateparse(&date).map(|x|
|
||||
@ -337,6 +344,7 @@ impl MaildirExtension for Maildir {
|
||||
flags,
|
||||
id,
|
||||
from,
|
||||
from_raw,
|
||||
subject,
|
||||
date_iso: date.format("%Y-%m-%d %H:%M").to_string(),
|
||||
date,
|
||||
@ -353,7 +361,8 @@ impl MaildirExtension for Maildir {
|
||||
let flags = maile.flags().to_owned();
|
||||
let mail = maile.parsed()?;
|
||||
let headers = mail.get_headers();
|
||||
let from = addrparse(&headers.get_all_values("From").join(" "))?.extract_single_info().context("failed to extract from")?;
|
||||
let from_raw = headers.get_all_values("From").join(" ");
|
||||
let from = addrparse(&from_raw).map(|x| x.extract_single_info()).ok().flatten();
|
||||
let subject = headers.get_all_values("Subject").join(" ");
|
||||
let date = headers.get_all_values("Date").join(" ");
|
||||
let date = dateparse(&date).map(|x|
|
||||
@ -364,6 +373,7 @@ impl MaildirExtension for Maildir {
|
||||
flags,
|
||||
id,
|
||||
from,
|
||||
from_raw,
|
||||
subject,
|
||||
date_iso: date.format("%Y-%m-%d %H:%M").to_string(),
|
||||
date,
|
||||
@ -534,3 +544,22 @@ pub fn parse_effect(effect: &str) -> Option<Effect> {
|
||||
fn default_unread_style() -> Style {
|
||||
Effect::Reverse.into()
|
||||
}
|
||||
|
||||
pub fn imap_flags_to_maildir(mut f: String, flags: &[Flag]) -> String {
|
||||
if flags.contains(&Flag::Seen) {
|
||||
f.push('S');
|
||||
} else {
|
||||
f = f.replace('S', "");
|
||||
}
|
||||
if flags.contains(&Flag::Answered) {
|
||||
f.push('R');
|
||||
} else {
|
||||
f = f.replace('R', "");
|
||||
}
|
||||
if flags.contains(&Flag::Flagged) {
|
||||
f.push('F');
|
||||
} else {
|
||||
f = f.replace('F', "");
|
||||
}
|
||||
f
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user