Compare commits

...

5 Commits

Author SHA1 Message Date
Manos Pitsidianakis a4ae4da8b1
Add export-mbox command 2021-01-10 01:45:03 +02:00
Manos Pitsidianakis 4050f6893f
melib/mbox: add MboxFormat::append() method
Add support for writing mbox files
2021-01-10 01:40:54 +02:00
Manos Pitsidianakis dcccd303ac
melib/mbox: rename MboxReader to MboxFormat 2021-01-10 01:40:54 +02:00
Manos Pitsidianakis 22a64e2d76
melib: Remove unnecessary "pub use" std exports 2021-01-10 01:40:27 +02:00
Manos Pitsidianakis 781a1d0e1b
melib/backends: add collection() method to MailBackend
Keep track of the Collection state in the backend side
2021-01-10 01:31:27 +02:00
27 changed files with 355 additions and 119 deletions

View File

@ -418,6 +418,8 @@ Copy or move to other mailbox.
Copy or move to another account's mailbox.
.It Cm delete
Delete selected threads.
.It Cm export-mbox Ar FILEPATH
Export selected threads to mboxcl2 file.
.It Cm create-mailbox Ar ACCOUNT Ar MAILBOX_PATH
create mailbox with given path.
Be careful with backends and separator sensitivity (eg IMAP)

View File

@ -58,15 +58,15 @@ use self::maildir::MaildirType;
use self::mbox::MboxType;
use super::email::{Envelope, EnvelopeHash, Flag};
use std::any::Any;
use std::collections::{BTreeMap, BTreeSet};
use std::collections::BTreeSet;
use std::fmt;
use std::fmt::Debug;
use std::ops::Deref;
use std::sync::{Arc, RwLock};
pub use futures::stream::Stream;
use futures::stream::Stream;
use std::future::Future;
pub use std::pin::Pin;
use std::pin::Pin;
use std::collections::HashMap;
@ -348,9 +348,7 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
mailbox_hash: MailboxHash,
) -> ResultFuture<()>;
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
None
}
fn collection(&self) -> crate::Collection;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;

View File

