Browse Source

Add copy/move to other account operations

tags/alpha-0.6.0
Manos Pitsidianakis 1 year ago
parent
commit
f3d5edfe14
Signed by untrusted user: epilys GPG Key ID: 73627C2F690DF710
  1. 4
      melib/src/backends.rs
  2. 23
      melib/src/backends/imap.rs
  3. 4
      melib/src/backends/jmap.rs
  4. 13
      melib/src/backends/maildir/backend.rs
  5. 2
      melib/src/backends/mbox.rs
  6. 2
      melib/src/backends/notmuch.rs
  7. 97
      src/components/mail/listing.rs
  8. 78
      src/conf/accounts.rs
  9. 67
      src/execute.rs
  10. 3
      src/execute/actions.rs
  11. 2
      src/plugins/backend.rs
  12. 1
      src/state.rs

4
melib/src/backends.rs

@ -299,8 +299,8 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
fn mailboxes(&self) -> Result<HashMap<MailboxHash, Mailbox>>;
fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp>;
fn save(&self, bytes: &[u8], mailbox: &str, flags: Option<Flag>) -> Result<()>;
fn delete(&self, _env_hash: EnvelopeHash) -> Result<()> {
fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()>;
fn delete(&self, _env_hash: EnvelopeHash, _mailbox_hash: MailboxHash) -> Result<()> {
Err(MeliError::new("Unimplemented."))
}
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {

23
melib/src/backends/imap.rs

@ -574,29 +574,22 @@ impl MailBackend for ImapType {
))
}
fn save(&self, bytes: &[u8], mailbox: &str, flags: Option<Flag>) -> Result<()> {
fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()> {
let path = {
let mailboxes = self.uid_store.mailboxes.read().unwrap();
let f_result = mailboxes
.values()
.find(|v| v.path == mailbox || v.name == mailbox);
if f_result
.map(|f| !f.permissions.lock().unwrap().create_messages)
.unwrap_or(false)
{
let mailbox = mailboxes.get(&mailbox_hash).ok_or(MeliError::new(format!(
"Mailbox with hash {} not found.",
mailbox_hash
)))?;
if !mailbox.permissions.lock().unwrap().create_messages {
return Err(MeliError::new(format!(
"You are not allowed to create messages in mailbox {}",
mailbox
mailbox.path()
)));
}
f_result
.map(|v| v.imap_path().to_string())
.ok_or(MeliError::new(format!(
"Mailbox with name {} not found.",
mailbox
)))?
mailbox.imap_path().to_string()
};
let mut response = String::with_capacity(8 * 1024);
let mut conn = try_lock(&self.connection, Some(std::time::Duration::new(5, 0)))?;

4
melib/src/backends/jmap.rs

@ -262,8 +262,8 @@ impl MailBackend for JmapType {
))
}
fn save(&self, _bytes: &[u8], _mailbox: &str, _flags: Option<Flag>) -> Result<()> {
Ok(())
fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option<Flag>) -> Result<()> {
Err(MeliError::new("Unimplemented."))
}
fn as_any(&self) -> &dyn::std::any::Any {

13
melib/src/backends/maildir/backend.rs

@ -656,17 +656,8 @@ impl MailBackend for MaildirType {
))
}
fn save(&self, bytes: &[u8], mailbox: &str, flags: Option<Flag>) -> Result<()> {
for f in self.mailboxes.values() {
if f.name == mailbox || f.path.to_str().unwrap() == mailbox {
return MaildirType::save_to_mailbox(f.fs_path.clone(), bytes, flags);
}
}
Err(MeliError::new(format!(
"'{}' is not a valid mailbox.",
mailbox
)))
fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()> {
MaildirType::save_to_mailbox(self.mailboxes[&mailbox_hash].fs_path.clone(), bytes, flags)
}
fn as_any(&self) -> &dyn::std::any::Any {

2
melib/src/backends/mbox.rs

@ -612,7 +612,7 @@ impl MailBackend for MboxType {
Box::new(MboxOp::new(hash, self.path.as_path(), offset, length))
}
fn save(&self, _bytes: &[u8], _mailbox: &str, _flags: Option<Flag>) -> Result<()> {
fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option<Flag>) -> Result<()> {
Err(MeliError::new("Unimplemented."))
}

2
melib/src/backends/notmuch.rs

@ -612,7 +612,7 @@ impl MailBackend for NotmuchDb {
})
}
fn save(&self, bytes: &[u8], _mailbox: &str, flags: Option<Flag>) -> Result<()> {
fn save(&self, bytes: &[u8], _mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()> {
let path = self
.save_messages_to
.as_ref()

97
src/components/mail/listing.rs

@ -143,7 +143,8 @@ pub trait MailListingTrait: ListingTrait {
thread_hash: ThreadHash,
a: &ListingAction,
) {
let account = &mut context.accounts[self.coordinates().0];
let account_pos = self.coordinates().0;
let account = &mut context.accounts[account_pos];
let mut envs_to_set: SmallVec<[EnvelopeHash; 8]> = SmallVec::new();
let mailbox_hash = self.coordinates().1;
for (_, h) in account.collection.threads[&mailbox_hash].thread_group_iter(thread_hash) {
@ -154,6 +155,7 @@ pub trait MailListingTrait: ListingTrait {
);
}
for env_hash in envs_to_set {
let account = &mut context.accounts[self.coordinates().0];
let mut op = account.operation(env_hash);
let mut envelope: EnvelopeRefMut = account.collection.get_env_mut(env_hash);
match a {
@ -173,7 +175,7 @@ pub trait MailListingTrait: ListingTrait {
}
ListingAction::Delete => {
drop(envelope);
if let Err(err) = account.delete(env_hash) {
if let Err(err) = account.delete(env_hash, mailbox_hash) {
context.replies.push_back(UIEvent::Notification(
Some("Could not delete.".to_string()),
err.to_string(),
@ -185,12 +187,99 @@ pub trait MailListingTrait: ListingTrait {
}
ListingAction::CopyTo(ref mailbox_path) => {
drop(envelope);
if let Err(err) =
account
.mailbox_by_path(mailbox_path)
.and_then(|mailbox_hash| {
op.as_bytes()
.and_then(|bytes| account.save(bytes, mailbox_hash, None))
})
{
context.replies.push_back(UIEvent::Notification(
Some("Could not copy.".to_string()),
err.to_string(),
Some(NotificationType::ERROR),
));
return;
}
continue;
}
ListingAction::CopyToOtherAccount(ref account_name, ref mailbox_path) => {
drop(envelope);
if let Err(err) = op.as_bytes().map(|b| b.to_vec()).and_then(|bytes| {
let account_pos = context
.accounts
.iter()
.position(|el| el.name() == account_name)
.ok_or_else(|| {
MeliError::new(format!(
"`{}` is not a valid account name",
account_name
))
})?;
let account = &mut context.accounts[account_pos];
let mailbox_hash = account.mailbox_by_path(mailbox_path)?;
account.save(&bytes, mailbox_hash, None)
}) {
context.replies.push_back(UIEvent::Notification(
Some("Could not copy.".to_string()),
err.to_string(),
Some(NotificationType::ERROR),
));
return;
}
continue;
}
ListingAction::MoveTo(ref mailbox_path) => {
drop(envelope);
if let Err(err) =
account
.mailbox_by_path(mailbox_path)
.and_then(|mailbox_hash| {
op.as_bytes()
.and_then(|bytes| account.save(bytes, mailbox_hash, None))
})
{
context.replies.push_back(UIEvent::Notification(
Some("Could not copy.".to_string()),
err.to_string(),
Some(NotificationType::ERROR),
));
return;
}
continue;
}
ListingAction::MoveToOtherAccount(ref account_name, ref mailbox_path) => {
drop(envelope);
if let Err(err) = op
.as_bytes()
.and_then(|bytes| account.save(bytes, mailbox_path, None))
.map(|b| b.to_vec())
.and_then(|bytes| {
let account_pos = context
.accounts
.iter()
.position(|el| el.name() == account_name)
.ok_or_else(|| {
MeliError::new(format!(
"`{}` is not a valid account name",
account_name
))
})?;
let account = &mut context.accounts[account_pos];
let mailbox_hash = account.mailbox_by_path(mailbox_path)?;
account.save(&bytes, mailbox_hash, None)
})
.and_then(|()| {
let account = &mut context.accounts[account_pos];
account
.delete(env_hash, mailbox_hash)
.chain_err_summary(|| {
"Envelope was copied but removal from original account failed"
})
})
{
context.replies.push_back(UIEvent::Notification(
Some("Could not copy.".to_string()),
Some("Could not move.".to_string()),
err.to_string(),
Some(NotificationType::ERROR),
));

78
src/conf/accounts.rs

@ -26,8 +26,8 @@
use super::{AccountConf, FileMailboxConf};
use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
use melib::backends::{
BackendOp, Backends, MailBackend, Mailbox, MailboxHash, NotifyFn, ReadOnlyOp, RefreshEvent,
RefreshEventConsumer, RefreshEventKind, SpecialUsageMailbox,
AccountHash, BackendOp, Backends, MailBackend, Mailbox, MailboxHash, NotifyFn, ReadOnlyOp,
RefreshEvent, RefreshEventConsumer, RefreshEventKind, SpecialUsageMailbox,
};
use melib::email::*;
use melib::error::{MeliError, Result};
@ -113,6 +113,7 @@ impl MailboxEntry {
pub struct Account {
pub index: usize,
name: String,
hash: AccountHash,
pub is_online: bool,
pub(crate) mailbox_entries: HashMap<MailboxHash, MailboxEntry>,
pub(crate) mailboxes_order: Vec<MailboxHash>,
@ -187,6 +188,7 @@ pub struct MailboxNode {
impl Account {
pub fn new(
index: usize,
hash: AccountHash,
name: String,
mut settings: AccountConf,
map: &Backends,
@ -232,6 +234,7 @@ impl Account {
Ok(Account {
index,
hash,
name,
is_online: false,
mailbox_entries: Default::default(),
@ -931,24 +934,27 @@ impl Account {
Ok(())
}
pub fn save(&self, bytes: &[u8], mailbox: &str, flags: Option<Flag>) -> Result<()> {
pub fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()> {
if self.settings.account.read_only() {
return Err(MeliError::new(format!(
"Account {} is read-only.",
self.name.as_str()
)));
}
self.backend.write().unwrap().save(bytes, mailbox, flags)
self.backend
.write()
.unwrap()
.save(bytes, mailbox_hash, flags)
}
pub fn delete(&self, env_hash: EnvelopeHash) -> Result<()> {
pub fn delete(&self, env_hash: EnvelopeHash, mailbox_hash: MailboxHash) -> Result<()> {
if self.settings.account.read_only() {
return Err(MeliError::new(format!(
"Account {} is read-only.",
self.name.as_str()
)));
}
self.backend.write().unwrap().delete(env_hash)
self.backend.write().unwrap().delete(env_hash, mailbox_hash)
}
pub fn contains_key(&self, h: EnvelopeHash) -> bool {
@ -1044,15 +1050,7 @@ impl Account {
if self.mailbox_entries.len() == 1 {
return Err(MeliError::new("Cannot delete only mailbox."));
}
let mailbox_hash = if let Some((mailbox_hash, _)) = self
.mailbox_entries
.iter()
.find(|(_, f)| f.ref_mailbox.path() == path)
{
*mailbox_hash
} else {
return Err(MeliError::new("Mailbox with that path not found."));
};
let mailbox_hash = self.mailbox_by_path(&path)?;
let mut mailboxes = self.backend.write().unwrap().delete_mailbox(mailbox_hash)?;
self.sender
.send(ThreadEvent::UIEvent(UIEvent::MailboxDelete((
@ -1070,14 +1068,9 @@ impl Account {
self.sent_mailbox = None;
}
self.collection.threads.remove(&mailbox_hash);
let deleted_mailbox = self.mailbox_entries.remove(&mailbox_hash).unwrap();
/* if deleted mailbox had parent, we need to update its children field */
if let Some(parent_hash) = self
.mailbox_entries
.remove(&mailbox_hash)
.unwrap()
.ref_mailbox
.parent()
{
if let Some(parent_hash) = deleted_mailbox.ref_mailbox.parent() {
self.mailbox_entries
.entry(parent_hash)
.and_modify(|parent| {
@ -1094,18 +1087,13 @@ impl Account {
// FIXME remove from settings as well
Ok(format!("'`{}` has been deleted.", &path))
Ok(format!(
"'`{}` has been deleted.",
&deleted_mailbox.ref_mailbox.path()
))
}
MailboxOperation::Subscribe(path) => {
let mailbox_hash = if let Some((mailbox_hash, _)) = self
.mailbox_entries
.iter()
.find(|(_, f)| f.ref_mailbox.path() == path)
{
*mailbox_hash
} else {
return Err(MeliError::new("Mailbox with that path not found."));
};
let mailbox_hash = self.mailbox_by_path(&path)?;
self.backend
.write()
.unwrap()
@ -1118,15 +1106,7 @@ impl Account {
Ok(format!("'`{}` has been subscribed.", &path))
}
MailboxOperation::Unsubscribe(path) => {
let mailbox_hash = if let Some((mailbox_hash, _)) = self
.mailbox_entries
.iter()
.find(|(_, f)| f.ref_mailbox.path() == path)
{
*mailbox_hash
} else {
return Err(MeliError::new("Mailbox with that path not found."));
};
let mailbox_hash = self.mailbox_by_path(&path)?;
self.backend
.write()
.unwrap()
@ -1143,13 +1123,13 @@ impl Account {
}
}
pub fn special_use_mailbox(&self, special_use: SpecialUsageMailbox) -> Option<&str> {
pub fn special_use_mailbox(&self, special_use: SpecialUsageMailbox) -> Option<MailboxHash> {
let ret = self
.mailbox_entries
.iter()
.find(|(_, f)| f.conf.mailbox_conf().usage == Some(special_use));
if let Some(ret) = ret.as_ref() {
Some(ret.1.name())
Some(ret.1.ref_mailbox.hash())
} else {
None
}
@ -1237,6 +1217,18 @@ impl Account {
Ok(ret)
}
}
pub fn mailbox_by_path(&self, path: &str) -> Result<MailboxHash> {
if let Some((mailbox_hash, _)) = self
.mailbox_entries
.iter()
.find(|(_, f)| f.ref_mailbox.path() == path)
{
Ok(*mailbox_hash)
} else {
Err(MeliError::new("Mailbox with that path not found."))
}
}
}
impl Index<&MailboxHash> for Account {

67
src/execute.rs

@ -236,22 +236,67 @@ define_commands!([
desc: "set [seen/unseen], toggles message's Seen flag.",
tokens: &[One(Literal("set")), One(Alternatives(&[to_stream!(One(Literal("seen"))), to_stream!(One(Literal("unseen")))]))],
parser:
(fn envelope_action<'a>(input: &'a [u8]) -> IResult<&'a [u8], Action> {
alt((
(fn seen_flag<'a>(input: &'a [u8]) -> IResult<&'a [u8], Action> {
preceded(
tag("set"),
alt((
map(tag("seen"), |_| Listing(SetSeen))
, map(tag("unseen"), |_| Listing(SetUnseen))
map(tag("seen"), |_| Listing(SetSeen)),
map(tag("unseen"), |_| Listing(SetUnseen))
))
) , map(preceded(tag("delete"), eof), |_| Listing(Delete))
, |input: &'a [u8]| -> IResult<&'a [u8], Action> {
)(input)
}
)
},
{ tags: ["delete"],
desc: "delete message",
tokens: &[One(Literal("delete"))],
parser: (
fn delete_message<'a>(input: &'a [u8]) -> IResult<&'a [u8], Action> {
map(preceded(tag("delete"), eof), |_| Listing(Delete))(input)
}
)
},
{ tags: ["copyto", "moveto"],
desc: "copy/move message",
tokens: &[One(Alternatives(&[to_stream!(One(Literal("copyto"))), to_stream!(One(Literal("moveto")))])), ZeroOrOne(AccountName), One(MailboxPath)],
parser: (
fn copymove<'a>(input: &'a [u8]) -> IResult<&'a [u8], Action> {
alt((
|input: &'a [u8]| -> IResult<&'a [u8], Action> {
let (input, _) = tag("copyto")(input.trim())?;
let (input, _) = is_a(" ")(input)?;
let (input,path) = quoted_argument(input)?;
Ok( (input, { Listing(CopyTo(path.to_string())) })) }
let (input, path) = quoted_argument(input)?;
let (input, _) = eof(input)?;
Ok( (input, { Listing(CopyTo(path.to_string())) }))
},
|input: &'a [u8]| -> IResult<&'a [u8], Action> {
let (input, _) = tag("copyto")(input.trim())?;
let (input, _) = is_a(" ")(input)?;
let (input, account) = quoted_argument(input)?;
let (input, _) = is_a(" ")(input)?;
let (input, path) = quoted_argument(input)?;
let (input, _) = eof(input)?;
Ok( (input, { Listing(CopyToOtherAccount(account.to_string(), path.to_string())) }))
},
|input: &'a [u8]| -> IResult<&'a [u8], Action> {
let (input, _) = tag("moveto")(input.trim())?;
let (input, _) = is_a(" ")(input)?;
let (input, path) = quoted_argument(input)?;
let (input, _) = eof(input)?;
Ok( (input, { Listing(MoveTo(path.to_string())) }))
},
|input: &'a [u8]| -> IResult<&'a [u8], Action> {
let (input, _) = tag("moveto")(input.trim())?;
let (input, _) = is_a(" ")(input)?;
let (input, account) = quoted_argument(input)?;
let (input, _) = is_a(" ")(input)?;
let (input, path) = quoted_argument(input)?;
let (input, _) = eof(input)?;
Ok( (input, { Listing(MoveToOtherAccount(account.to_string(), path.to_string())) }))
}
))(input)
})
}
)
},
{ tags: ["close"],
desc: "close non-sticky tabs",
@ -595,7 +640,9 @@ fn conversations(input: &[u8]) -> IResult<&[u8], Action> {
fn listing_action(input: &[u8]) -> IResult<&[u8], Action> {
alt((
toggle,
envelope_action,
seen_flag,
delete_message,
copymove,
search,
toggle_thread_snooze,
open_in_new_tab,

3
src/execute/actions.rs

@ -47,6 +47,9 @@ pub enum ListingAction {
SetSeen,
SetUnseen,
CopyTo(MailboxPath),
CopyToOtherAccount(AccountName, MailboxPath),
MoveTo(MailboxPath),
MoveToOtherAccount(AccountName, MailboxPath),
Delete,
OpenInNewTab,
Tag(TagAction),

2
src/plugins/backend.rs

@ -213,7 +213,7 @@ impl MailBackend for PluginBackend {
})
}
fn save(&self, _bytes: &[u8], _mailbox: &str, _flags: Option<Flag>) -> Result<()> {
fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option<Flag>) -> Result<()> {
Err(MeliError::new("Unimplemented."))
}
fn create_mailbox(

1
src/state.rs

@ -255,6 +255,7 @@ impl State {
account_hashes.insert(account_hash, index);
Account::new(
index,
account_hash,
n.to_string(),
a_s.clone(),
&backends,

Loading…
Cancel
Save