@ -42,20 +42,21 @@ use crate::backends::{
*,
};
use crate::collection::Collection;
use crate::conf::AccountSettings;
use crate::connections::timeout;
use crate::email::{parser::BytesExt, *};
use crate::error::{MeliError, Result, ResultIntoMeliError};
use futures::lock::Mutex as FutureMutex;
use futures::stream::Stream;
use std::collections::{hash_map::DefaultHasher, BTreeMap};
use std::collections::hash_map::DefaultHasher;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::convert::TryFrom;
use std::convert::TryInto;
use std::hash::Hasher;
use std::pin::Pin;
use std::str::FromStr;
use std::sync::{Arc, Mutex, RwLock};
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
pub type ImapNum = usize;
@ -142,7 +143,7 @@ pub struct UIDStore {
msn_index: Arc<Mutex<HashMap<MailboxHash, Vec<UID>>>>,
byte_cache: Arc<Mutex<HashMap<UID, EnvelopeCache>>>,
tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
collection: Collection,
/* Offline caching */
uidvalidity: Arc<Mutex<HashMap<MailboxHash, UID>>>,
@ -178,7 +179,7 @@ impl UIDStore {
msn_index: Default::default(),
byte_cache: Default::default(),
mailboxes: Arc::new(FutureMutex::new(Default::default())),
tag_index: Arc::new(RwLock::new(Default::default())),
collection: Default::default(),
is_online: Arc::new(Mutex::new((
SystemTime::now(),
Err(MeliError::new("Account is uninitialised.")),
@ -710,7 +711,7 @@ impl MailBackend for ImapType {
/* Set flags/tags to true */
let mut set_seen = false;
let command = {
let mut tag_lck = uid_store.tag_index.write().unwrap();
let mut tag_lck = uid_store.collection.tag_index.write().unwrap();
let mut cmd = format!("UID STORE {}", uids[0]);
for uid in uids.iter().skip(1) {
cmd = format!("{},{}", cmd, uid);
@ -859,10 +860,6 @@ impl MailBackend for ImapType {
}))
}
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
Some(self.uid_store.tag_index.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
@ -871,6 +868,10 @@ impl MailBackend for ImapType {
self
}
fn collection(&self) -> Collection {
self.uid_store.collection.clone()
}
fn create_mailbox(
&mut self,
mut path: String,
@ -1773,7 +1774,7 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
}
env.set_references(value);
}
let mut tag_lck = uid_store.tag_index.write().unwrap();
let mut tag_lck = uid_store.collection.tag_index.write().unwrap();
if let Some((flags, keywords)) = flags {
env.set_flags(*flags);
if !env.is_seen() {

View File

@ -239,7 +239,7 @@ mod sqlite3_m {
.entry(mailbox_hash)
.and_modify(|entry| *entry = uidvalidity)
.or_insert(uidvalidity);
let mut tag_lck = self.uid_store.tag_index.write().unwrap();
let mut tag_lck = self.uid_store.collection.tag_index.write().unwrap();
for f in to_str!(&flags).split('\0') {
let hash = tag_hash!(f);
//debug!("hash {} flag {}", hash, &f);

View File

@ -171,7 +171,7 @@ impl ImapConnection {
}
env.set_references(value);
}
let mut tag_lck = self.uid_store.tag_index.write().unwrap();
let mut tag_lck = self.uid_store.collection.tag_index.write().unwrap();
if let Some((flags, keywords)) = flags {
env.set_flags(*flags);
if !env.is_seen() {
@ -469,7 +469,7 @@ impl ImapConnection {
}
env.set_references(value);
}
let mut tag_lck = self.uid_store.tag_index.write().unwrap();
let mut tag_lck = self.uid_store.collection.tag_index.write().unwrap();
if let Some((flags, keywords)) = flags {
env.set_flags(*flags);
if !env.is_seen() {

View File

@ -238,7 +238,7 @@ impl ImapConnection {
}
env.set_references(value);
}
let mut tag_lck = self.uid_store.tag_index.write().unwrap();
let mut tag_lck = self.uid_store.collection.tag_index.write().unwrap();
if let Some((flags, keywords)) = flags {
env.set_flags(*flags);
if !env.is_seen() {
@ -381,7 +381,7 @@ impl ImapConnection {
}
env.set_references(value);
}
let mut tag_lck = self.uid_store.tag_index.write().unwrap();
let mut tag_lck = self.uid_store.collection.tag_index.write().unwrap();
if let Some((flags, keywords)) = flags {
env.set_flags(*flags);
if !env.is_seen() {

View File

@ -388,7 +388,7 @@ pub async fn examine_updates(
}
env.set_references(value);
}
let mut tag_lck = uid_store.tag_index.write().unwrap();
let mut tag_lck = uid_store.collection.tag_index.write().unwrap();
if let Some((flags, keywords)) = flags {
env.set_flags(*flags);
if !env.is_seen() {

View File

@ -23,12 +23,13 @@ use crate::backends::*;
use crate::conf::AccountSettings;
use crate::email::*;
use crate::error::{MeliError, Result};
use crate::Collection;
use futures::lock::Mutex as FutureMutex;
use isahc::config::RedirectPolicy;
use isahc::prelude::HttpClient;
use isahc::ResponseExt;
use serde_json::Value;
use std::collections::{hash_map::DefaultHasher, BTreeMap, HashMap, HashSet};
use std::collections::{hash_map::DefaultHasher, HashMap, HashSet};
use std::convert::TryFrom;
use std::hash::{Hash, Hasher};
use std::str::FromStr;
@ -183,7 +184,7 @@ pub struct Store {
pub id_store: Arc<Mutex<HashMap<EnvelopeHash, Id<EmailObject>>>>,
pub reverse_id_store: Arc<Mutex<HashMap<Id<EmailObject>, EnvelopeHash>>>,
pub blob_id_store: Arc<Mutex<HashMap<EnvelopeHash, Id<BlobObject>>>>,
pub tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
pub collection: Collection,
pub mailboxes: Arc<RwLock<HashMap<MailboxHash, JmapMailbox>>>,
pub mailboxes_index: Arc<RwLock<HashMap<MailboxHash, HashSet<EnvelopeHash>>>>,
pub mailbox_state: Arc<Mutex<State<MailboxObject>>>,
@ -194,7 +195,7 @@ pub struct Store {
impl Store {
pub fn add_envelope(&self, obj: EmailObject) -> Envelope {
let mut tag_lck = self.tag_index.write().unwrap();
let mut tag_lck = self.collection.tag_index.write().unwrap();
let tags = obj
.keywords()
.keys()
@ -483,10 +484,6 @@ impl MailBackend for JmapType {
}))
}
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
Some(self.store.tag_index.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
@ -495,6 +492,10 @@ impl MailBackend for JmapType {
self
}
fn collection(&self) -> Collection {
self.store.collection.clone()
}
fn search(
&self,
q: crate::search::Query,
@ -753,7 +754,7 @@ impl MailBackend for JmapType {
}
{
let mut tag_index_lck = store.tag_index.write().unwrap();
let mut tag_index_lck = store.collection.tag_index.write().unwrap();
for (flag, value) in flags.iter() {
match flag {
Ok(_) => {}
@ -841,12 +842,12 @@ impl JmapType {
online_status,
event_consumer,
is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)),
collection: Collection::default(),
byte_cache: Default::default(),
id_store: Default::default(),
reverse_id_store: Default::default(),
blob_id_store: Default::default(),
tag_index: Default::default(),
mailboxes: Default::default(),
mailboxes_index: Default::default(),
mailbox_state: Default::default(),

View File

@ -30,7 +30,7 @@ use crate::backends::*;
use crate::email::Flag;
use crate::error::{MeliError, Result};
use crate::shellexpand::ShellExpandTrait;
pub use futures::stream::Stream;
use futures::stream::Stream;
use std::collections::hash_map::DefaultHasher;
use std::fs;
use std::hash::{Hash, Hasher};

View File

@ -25,6 +25,7 @@ use crate::conf::AccountSettings;
use crate::email::{Envelope, EnvelopeHash, Flag};
use crate::error::{ErrorKind, MeliError, Result};
use crate::shellexpand::ShellExpandTrait;
use crate::Collection;
use futures::prelude::Stream;
extern crate notify;
@ -109,6 +110,7 @@ pub struct MaildirType {
mailbox_index: Arc<Mutex<HashMap<EnvelopeHash, MailboxHash>>>,
hash_indexes: HashIndexes,
event_consumer: BackendEventConsumer,
collection: Collection,
path: PathBuf,
}
@ -1003,6 +1005,10 @@ impl MailBackend for MaildirType {
}))
}
fn collection(&self) -> Collection {
self.collection.clone()
}
fn create_mailbox(
&mut self,
new_path: String,
@ -1236,6 +1242,7 @@ impl MaildirType {
hash_indexes: Arc::new(Mutex::new(hash_indexes)),
mailbox_index: Default::default(),
event_consumer,
collection: Default::default(),
path: root_path,
}))
}

View File

@ -24,6 +24,7 @@
*/
use crate::backends::*;
use crate::collection::Collection;
use crate::conf::AccountSettings;
use crate::email::parser::BytesExt;
use crate::email::*;
@ -47,6 +48,8 @@ use std::str::FromStr;
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex, RwLock};
pub mod write;
type Offset = usize;
type Length = usize;
@ -271,14 +274,14 @@ impl BackendOp for MboxOp {
}
#[derive(Debug, Clone, Copy)]
pub enum MboxReader {
pub enum MboxFormat {
MboxO,
MboxRd,
MboxCl,
MboxCl2,
}
impl Default for MboxReader {
impl Default for MboxFormat {
fn default() -> Self {
Self::MboxCl2
}
@ -321,8 +324,8 @@ macro_rules! find_From__line {
}};
}
impl MboxReader {
fn parse<'i>(&self, input: &'i [u8]) -> IResult<&'i [u8], Envelope> {
impl MboxFormat {
pub fn parse<'i>(&self, input: &'i [u8]) -> IResult<&'i [u8], Envelope> {
let orig_input = input;
let mut input = input;
match self {
@ -605,7 +608,7 @@ pub fn mbox_parse(
index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>>,
input: &[u8],
file_offset: usize,
reader: Option<MboxReader>,
format: Option<MboxFormat>,
) -> IResult<&[u8], Vec<Envelope>> {
if input.is_empty() {
return Err(nom::Err::Error((input, ErrorKind::Tag)));
@ -614,9 +617,9 @@ pub fn mbox_parse(
let mut index = index.lock().unwrap();
let mut envelopes = Vec::with_capacity(32);
let reader = reader.unwrap_or(MboxReader::MboxCl2);
let format = format.unwrap_or(MboxFormat::MboxCl2);
while !input[offset + file_offset..].is_empty() {
let (next_input, env) = match reader.parse(&input[offset + file_offset..]) {
let (next_input, env) = match format.parse(&input[offset + file_offset..]) {
Ok(v) => v,
Err(e) => {
// Try to recover from this error by finding a new candidate From_ line
@ -648,12 +651,12 @@ pub fn mbox_parse(
Ok((&[], envelopes))
}
struct MessageIterator<'a> {
index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>>,
input: &'a [u8],
file_offset: usize,
offset: usize,
reader: Option<MboxReader>,
pub struct MessageIterator<'a> {
pub index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>>,
pub input: &'a [u8],
pub file_offset: usize,
pub offset: usize,
pub format: Option<MboxFormat>,
}
impl<'a> Iterator for MessageIterator<'a> {
@ -664,10 +667,10 @@ impl<'a> Iterator for MessageIterator<'a> {
}
let mut index = self.index.lock().unwrap();
let reader = self.reader.unwrap_or(MboxReader::MboxCl2);
let format = self.format.unwrap_or(MboxFormat::MboxCl2);
while !self.input[self.offset + self.file_offset..].is_empty() {
let (next_input, env) =
match reader.parse(&self.input[self.offset + self.file_offset..]) {
match format.parse(&self.input[self.offset + self.file_offset..]) {
Ok(v) => v,
Err(e) => {
// Try to recover from this error by finding a new candidate From_ line
@ -708,9 +711,10 @@ impl<'a> Iterator for MessageIterator<'a> {
pub struct MboxType {
account_name: String,
path: PathBuf,
collection: Collection,
mailbox_index: Arc<Mutex<HashMap<EnvelopeHash, MailboxHash>>>,
mailboxes: Arc<Mutex<HashMap<MailboxHash, MboxMailbox>>>,
prefer_mbox_type: Option<MboxReader>,
prefer_mbox_type: Option<MboxFormat>,
event_consumer: BackendEventConsumer,
}
@ -739,7 +743,7 @@ impl MailBackend for MboxType {
mailbox_hash: MailboxHash,
mailbox_index: Arc<Mutex<HashMap<EnvelopeHash, MailboxHash>>>,
mailboxes: Arc<Mutex<HashMap<MailboxHash, MboxMailbox>>>,
prefer_mbox_type: Option<MboxReader>,
prefer_mbox_type: Option<MboxFormat>,
offset: usize,
file_offset: usize,
contents: Vec<u8>,
@ -754,7 +758,7 @@ impl MailBackend for MboxType {
input: &self.contents.as_slice(),
offset: self.offset,
file_offset: self.file_offset,
reader: self.prefer_mbox_type,
format: self.prefer_mbox_type,
};
let mut payload = vec![];
let mut done = false;
@ -1057,6 +1061,10 @@ impl MailBackend for MboxType {
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn collection(&self) -> Collection {
self.collection.clone()
}
}
macro_rules! get_conf_val {
@ -1108,10 +1116,10 @@ impl MboxType {
path,
prefer_mbox_type: match prefer_mbox_type.as_str() {
"auto" => None,
"mboxo" => Some(MboxReader::MboxO),
"mboxrd" => Some(MboxReader::MboxRd),
"mboxcl" => Some(MboxReader::MboxCl),
"mboxcl2" => Some(MboxReader::MboxCl2),
"mboxo" => Some(MboxFormat::MboxO),
"mboxrd" => Some(MboxFormat::MboxRd),
"mboxcl" => Some(MboxFormat::MboxCl),
"mboxcl2" => Some(MboxFormat::MboxCl2),
_ => {
return Err(MeliError::new(format!(
"{} invalid `prefer_mbox_type` value: `{}`",
@ -1120,6 +1128,7 @@ impl MboxType {
)))
}
},
collection: Collection::default(),
mailbox_index: Default::default(),
mailboxes: Default::default(),
};

View File

@ -0,0 +1,112 @@
/*
* meli - mailbox module.
*
* Copyright 2021 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
impl MboxFormat {
pub fn append(
&self,
writer: &mut dyn std::io::Write,
input: &[u8],
envelope_from: Option<&Address>,
delivery_date: Option<crate::UnixTimestamp>,
is_empty: bool,
crlf: bool,
) -> Result<()> {
let line_ending: &'static [u8] = if crlf { &b"\r\n"[..] } else { &b"\n"[..] };
if !is_empty {
writer.write_all(line_ending)?;
writer.write_all(line_ending)?;
}
writer.write_all(&b"From "[..])?;
if let Some(from) = envelope_from {
writer.write_all(from.address_spec_raw())?;
} else {
writer.write_all(&b"MAILER-DAEMON"[..])?;
}
writer.write_all(&b" "[..])?;
writer.write_all(
crate::datetime::timestamp_to_string(
delivery_date.unwrap_or_else(|| crate::datetime::now()),
Some(crate::datetime::ASCTIME_FMT),
true,
)
.trim()
.as_bytes(),
)?;
writer.write_all(line_ending)?;
let (mut headers, body) = parser::mail(input)?;
match self {
MboxFormat::MboxO | MboxFormat::MboxRd => Err(MeliError::new("Unimplemented.")),
MboxFormat::MboxCl => {
headers.retain(|(header_name, _)| {
!header_name.eq_ignore_ascii_case(b"Content-Length")
});
let len = (body.len()
+ body
.windows(b"\nFrom ".len())
.filter(|w| w == b"\nFrom ")
.count()
+ if body.starts_with(b"From ") { 1 } else { 0 })
.to_string();
for (h, v) in headers
.into_iter()
.chain(Some((&b"Content-Length"[..], len.as_bytes())))
{
writer.write_all(h)?;
writer.write_all(&b": "[..])?;
writer.write_all(v)?;
writer.write_all(line_ending)?;
}
writer.write_all(line_ending)?;
if body.starts_with(b"From ") {
writer.write_all(&[b'>'])?;
}
for i in 0..body.len() {
writer.write_all(&[body[i]])?;
if body[i..].starts_with(b"\nFrom ") {
writer.write_all(&[b'>'])?;
}
}
Ok(())
}
MboxFormat::MboxCl2 => {
headers.retain(|(header_name, _)| {
!header_name.eq_ignore_ascii_case(b"Content-Length")
});
let len = body.len().to_string();
for (h, v) in headers
.into_iter()
.chain(Some((&b"Content-Length"[..], len.as_bytes())))
{
writer.write_all(h)?;
writer.write_all(&b": "[..])?;
writer.write_all(v)?;
writer.write_all(line_ending)?;
}
writer.write_all(line_ending)?;
writer.write_all(body)?;
Ok(())
}
}
}
}

View File

@ -32,18 +32,18 @@ pub use operations::*;
mod connection;
pub use connection::*;
use crate::backends::*;
use crate::conf::AccountSettings;
use crate::email::*;
use crate::error::{MeliError, Result, ResultIntoMeliError};
use crate::{backends::*, Collection};
use futures::lock::Mutex as FutureMutex;
use futures::stream::Stream;
use std::collections::{hash_map::DefaultHasher, BTreeMap, BTreeSet, HashMap, HashSet};
use std::collections::{hash_map::DefaultHasher, BTreeSet, HashMap, HashSet};
use std::future::Future;
use std::hash::Hasher;
use std::pin::Pin;
use std::str::FromStr;
use std::sync::{Arc, Mutex, RwLock};
use std::sync::{Arc, Mutex};
use std::time::Instant;
pub type UID = usize;
@ -77,6 +77,7 @@ pub struct UIDStore {
hash_index: Arc<Mutex<HashMap<EnvelopeHash, (UID, MailboxHash)>>>,
uid_index: Arc<Mutex<HashMap<(MailboxHash, UID), EnvelopeHash>>>,
collection: Collection,
mailboxes: Arc<FutureMutex<HashMap<MailboxHash, NntpMailbox>>>,
is_online: Arc<Mutex<(Instant, Result<()>)>>,
event_consumer: BackendEventConsumer,
@ -97,6 +98,7 @@ impl UIDStore {
hash_index: Default::default(),
uid_index: Default::default(),
mailboxes: Arc::new(FutureMutex::new(Default::default())),
collection: Collection::new(),
is_online: Arc::new(Mutex::new((
Instant::now(),
Err(MeliError::new("Account is uninitialised.")),
@ -294,10 +296,6 @@ impl MailBackend for NntpType {
Err(MeliError::new("NNTP doesn't support deletion."))
}
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
None
}
fn as_any(&self) -> &dyn Any {
self
}
@ -306,6 +304,10 @@ impl MailBackend for NntpType {
self
}
fn collection(&self) -> Collection {
self.uid_store.collection.clone()
}
fn create_mailbox(
&mut self,
_path: String,

View File

@ -19,11 +19,11 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::backends::*;
use crate::conf::AccountSettings;
use crate::email::{Envelope, EnvelopeHash, Flag};
use crate::error::{MeliError, Result};
use crate::shellexpand::ShellExpandTrait;
use crate::{backends::*, Collection};
use smallvec::SmallVec;
use std::collections::{
hash_map::{DefaultHasher, HashMap},
@ -220,7 +220,7 @@ pub struct NotmuchDb {
mailboxes: Arc<RwLock<HashMap<MailboxHash, NotmuchMailbox>>>,
index: Arc<RwLock<HashMap<EnvelopeHash, CString>>>,
mailbox_index: Arc<RwLock<HashMap<EnvelopeHash, SmallVec<[MailboxHash; 16]>>>>,
tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
collection: Collection,
path: PathBuf,
account_name: Arc<String>,
account_hash: AccountHash,
@ -358,7 +358,7 @@ impl NotmuchDb {
path,
index: Arc::new(RwLock::new(Default::default())),
mailbox_index: Arc::new(RwLock::new(Default::default())),
tag_index: Arc::new(RwLock::new(Default::default())),
collection: Collection::default(),
mailboxes: Arc::new(RwLock::new(mailboxes)),
save_messages_to: None,
@ -510,7 +510,7 @@ impl MailBackend for NotmuchDb {
)?);
let index = self.index.clone();
let mailbox_index = self.mailbox_index.clone();
let tag_index = self.tag_index.clone();
let tag_index = self.collection.tag_index.clone();
let mailboxes = self.mailboxes.clone();
let v: Vec<CString>;
{
@ -561,7 +561,7 @@ impl MailBackend for NotmuchDb {
let mailboxes = self.mailboxes.clone();
let index = self.index.clone();
let mailbox_index = self.mailbox_index.clone();
let tag_index = self.tag_index.clone();
let tag_index = self.collection.tag_index.clone();
let event_consumer = self.event_consumer.clone();
Ok(Box::pin(async move {
let new_revision_uuid = database.get_revision_uuid();
@ -586,13 +586,13 @@ impl MailBackend for NotmuchDb {
use notify::{watcher, RecursiveMode, Watcher};
let account_hash = self.account_hash;
let collection = self.collection.clone();
let lib = self.lib.clone();
let path = self.path.clone();
let revision_uuid = self.revision_uuid.clone();
let mailboxes = self.mailboxes.clone();
let index = self.index.clone();
let mailbox_index = self.mailbox_index.clone();
let tag_index = self.tag_index.clone();
let event_consumer = self.event_consumer.clone();
let (tx, rx) = std::sync::mpsc::channel();
@ -616,7 +616,7 @@ impl MailBackend for NotmuchDb {
mailboxes.clone(),
index.clone(),
mailbox_index.clone(),
tag_index.clone(),
collection.tag_index.clone(),
account_hash.clone(),
event_consumer.clone(),
new_revision_uuid,
@ -651,7 +651,7 @@ impl MailBackend for NotmuchDb {
hash,
index: self.index.clone(),
bytes: None,
tag_index: self.tag_index.clone(),
collection: self.collection.clone(),
}))
}
@ -693,7 +693,7 @@ impl MailBackend for NotmuchDb {
self.lib.clone(),
true,
)?;
let tag_index = self.tag_index.clone();
let collection = self.collection.clone();
let index = self.index.clone();
Ok(Box::pin(async move {
@ -781,7 +781,11 @@ impl MailBackend for NotmuchDb {
for (f, v) in flags.iter() {
if let (Err(tag), true) = (f, v) {
let hash = tag_hash!(tag);
tag_index.write().unwrap().insert(hash, tag.to_string());
collection
.tag_index
.write()
.unwrap()
.insert(hash, tag.to_string());
}
}
@ -834,8 +838,8 @@ impl MailBackend for NotmuchDb {
}))
}
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
Some(self.tag_index.clone())
fn collection(&self) -> Collection {
self.collection.clone()
}
fn as_any(&self) -> &dyn Any {
@ -851,7 +855,7 @@ impl MailBackend for NotmuchDb {
struct NotmuchOp {
hash: EnvelopeHash,
index: Arc<RwLock<HashMap<EnvelopeHash, CString>>>,
tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
collection: Collection,
database: Arc<DbConnection>,
bytes: Option<Vec<u8>>,
lib: Arc<libloading::Library>,

View File

@ -25,7 +25,7 @@ use smallvec::SmallVec;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::collections::{HashMap, HashSet};
use std::collections::{BTreeMap, HashMap, HashSet};
pub struct EnvelopeRef<'g> {
guard: RwLockReadGuard<'g, HashMap<EnvelopeHash, Envelope>>,
@ -64,8 +64,9 @@ pub struct Collection {
pub envelopes: Arc<RwLock<HashMap<EnvelopeHash, Envelope>>>,
pub message_id_index: Arc<RwLock<HashMap<Vec<u8>, EnvelopeHash>>>,
pub threads: Arc<RwLock<HashMap<MailboxHash, Threads>>>,
sent_mailbox: Arc<RwLock<Option<MailboxHash>>>,
pub sent_mailbox: Arc<RwLock<Option<MailboxHash>>>,
pub mailboxes: Arc<RwLock<HashMap<MailboxHash, HashSet<EnvelopeHash>>>>,
pub tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
}
impl Default for Collection {
@ -115,6 +116,7 @@ impl Collection {
Collection {
envelopes: Arc::new(RwLock::new(Default::default())),
tag_index: Arc::new(RwLock::new(BTreeMap::default())),
message_id_index,
threads,
mailboxes,

View File

@ -48,6 +48,8 @@ pub const RFC3339_FMT: &str = "%Y-%m-%d\0";
pub const RFC822_FMT_WITH_TIME: &str = "%a, %e %h %Y %H:%M:%S \0";
pub const RFC822_FMT: &str = "%e %h %Y %H:%M:%S \0";
pub const DEFAULT_FMT: &str = "%a, %d %b %Y %R\0";
//"Tue May 21 13:46:22 1991\n"
pub const ASCTIME_FMT: &str = "%a %b %d %H:%M:%S %Y\n\0";
extern "C" {
fn strptime(

View File

@ -44,7 +44,6 @@ pub use iterators::*;
use crate::text_processing::grapheme_clusters::*;
use uuid::Uuid;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::fmt;
@ -131,7 +130,7 @@ macro_rules! make {
e.parent = Some($p);
});
let old_group = std::mem::replace($threads.groups.entry(old_group_hash).or_default(), ThreadGroup::Node {
parent: RefCell::new(parent_group_hash),
parent: Arc::new(RwLock::new(parent_group_hash)),
});
$threads.thread_nodes.entry($c).and_modify(|e| {
e.group = parent_group_hash;
@ -292,7 +291,7 @@ pub struct Thread {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum ThreadGroup {
Root(Thread),
Node { parent: RefCell<ThreadHash> },
Node { parent: Arc<RwLock<ThreadHash>> },
}
impl Default for ThreadGroup {
@ -411,16 +410,16 @@ impl ThreadNode {
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Threads {
pub thread_nodes: HashMap<ThreadNodeHash, ThreadNode>,
root_set: RefCell<Vec<ThreadNodeHash>>,
tree_index: RefCell<Vec<ThreadNodeHash>>,
root_set: Arc<RwLock<Vec<ThreadNodeHash>>>,
tree_index: Arc<RwLock<Vec<ThreadNodeHash>>>,
pub groups: HashMap<ThreadHash, ThreadGroup>,
message_ids: HashMap<Vec<u8>, ThreadNodeHash>,
pub message_ids_set: HashSet<Vec<u8>>,
pub missing_message_ids: HashSet<Vec<u8>>,
pub hash_set: HashSet<EnvelopeHash>,
sort: RefCell<(SortField, SortOrder)>,
subsort: RefCell<(SortField, SortOrder)>,
sort: Arc<RwLock<(SortField, SortOrder)>>,
subsort: Arc<RwLock<(SortField, SortOrder)>>,
}
impl PartialEq for ThreadNode {
@ -454,13 +453,13 @@ impl Threads {
pub fn find_group(&self, h: ThreadHash) -> ThreadHash {
let p = match self.groups[&h] {
ThreadGroup::Root(_) => return h,
ThreadGroup::Node { ref parent } => *parent.borrow(),
ThreadGroup::Node { ref parent } => *parent.read().unwrap(),
};
let parent_group = self.find_group(p);
match self.groups[&h] {
ThreadGroup::Node { ref parent } => {
*parent.borrow_mut() = parent_group;
*parent.write().unwrap() = parent_group;
}
_ => unreachable!(),
}
@ -491,8 +490,8 @@ impl Threads {
message_ids_set,
missing_message_ids,
hash_set,
sort: RefCell::new((SortField::Date, SortOrder::Desc)),
subsort: RefCell::new((SortField::Subject, SortOrder::Desc)),
sort: Arc::new(RwLock::new((SortField::Date, SortOrder::Desc))),
subsort: Arc::new(RwLock::new((SortField::Subject, SortOrder::Desc))),
..Default::default()
}
@ -573,7 +572,7 @@ impl Threads {
};
if self.thread_nodes[&t_id].parent.is_none() {
let mut tree_index = self.tree_index.borrow_mut();
let mut tree_index = self.tree_index.write().unwrap();
if let Some(i) = tree_index.iter().position(|t| *t == t_id) {
tree_index.remove(i);
}
@ -845,7 +844,7 @@ impl Threads {
/*
save_graph(
&self.tree_index.borrow(),
&self.tree_index.read().unwrap(),
&self.thread_nodes,
&self
.message_ids
@ -871,7 +870,7 @@ impl Threads {
ref thread_nodes,
..
} = self;
let tree = &mut tree_index.borrow_mut();
let tree = &mut tree_index.write().unwrap();
for t in tree.iter_mut() {
thread_nodes[t].children.sort_by(|a, b| match subsort {
(SortField::Date, SortOrder::Desc) => {
@ -1090,7 +1089,7 @@ impl Threads {
});
}
fn inner_sort_by(&self, sort: (SortField, SortOrder), envelopes: &Envelopes) {
let tree = &mut self.tree_index.borrow_mut();
let tree = &mut self.tree_index.write().unwrap();
let envelopes = envelopes.read().unwrap();
tree.sort_by(|a, b| match sort {
(SortField::Date, SortOrder::Desc) => {
@ -1172,13 +1171,13 @@ impl Threads {
subsort: (SortField, SortOrder),
envelopes: &Envelopes,
) {
if *self.sort.borrow() != sort {
if *self.sort.read().unwrap() != sort {
self.inner_sort_by(sort, envelopes);
*self.sort.borrow_mut() = sort;
*self.sort.write().unwrap() = sort;
}
if *self.subsort.borrow() != subsort {
if *self.subsort.read().unwrap() != subsort {
self.inner_subsort_by(subsort, envelopes);
*self.subsort.borrow_mut() = subsort;
*self.subsort.write().unwrap() = subsort;
}
}
@ -1196,11 +1195,11 @@ impl Threads {
}
pub fn root_len(&self) -> usize {
self.tree_index.borrow().len()
self.tree_index.read().unwrap().len()
}
pub fn root_set(&self, idx: usize) -> ThreadNodeHash {
self.tree_index.borrow()[idx]
self.tree_index.read().unwrap()[idx]
}
pub fn roots(&self) -> SmallVec<[ThreadHash; 1024]> {

View File

@ -419,6 +419,19 @@ define_commands!([
}
)
},
{ tags: ["export-mbox "],
desc: "export-mbox PATH",
tokens: &[One(Literal("export-mbox")), One(Filepath)],
parser:(
fn export_mbox(input: &[u8]) -> IResult<&[u8], Action> {
let (input, _) = tag("export-mbox")(input.trim())?;
let (input, _) = is_a(" ")(input)?;
let (input, path) = quoted_argument(input.trim())?;
let (input, _) = eof(input)?;
Ok((input, Listing(ExportMbox(Some(melib::backends::mbox::MboxFormat::MboxCl2), path.to_string().into()))))
}
)
},
{ tags: ["list-archive", "list-post", "list-unsubscribe", "list-"],
desc: "list-[unsubscribe/post/archive]",
tokens: &[One(Alternatives(&[to_stream!(One(Literal("list-archive"))), to_stream!(One(Literal("list-post"))), to_stream!(One(Literal("list-unsubscribe")))]))],
@ -852,6 +865,7 @@ fn listing_action(input: &[u8]) -> IResult<&[u8], Action> {
select,
toggle_thread_snooze,
open_in_new_tab,
export_mbox,
_tag,
))(input)
}

View File

@ -51,6 +51,7 @@ pub enum ListingAction {
MoveTo(MailboxPath),
MoveToOtherAccount(AccountName, MailboxPath),
Import(PathBuf, MailboxPath),
ExportMbox(Option<melib::backends::mbox::MboxFormat>, PathBuf),
Delete,
OpenInNewTab,
Tag(TagAction),

View File

@ -31,6 +31,7 @@ use indexmap::IndexSet;
use nix::sys::wait::WaitStatus;
use std::convert::TryInto;
use std::future::Future;
use std::pin::Pin;
use std::process::{Command, Stdio};
use std::str::FromStr;
use std::sync::{Arc, Mutex};

View File

@ -342,6 +342,87 @@ pub trait MailListingTrait: ListingTrait {
}
}
}
ListingAction::ExportMbox(format, ref path) => {
use futures::future::try_join_all;
use std::future::Future;
use std::io::Write;
use std::pin::Pin;
let futures: Result<Vec<_>> = envs_to_set
.iter()
.map(|&env_hash| account.operation(env_hash).and_then(|mut op| op.as_bytes()))
.collect::<Result<Vec<_>>>();
let path_ = path.to_path_buf();
let format = format.clone().unwrap_or_default();
let collection = account.collection.clone();
let (sender, mut receiver) = crate::jobs::oneshot::channel();
let fut: Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>> =
Box::pin(async move {
let cl = async move {
let bytes: Vec<Vec<u8>> = try_join_all(futures?).await?;
let envs: Vec<_> = envs_to_set
.iter()
.map(|&env_hash| collection.get_env(env_hash))
.collect();
let mut file = std::io::BufWriter::new(std::fs::File::create(&path_)?);
let mut iter = envs.iter().zip(bytes.into_iter());
if let Some((env, ref bytes)) = iter.next() {
format.append(
&mut file,
bytes.as_slice(),
env.from().get(0),
Some(env.date()),
true,
false,
)?;
}
for (env, bytes) in iter {
format.append(
&mut file,
bytes.as_slice(),
env.from().get(0),
Some(env.date()),
false,
false,
)?;
}
file.flush()?;
Ok(())
};
let r: Result<()> = cl.await;
let _ = sender.send(r);
Ok(())
});
let handle = account.job_executor.spawn_blocking(fut);
let path = path.to_path_buf();
account.insert_job(
handle.job_id,
JobRequest::Generic {
name: "exporting mbox".into(),
handle,
on_finish: Some(CallbackFn(Box::new(move |context: &mut Context| {
context.replies.push_back(match receiver.try_recv() {
Err(_) | Ok(None) => UIEvent::Notification(
Some("Could not export mbox".to_string()),
"Job was canceled.".to_string(),
Some(NotificationType::Info),
),
Ok(Some(Err(err))) => UIEvent::Notification(
Some("Could not export mbox".to_string()),
err.to_string(),
Some(NotificationType::Error(err.kind)),
),
Ok(Some(Ok(()))) => UIEvent::Notification(
Some("Succesfully exported mbox".to_string()),
format!("Wrote to file {}", path.display()),
Some(NotificationType::Info),
),
});
}))),
logging_level: melib::LoggingLevel::INFO,
},
);
}
ListingAction::MoveToOtherAccount(ref _account_name, ref _mailbox_path) => {
context
.replies
@ -967,6 +1048,7 @@ impl Component for Listing {
| Action::Listing(a @ ListingAction::MoveTo(_))
| Action::Listing(a @ ListingAction::CopyToOtherAccount(_, _))
| Action::Listing(a @ ListingAction::MoveToOtherAccount(_, _))
| Action::Listing(a @ ListingAction::ExportMbox(_, _))
| Action::Listing(a @ ListingAction::Tag(_)) => {
let focused = self.component.get_focused_items(context);
self.component.perform_action(context, focused, a);

View File

@ -895,9 +895,9 @@ impl CompactListing {
let thread = threads.thread_ref(hash);
let mut tags = String::new();
let mut colors: SmallVec<[_; 8]> = SmallVec::new();
let backend_lck = context.accounts[&self.cursor_pos.0].backend.read().unwrap();
if let Some(t) = backend_lck.tags() {
let tags_lck = t.read().unwrap();
let account = &context.accounts[&self.cursor_pos.0];
if account.backend_capabilities.supports_tags {
let tags_lck = account.collection.tag_index.read().unwrap();
for t in e.labels().iter() {
if mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]

View File

@ -908,9 +908,9 @@ impl ConversationsListing {
let thread = threads.thread_ref(hash);
let mut tags = String::new();
let mut colors = SmallVec::new();
let backend_lck = context.accounts[&self.cursor_pos.0].backend.read().unwrap();
if let Some(t) = backend_lck.tags() {
let tags_lck = t.read().unwrap();
let account = &context.accounts[&self.cursor_pos.0];
if account.backend_capabilities.supports_tags {
let tags_lck = account.collection.tag_index.read().unwrap();
for t in e.labels().iter() {
if mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]

View File

@ -732,9 +732,9 @@ impl PlainListing {
fn make_entry_string(&self, e: EnvelopeRef, context: &Context) -> EntryStrings {
let mut tags = String::new();
let mut colors = SmallVec::new();
let backend_lck = context.accounts[&self.cursor_pos.0].backend.read().unwrap();
if let Some(t) = backend_lck.tags() {
let tags_lck = t.read().unwrap();
let account = &context.accounts[&self.cursor_pos.0];
if account.backend_capabilities.supports_tags {
let tags_lck = account.collection.tag_index.read().unwrap();
for t in e.labels().iter() {
if mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]

View File

@ -839,9 +839,9 @@ impl ThreadListing {
fn make_entry_string(&self, e: &Envelope, context: &Context) -> EntryStrings {
let mut tags = String::new();
let mut colors: SmallVec<[_; 8]> = SmallVec::new();
let backend_lck = context.accounts[&self.cursor_pos.0].backend.read().unwrap();
if let Some(t) = backend_lck.tags() {
let tags_lck = t.read().unwrap();
let account = &context.accounts[&self.cursor_pos.0];
if account.backend_capabilities.supports_tags {
let tags_lck = account.collection.tag_index.read().unwrap();
for t in e.labels().iter() {
if mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]

View File

@ -40,9 +40,10 @@ use std::collections::{HashMap, HashSet};
use crate::types::UIEvent::{self, EnvelopeRemove, EnvelopeRename, EnvelopeUpdate, Notification};
use crate::{StatusEvent, ThreadEvent};
use crossbeam::Sender;
use futures::future::FutureExt;
pub use futures::stream::Stream;
use futures::stream::StreamExt;
use futures::{
future::FutureExt,
stream::{Stream, StreamExt},
};
use std::borrow::Cow;
use std::collections::VecDeque;
use std::convert::TryFrom;
@ -517,7 +518,7 @@ impl Account {
tree: Default::default(),
address_book,
sent_mailbox: Default::default(),
collection: Default::default(),
collection: backend.collection(),
settings,
sender,
job_executor,

View File

@ -46,7 +46,7 @@ pub struct PluginBackend {
plugin: Plugin,
child: std::process::Child,
channel: Arc<Mutex<RpcChannel>>,
tag_index: Option<Arc<RwLock<BTreeMap<u64, String>>>>,
collection: melib::Collection,
is_online: Arc<Mutex<(std::time::Instant, Result<()>)>>,
}
@ -200,7 +200,6 @@ impl MailBackend for PluginBackend {
Ok(Box::new(PluginOp {
hash,
channel: self.channel.clone(),
tag_index: self.tag_index.clone(),
bytes: None,
}))
}
@ -219,8 +218,8 @@ impl MailBackend for PluginBackend {
) -> ResultFuture<(MailboxHash, HashMap<MailboxHash, Mailbox>)> {
Err(MeliError::new("Unimplemented."))
}
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
self.tag_index.clone()
fn collection(&self) -> melib::Collection {
self.collection.clone()
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
@ -257,7 +256,7 @@ impl PluginBackend {
child,
plugin,
channel: Arc::new(Mutex::new(channel)),
tag_index: None,
collection: Default::default(),
is_online: Arc::new(Mutex::new((now, Err(MeliError::new("Unitialized"))))),
}))
}
@ -285,7 +284,6 @@ impl PluginBackend {
struct PluginOp {
hash: EnvelopeHash,
channel: Arc<Mutex<RpcChannel>>,
tag_index: Option<Arc<RwLock<BTreeMap<u64, String>>>>,
bytes: Option<String>,
}