Browse Source

Replace every use of Folder with Mailbox

Use Mailbox for consistency.
tags/alpha-0.6.0
Manos Pitsidianakis 2 years ago
parent
commit
4ac52d9d5b
Signed by untrusted user: epilys GPG Key ID: 73627C2F690DF710
  1. 30
      meli.1
  2. 76
      meli.conf.5
  3. 104
      melib/src/backends.rs
  4. 260
      melib/src/backends/imap.rs
  5. 28
      melib/src/backends/imap/mailbox.rs
  6. 14
      melib/src/backends/imap/operations.rs
  7. 4
      melib/src/backends/imap/protocol_parser.rs
  8. 134
      melib/src/backends/imap/watch.rs
  9. 36
      melib/src/backends/jmap.rs
  10. 24
      melib/src/backends/jmap/mailbox.rs
  11. 2
      melib/src/backends/jmap/objects/email.rs
  12. 14
      melib/src/backends/jmap/protocol.rs
  13. 52
      melib/src/backends/maildir.rs
  14. 274
      melib/src/backends/maildir/backend.rs
  15. 124
      melib/src/backends/mbox.rs
  16. 92
      melib/src/backends/notmuch.rs
  17. 100
      melib/src/collection.rs
  18. 22
      melib/src/conf.rs
  19. 4
      melib/src/thread.rs
  20. 32
      sample-config
  21. 6
      src/cache.rs
  22. 4
      src/components/mail.rs
  23. 12
      src/components/mail/compose.rs
  24. 140
      src/components/mail/listing.rs
  25. 20
      src/components/mail/listing/compact.rs
  26. 25
      src/components/mail/listing/conversations.rs
  27. 8
      src/components/mail/listing/offline.rs
  28. 20
      src/components/mail/listing/plain.rs
  29. 16
      src/components/mail/listing/thread.rs
  30. 18
      src/components/mail/status.rs
  31. 6
      src/components/mail/view.rs
  32. 6
      src/components/mail/view/thread.rs
  33. 64
      src/conf.rs
  34. 515
      src/conf/accounts.rs
  35. 6
      src/conf/shortcuts.rs
  36. 54
      src/execute.rs
  37. 26
      src/execute/actions.rs
  38. 22
      src/plugins/backend.rs
  39. 36
      src/state.rs
  40. 14
      src/types.rs

30
meli.1

@ -94,7 +94,7 @@ The menu's visibility may be toggled with
.Ic toggle_menu_visibility Ns
).
.Pp
The view into each folder has 4 modes: plain, threaded, conversations and compact.
The view into each mailbox has 4 modes: plain, threaded, conversations and compact.
Plain views each mail indvidually, threaded shows their thread relationship visually, and conversations includes one entry per thread of emails (compact is one row per thread).
.Pp
If you're using a light color palette in your terminal, you may set
@ -224,9 +224,9 @@ To save your draft without sending it, issue command
.Cm close
and select 'save as draft'.
.Pp
With no Draft or Sent folder,
With no Draft or Sent mailbox,
.Nm
tries first saving mail in your INBOX and then at any other folder.
tries first saving mail in your INBOX and then at any other mailbox.
On complete failure to save your draft or sent message it will be saved in your
.Em tmp
directory instead and you will be notified of its location.
@ -307,17 +307,17 @@ filter mailbox with
key.
Escape exits filter results
.It Cm set read, set unread
.It Cm create-folder Ar ACCOUNT Ar FOLDER_PATH
create folder with given path.
.It Cm create-mailbox Ar ACCOUNT Ar MAILBOX_PATH
create mailbox with given path.
Be careful with backends and separator sensitivity (eg IMAP)
.It Cm subscribe-folder Ar ACCOUNT Ar FOLDER_PATH
subscribe to folder with given path
.It Cm unsubscribe-folder Ar ACCOUNT Ar FOLDER_PATH
unsubscribe to folder with given path
.It Cm rename-folder Ar ACCOUNT Ar FOLDER_PATH_SRC Ar FOLDER_PATH_DEST
rename folder
.It Cm delete-folder Ar ACCOUNT Ar FOLDER_PATH
delete folder
.It Cm subscribe-mailbox Ar ACCOUNT Ar MAILBOX_PATH
subscribe to mailbox with given path
.It Cm unsubscribe-mailbox Ar ACCOUNT Ar MAILBOX_PATH
unsubscribe to mailbox with given path
.It Cm rename-mailbox Ar ACCOUNT Ar MAILBOX_PATH_SRC Ar MAILBOX_PATH_DEST
rename mailbox
.It Cm delete-mailbox Ar ACCOUNT Ar MAILBOX_PATH
delete mailbox
.El
.Pp
envelope view commands:
@ -376,9 +376,9 @@ Non-complete list of shortcuts and their default values.
PageUp,
.It Ic next_page
PageDown
.It Ic prev_folder
.It Ic prev_mailbox
\&'K'
.It Ic next_folder
.It Ic next_mailbox
\&'J'
.It Ic prev_account
\&'l'

76
meli.conf.5

@ -56,23 +56,23 @@ example configuration
.Bd -literal
# Setting up a Maildir account
[accounts.account-name]
root_folder = "/path/to/root/folder"
root_mailbox = "/path/to/root/folder"
format = "Maildir"
index_style = "Compact"
identity="email@address.tld"
subscribed_folders = ["folder", "folder/Sent"] # or [ "*", ] for all folders
subscribed_mailboxes = ["folder", "folder/Sent"] # or [ "*", ] for all mailboxes
display_name = "Name"
# Set folder-specific settings
[accounts.account-name.folders]
# Set mailbox-specific settings
[accounts.account-name.mailboxes]
"INBOX" = { alias="Inbox" } #inline table
"drafts" = { alias="Drafts" } #inline table
[accounts.account-name.folders."foobar-devel"] # or a regular table
ignore = true # don't show notifications for this folder
[accounts.account-name.mailboxes."foobar-devel"] # or a regular table
ignore = true # don't show notifications for this mailbox
# Setting up an mbox account
[accounts.mbox]
root_folder = "/var/mail/username"
root_mailbox = "/var/mail/username"
format = "mbox"
index_style = "Compact"
identity="username@hostname.local"
@ -105,16 +105,16 @@ available options are listed below.
.Sy default values are shown in parentheses.
.Sh ACCOUNTS
.Bl -tag -width 36n
.It Ic root_folder Ar String
the backend-specific path of the root_folder, usually INBOX.
.It Ic root_mailbox Ar String
the backend-specific path of the root_mailbox, usually INBOX.
.It Ic format Ar String Op maildir mbox imap notmuch jmap
the format of the mail backend.
.It Ic subscribed_folders Ar [String,]
an array of folder paths to display in the UI.
Paths are relative to the root folder (eg "INBOX/Sent", not "Sent").
.It Ic subscribed_mailboxes Ar [String,]
an array of mailbox paths to display in the UI.
Paths are relative to the root mailbox (eg "INBOX/Sent", not "Sent").
The glob wildcard
.Em \&*
can be used to match every folder name and path.
can be used to match every mailbox name and path.
.It Ic identity Ar String
your e-mail address that is inserted in the From: headers of outgoing mail
.It Ic index_style Ar String
@ -148,20 +148,20 @@ Available options are 'none' and 'sqlite3'
.It Ic vcard_folder Ar String
(optional) Folder that contains .vcf files.
They are parsed and imported read-only.
.It Ic folders Ar folder_config
(optional) configuration for each folder.
.It Ic mailboxes Ar mailbox
(optional) configuration for each mailbox.
Its format is described below in
.Sx FOLDERS Ns
.Sx mailboxes Ns
\&.
.El
.Sh notmuch only
.Ic root_folder
.Ic root_mailbox
points to the directory which contains the
.Pa .notmuch/
subdirectory.
notmuch folders are virtual, since they are defined by user-given notmuch queries.
Thus you have to explicitly state the folders you want in the
.Ic folders
notmuch mailboxes are virtual, since they are defined by user-given notmuch queries.
Thus you have to explicitly state the mailboxes you want in the
.Ic mailboxes
field and set the
.Ar query
property to each of them.
@ -170,7 +170,7 @@ Example:
[accounts.notmuch]
format = "notmuch"
\&...
[accounts.notmuch.folders]
[accounts.notmuch.mailboxes]
"INBOX" = { query="tag:inbox", subscribe = true }
"Drafts" = { query="tag:draft", subscribe = true }
"Sent" = { query="from:username@server.tld from:username2@server.tld", subscribe = true }
@ -213,22 +213,22 @@ example:
.\" default value
.Pq Em false
.El
.Sh FOLDERS
.Sh mailboxes
.Bl -tag -width 36n
.It Ic alias Ar String
(optional) show a different name for this folder in the UI
(optional) show a different name for this mailbox in the UI
.It Ic autoload Ar boolean
(optional) load this folder on startup (not functional yet)
(optional) load this mailbox on startup (not functional yet)
.It Ic subscribe Ar boolean
(optional) watch this folder for updates
(optional) watch this mailbox for updates
.\" default value
.Pq Em true
.It Ic ignore Ar boolean
(optional) silently insert updates for this folder, if any
(optional) silently insert updates for this mailbox, if any
.\" default value
.Pq Em false
.It Ic usage Ar boolean
(optional) special usage of this folder.
(optional) special usage of this mailbox.
Valid values are:
.Bl -bullet -compact
.It
@ -248,9 +248,9 @@ Valid values are:
.It
.Ar Trash
.El
otherwise usage is inferred from the folder title.
otherwise usage is inferred from the mailbox title.
.It Ic conf_override Ar boolean
(optional) override global settings for this folder.
(optional) override global settings for this mailbox.
Available sections to override are
.Em pager, notifications, shortcuts, composing
and the account options
@ -258,9 +258,9 @@ and the account options
\&.
Example:
.Bd -literal
[accounts."imap.domain.tld".folders."INBOX"]
[accounts."imap.domain.tld".mailboxes."INBOX"]
index_style = "plain"
[accounts."imap.domain.tld".folders."INBOX".pager]
[accounts."imap.domain.tld".mailboxes."INBOX".pager]
filter = ""
.Ed
.El
@ -347,12 +347,12 @@ Go to previous page.
Go to next page.
.\" default value
.Pq Em PageDown
.It Ic prev_folder
Go to previous folder.
.It Ic prev_mailbox
Go to previous mailbox.
.\" default value
.Pq Em K
.It Ic next_folder
Go to next folder.
.It Ic next_mailbox
Go to next mailbox.
.\" default value
.Pq Em J
.It Ic prev_account
@ -372,7 +372,7 @@ Set thread as seen.
.\" default value
.Pq Em n
.It Ic refresh
Manually request a folder refresh.
Manually request a mailbox refresh.
.\" default value
.Pq Em F5
.It Ic search
@ -622,8 +622,8 @@ example configuration:
colors = { signed="#Ff6600", replied="DeepSkyBlue4", draft="#f00", replied="8" }
[accounts.dummy]
\&...
[accounts.dummy.folders]
# per folder override:
[accounts.dummy.mailboxes]
# per mailbox override:
"INBOX" = { tags.ignore_tags=["inbox", ] }
.Ed
.Sh PGP

104
melib/src/backends.rs

@ -193,12 +193,12 @@ pub enum RefreshEventKind {
#[derive(Debug)]
pub struct RefreshEvent {
hash: FolderHash,
hash: MailboxHash,
kind: RefreshEventKind,
}
impl RefreshEvent {
pub fn hash(&self) -> FolderHash {
pub fn hash(&self) -> MailboxHash {
self.hash
}
pub fn kind(self) -> RefreshEventKind {
@ -220,7 +220,7 @@ impl RefreshEventConsumer {
}
}
pub struct NotifyFn(Box<dyn Fn(FolderHash) -> () + Send + Sync>);
pub struct NotifyFn(Box<dyn Fn(MailboxHash) -> () + Send + Sync>);
impl fmt::Debug for NotifyFn {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -228,17 +228,17 @@ impl fmt::Debug for NotifyFn {
}
}
impl From<Box<dyn Fn(FolderHash) -> () + Send + Sync>> for NotifyFn {
fn from(kind: Box<dyn Fn(FolderHash) -> () + Send + Sync>) -> Self {
impl From<Box<dyn Fn(MailboxHash) -> () + Send + Sync>> for NotifyFn {
fn from(kind: Box<dyn Fn(MailboxHash) -> () + Send + Sync>) -> Self {
NotifyFn(kind)
}
}
impl NotifyFn {
pub fn new(b: Box<dyn Fn(FolderHash) -> () + Send + Sync>) -> Self {
pub fn new(b: Box<dyn Fn(MailboxHash) -> () + Send + Sync>) -> Self {
NotifyFn(b)
}
pub fn notify(&self, f: FolderHash) {
pub fn notify(&self, f: MailboxHash) {
self.0(f);
}
}
@ -246,10 +246,10 @@ impl NotifyFn {
pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
fn is_online(&self) -> Result<()>;
fn connect(&mut self) {}
fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>>;
fn get(&mut self, mailbox: &Mailbox) -> Async<Result<Vec<Envelope>>>;
fn refresh(
&mut self,
_folder_hash: FolderHash,
_mailbox_hash: MailboxHash,
_sender: RefreshEventConsumer,
) -> Result<Async<()>> {
Err(MeliError::new("Unimplemented."))
@ -259,10 +259,10 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
sender: RefreshEventConsumer,
work_context: WorkContext,
) -> Result<std::thread::ThreadId>;
fn folders(&self) -> Result<FnvHashMap<FolderHash, Folder>>;
fn mailboxes(&self) -> Result<FnvHashMap<MailboxHash, Mailbox>>;
fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp>;
fn save(&self, bytes: &[u8], folder: &str, flags: Option<Flag>) -> Result<()>;
fn save(&self, bytes: &[u8], mailbox: &str, flags: Option<Flag>) -> Result<()>;
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
None
}
@ -272,32 +272,32 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
unimplemented!()
}
fn create_folder(
fn create_mailbox(
&mut self,
_path: String,
) -> Result<(FolderHash, FnvHashMap<FolderHash, Folder>)> {
) -> Result<(MailboxHash, FnvHashMap<MailboxHash, Mailbox>)> {
Err(MeliError::new("Unimplemented."))
}
fn delete_folder(
fn delete_mailbox(
&mut self,
_folder_hash: FolderHash,
) -> Result<FnvHashMap<FolderHash, Folder>> {
_mailbox_hash: MailboxHash,
) -> Result<FnvHashMap<MailboxHash, Mailbox>> {
Err(MeliError::new("Unimplemented."))
}
fn set_folder_subscription(&mut self, _folder_hash: FolderHash, _val: bool) -> Result<()> {
fn set_mailbox_subscription(&mut self, _mailbox_hash: MailboxHash, _val: bool) -> Result<()> {
Err(MeliError::new("Unimplemented."))
}
fn rename_folder(&mut self, _folder_hash: FolderHash, _new_path: String) -> Result<Folder> {
fn rename_mailbox(&mut self, _mailbox_hash: MailboxHash, _new_path: String) -> Result<Mailbox> {
Err(MeliError::new("Unimplemented."))
}
fn set_folder_permissions(
fn set_mailbox_permissions(
&mut self,
_folder_hash: FolderHash,
_val: FolderPermissions,
_mailbox_hash: MailboxHash,
_val: MailboxPermissions,
) -> Result<()> {
Err(MeliError::new("Unimplemented."))
}
@ -315,7 +315,7 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
/// ```ignore
/// /* Create operation from Backend */
///
/// let op = backend.operation(message.hash(), mailbox.folder.hash());
/// let op = backend.operation(message.hash(), mailbox.hash());
/// ```
///
/// # Example
@ -443,30 +443,30 @@ impl SpecialUsageMailbox {
}
}
pub trait BackendFolder: Debug {
fn hash(&self) -> FolderHash;
pub trait BackendMailbox: Debug {
fn hash(&self) -> MailboxHash;
fn name(&self) -> &str;
/// Path of folder within the mailbox hierarchy, with `/` as separator.
/// Path of mailbox within the mailbox hierarchy, with `/` as separator.
fn path(&self) -> &str;
fn change_name(&mut self, new_name: &str);
fn clone(&self) -> Folder;
fn children(&self) -> &[FolderHash];
fn parent(&self) -> Option<FolderHash>;
fn clone(&self) -> Mailbox;
fn children(&self) -> &[MailboxHash];
fn parent(&self) -> Option<MailboxHash>;
fn is_subscribed(&self) -> bool;
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()>;
fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()>;
fn special_usage(&self) -> SpecialUsageMailbox;
fn permissions(&self) -> FolderPermissions;
fn permissions(&self) -> MailboxPermissions;
fn count(&self) -> Result<(usize, usize)>;
}
#[derive(Debug)]
struct DummyFolder {
v: Vec<FolderHash>,
struct DummyMailbox {
v: Vec<MailboxHash>,
}
impl BackendFolder for DummyFolder {
fn hash(&self) -> FolderHash {
impl BackendMailbox for DummyMailbox {
fn hash(&self) -> MailboxHash {
0
}
@ -480,24 +480,24 @@ impl BackendFolder for DummyFolder {
fn change_name(&mut self, _s: &str) {}
fn clone(&self) -> Folder {
folder_default()
fn clone(&self) -> Mailbox {
mailbox_default()
}
fn special_usage(&self) -> SpecialUsageMailbox {
SpecialUsageMailbox::Normal
}
fn children(&self) -> &[FolderHash] {
fn children(&self) -> &[MailboxHash] {
&self.v
}
fn parent(&self) -> Option<FolderHash> {
fn parent(&self) -> Option<MailboxHash> {
None
}
fn permissions(&self) -> FolderPermissions {
FolderPermissions::default()
fn permissions(&self) -> MailboxPermissions {
MailboxPermissions::default()
}
fn is_subscribed(&self) -> bool {
true
@ -513,29 +513,29 @@ impl BackendFolder for DummyFolder {
}
}
pub fn folder_default() -> Folder {
Box::new(DummyFolder {
pub fn mailbox_default() -> Mailbox {
Box::new(DummyMailbox {
v: Vec::with_capacity(0),
})
}
pub type FolderHash = u64;
pub type Folder = Box<dyn BackendFolder + Send + Sync>;
pub type MailboxHash = u64;
pub type Mailbox = Box<dyn BackendMailbox + Send + Sync>;
impl Clone for Folder {
impl Clone for Mailbox {
fn clone(&self) -> Self {
BackendFolder::clone(self.deref())
BackendMailbox::clone(self.deref())
}
}
impl Default for Folder {
impl Default for Mailbox {
fn default() -> Self {
folder_default()
mailbox_default()
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct FolderPermissions {
pub struct MailboxPermissions {
pub create_messages: bool,
pub remove_messages: bool,
pub set_flags: bool,
@ -546,22 +546,22 @@ pub struct FolderPermissions {
pub change_permissions: bool,
}
impl Default for FolderPermissions {
impl Default for MailboxPermissions {
fn default() -> Self {
FolderPermissions {
MailboxPermissions {
create_messages: false,
remove_messages: false,
set_flags: false,
create_child: false,
rename_messages: false,
delete_messages: false,
delete_mailbox: false,
delete_mailbox: true,
change_permissions: false,
}
}
}
impl std::fmt::Display for FolderPermissions {
impl std::fmt::Display for MailboxPermissions {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "{:#?}", self)
}

260
melib/src/backends/imap.rs

@ -24,8 +24,8 @@ use smallvec::SmallVec;
#[macro_use]
mod protocol_parser;
pub use protocol_parser::{UntaggedResponse::*, *};
mod folder;
pub use folder::*;
mod mailbox;
pub use mailbox::*;
mod operations;
pub use operations::*;
mod connection;
@ -35,10 +35,10 @@ pub use watch::*;
use crate::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
use crate::backends::BackendOp;
use crate::backends::FolderHash;
use crate::backends::MailboxHash;
use crate::backends::RefreshEvent;
use crate::backends::RefreshEventKind::{self, *};
use crate::backends::{BackendFolder, Folder, MailBackend, RefreshEventConsumer};
use crate::backends::{BackendMailbox, MailBackend, Mailbox, RefreshEventConsumer};
use crate::conf::AccountSettings;
use crate::email::*;
use crate::error::{MeliError, Result};
@ -117,8 +117,8 @@ macro_rules! get_conf_val {
#[derive(Debug)]
pub struct UIDStore {
uidvalidity: Arc<Mutex<FnvHashMap<FolderHash, UID>>>,
hash_index: Arc<Mutex<FnvHashMap<EnvelopeHash, (UID, FolderHash)>>>,
uidvalidity: Arc<Mutex<FnvHashMap<MailboxHash, UID>>>,
hash_index: Arc<Mutex<FnvHashMap<EnvelopeHash, (UID, MailboxHash)>>>,
uid_index: Arc<Mutex<FnvHashMap<UID, EnvelopeHash>>>,
byte_cache: Arc<Mutex<FnvHashMap<UID, EnvelopeCache>>>,
@ -134,7 +134,7 @@ pub struct ImapType {
can_create_flags: Arc<Mutex<bool>>,
tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
folders: Arc<RwLock<FnvHashMap<FolderHash, ImapFolder>>>,
mailboxes: Arc<RwLock<FnvHashMap<MailboxHash, ImapMailbox>>>,
}
impl MailBackend for ImapType {
@ -152,16 +152,16 @@ impl MailBackend for ImapType {
}
}
fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
fn get(&mut self, mailbox: &Mailbox) -> Async<Result<Vec<Envelope>>> {
let mut w = AsyncBuilder::new();
let handle = {
let tx = w.tx();
let uid_store = self.uid_store.clone();
let tag_index = self.tag_index.clone();
let can_create_flags = self.can_create_flags.clone();
let folder_hash = folder.hash();
let (permissions, folder_path, folder_exists, no_select, unseen) = {
let f = &self.folders.read().unwrap()[&folder_hash];
let mailbox_hash = mailbox.hash();
let (permissions, mailbox_path, mailbox_exists, no_select, unseen) = {
let f = &self.mailboxes.read().unwrap()[&mailbox_hash];
(
f.permissions.clone(),
f.imap_path().to_string(),
@ -182,24 +182,24 @@ impl MailBackend for ImapType {
let tx = _tx;
let mut response = String::with_capacity(8 * 1024);
let mut conn = connection.lock()?;
debug!("locked for get {}", folder_path);
debug!("locked for get {}", mailbox_path);
/* first SELECT the mailbox to get READ/WRITE permissions (because EXAMINE only
* returns READ-ONLY for both cases) */
conn.send_command(format!("SELECT \"{}\"", folder_path).as_bytes())?;
conn.send_command(format!("SELECT \"{}\"", mailbox_path).as_bytes())?;
conn.read_response(&mut response)?;
let examine_response = protocol_parser::select_response(&response)?;
*can_create_flags.lock().unwrap() = examine_response.can_create_flags;
debug!(
"folder: {} examine_response: {:?}",
folder_path, examine_response
"mailbox: {} examine_response: {:?}",
mailbox_path, examine_response
);
let mut exists: usize = examine_response.uidnext - 1;
{
let mut uidvalidities = uid_store.uidvalidity.lock().unwrap();
let v = uidvalidities
.entry(folder_hash)
.entry(mailbox_hash)
.or_insert(examine_response.uidvalidity);
*v = examine_response.uidvalidity;
@ -210,11 +210,11 @@ impl MailBackend for ImapType {
permissions.rename_messages = !examine_response.read_only;
permissions.delete_messages = !examine_response.read_only;
permissions.delete_messages = !examine_response.read_only;
let mut folder_exists = folder_exists.lock().unwrap();
*folder_exists = exists;
let mut mailbox_exists = mailbox_exists.lock().unwrap();
*mailbox_exists = exists;
}
/* reselecting the same mailbox with EXAMINE prevents expunging it */
conn.send_command(format!("EXAMINE \"{}\"", folder_path).as_bytes())?;
conn.send_command(format!("EXAMINE \"{}\"", mailbox_path).as_bytes())?;
conn.read_response(&mut response)?;
let mut tag_lck = tag_index.write().unwrap();
@ -247,7 +247,7 @@ impl MailBackend for ImapType {
let mut env = envelope.unwrap();
let mut h = DefaultHasher::new();
h.write_usize(uid);
h.write(folder_path.as_bytes());
h.write(mailbox_path.as_bytes());
env.set_hash(h.finish());
if let Some((flags, keywords)) = flags {
if !flags.contains(Flag::SEEN) {
@ -266,7 +266,7 @@ impl MailBackend for ImapType {
.hash_index
.lock()
.unwrap()
.insert(env.hash(), (uid, folder_hash));
.insert(env.hash(), (uid, mailbox_hash));
uid_store.uid_index.lock().unwrap().insert(uid, env.hash());
envelopes.push(env);
}
@ -290,15 +290,15 @@ impl MailBackend for ImapType {
fn refresh(
&mut self,
folder_hash: FolderHash,
mailbox_hash: MailboxHash,
sender: RefreshEventConsumer,
) -> Result<Async<()>> {
self.connection.lock().unwrap().connect()?;
let inbox = self
.folders
.mailboxes
.read()
.unwrap()
.get(&folder_hash)
.get(&mailbox_hash)
.map(std::clone::Clone::clone)
.unwrap();
let tag_index = self.tag_index.clone();
@ -340,7 +340,7 @@ impl MailBackend for ImapType {
sender: RefreshEventConsumer,
work_context: WorkContext,
) -> Result<std::thread::ThreadId> {
let folders = self.folders.clone();
let mailboxes = self.mailboxes.clone();
let tag_index = self.tag_index.clone();
let conn = ImapConnection::new_connection(&self.server_conf, self.online.clone());
let main_conn = self.connection.clone();
@ -365,7 +365,7 @@ impl MailBackend for ImapType {
is_online,
main_conn,
uid_store,
folders,
mailboxes,
sender,
work_context,
tag_index,
@ -379,38 +379,41 @@ impl MailBackend for ImapType {
Ok(handle.thread().id())
}
fn folders(&self) -> Result<FnvHashMap<FolderHash, Folder>> {
fn mailboxes(&self) -> Result<FnvHashMap<MailboxHash, Mailbox>> {
{
let folders = self.folders.read().unwrap();
if !folders.is_empty() {
return Ok(folders
let mailboxes = self.mailboxes.read().unwrap();
if !mailboxes.is_empty() {
return Ok(mailboxes
.iter()
.map(|(h, f)| (*h, Box::new(Clone::clone(f)) as Folder))
.map(|(h, f)| (*h, Box::new(Clone::clone(f)) as Mailbox))
.collect());
}
}
let mut folders = self.folders.write()?;
*folders = ImapType::imap_folders(&self.connection)?;
folders.retain(|_, f| (self.is_subscribed)(f.path()));
let keys = folders.keys().cloned().collect::<FnvHashSet<FolderHash>>();
let mut mailboxes = self.mailboxes.write()?;
*mailboxes = ImapType::imap_mailboxes(&self.connection)?;
mailboxes.retain(|_, f| (self.is_subscribed)(f.path()));
let keys = mailboxes
.keys()
.cloned()
.collect::<FnvHashSet<MailboxHash>>();
let mut uid_lock = self.uid_store.uidvalidity.lock().unwrap();
for f in folders.values_mut() {
for f in mailboxes.values_mut() {
uid_lock.entry(f.hash()).or_default();
f.children.retain(|c| keys.contains(c));
}
drop(uid_lock);
Ok(folders
Ok(mailboxes
.iter()
.filter(|(_, f)| f.is_subscribed)
.map(|(h, f)| (*h, Box::new(Clone::clone(f)) as Folder))
.map(|(h, f)| (*h, Box::new(Clone::clone(f)) as Mailbox))
.collect())
}
fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp> {
let (uid, folder_hash) = self.uid_store.hash_index.lock().unwrap()[&hash];
let (uid, mailbox_hash) = self.uid_store.hash_index.lock().unwrap()[&hash];
Box::new(ImapOp::new(
uid,
self.folders.read().unwrap()[&folder_hash]
self.mailboxes.read().unwrap()[&mailbox_hash]
.imap_path()
.to_string(),
self.connection.clone(),
@ -419,28 +422,28 @@ impl MailBackend for ImapType {
))
}
fn save(&self, bytes: &[u8], folder: &str, flags: Option<Flag>) -> Result<()> {
fn save(&self, bytes: &[u8], mailbox: &str, flags: Option<Flag>) -> Result<()> {
let path = {
let folders = self.folders.read().unwrap();
let mailboxes = self.mailboxes.read().unwrap();
let f_result = folders
let f_result = mailboxes
.values()
.find(|v| v.path == folder || v.name == folder);
.find(|v| v.path == mailbox || v.name == mailbox);
if f_result
.map(|f| !f.permissions.lock().unwrap().create_messages)
.unwrap_or(false)
{
return Err(MeliError::new(format!(
"You are not allowed to create messages in folder {}",
folder
"You are not allowed to create messages in mailbox {}",
mailbox
)));
}
f_result
.map(|v| v.imap_path().to_string())
.ok_or(MeliError::new(format!(
"Folder with name {} not found.",
folder
"Mailbox with name {} not found.",
mailbox
)))?
};
let mut response = String::with_capacity(8 * 1024);
@ -478,10 +481,10 @@ impl MailBackend for ImapType {
}
}
fn create_folder(
fn create_mailbox(
&mut self,
mut path: String,
) -> Result<(FolderHash, FnvHashMap<FolderHash, Folder>)> {
) -> Result<(MailboxHash, FnvHashMap<MailboxHash, Mailbox>)> {
/* Must transform path to something the IMAP server will accept
*
* Each root mailbox has a hierarchy delimeter reported by the LIST entry. All paths
@ -496,21 +499,21 @@ impl MailBackend for ImapType {
* decision is unpleasant for you.
*/
let mut folders = self.folders.write().unwrap();
for root_folder in folders.values().filter(|f| f.parent.is_none()) {
if path.starts_with(&root_folder.name) {
debug!("path starts with {:?}", &root_folder);
let mut mailboxes = self.mailboxes.write().unwrap();
for root_mailbox in mailboxes.values().filter(|f| f.parent.is_none()) {
if path.starts_with(&root_mailbox.name) {
debug!("path starts with {:?}", &root_mailbox);
path = path.replace(
'/',
(root_folder.separator as char).encode_utf8(&mut [0; 4]),
(root_mailbox.separator as char).encode_utf8(&mut [0; 4]),
);
break;
}
}
if folders.values().any(|f| f.path == path) {
if mailboxes.values().any(|f| f.path == path) {
return Err(MeliError::new(format!(
"Folder named `{}` in account `{}` already exists.",
"Mailbox named `{}` in account `{}` already exists.",
path, self.account_name,
)));
}
@ -527,21 +530,24 @@ impl MailBackend for ImapType {
let ret: Result<()> = ImapResponse::from(&response).into();
ret?;
let new_hash = get_path_hash!(path.as_str());
folders.clear();
drop(folders);
Ok((new_hash, self.folders().map_err(|err| MeliError::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err)))?))
mailboxes.clear();
drop(mailboxes);
Ok((new_hash, self.mailboxes().map_err(|err| MeliError::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err)))?))
}
fn delete_folder(&mut self, folder_hash: FolderHash) -> Result<FnvHashMap<FolderHash, Folder>> {
let mut folders = self.folders.write().unwrap();
let permissions = folders[&folder_hash].permissions();
fn delete_mailbox(
&mut self,
mailbox_hash: MailboxHash,
) -> Result<FnvHashMap<MailboxHash, Mailbox>> {
let mut mailboxes = self.mailboxes.write().unwrap();
let permissions = mailboxes[&mailbox_hash].permissions();
if !permissions.delete_mailbox {
return Err(MeliError::new(format!("You do not have permission to delete `{}`. Set permissions for this mailbox are {}", folders[&folder_hash].name(), permissions)));
return Err(MeliError::new(format!("You do not have permission to delete `{}`. Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions)));
}
let mut response = String::with_capacity(8 * 1024);
{
let mut conn_lck = self.connection.lock()?;
if !folders[&folder_hash].no_select {
if !mailboxes[&mailbox_hash].no_select {
/* make sure mailbox is not selected before it gets deleted, otherwise
* connection gets dropped by server */
if conn_lck
@ -550,42 +556,46 @@ impl MailBackend for ImapType {
.any(|cap| cap.eq_ignore_ascii_case(b"UNSELECT"))
{
conn_lck.send_command(
format!("UNSELECT \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
format!("UNSELECT \"{}\"", mailboxes[&mailbox_hash].imap_path()).as_bytes(),
)?;
conn_lck.read_response(&mut response)?;
} else {
conn_lck.send_command(
format!("SELECT \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
format!("SELECT \"{}\"", mailboxes[&mailbox_hash].imap_path()).as_bytes(),
)?;
conn_lck.read_response(&mut response)?;
conn_lck.send_command(
format!("EXAMINE \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
format!("EXAMINE \"{}\"", mailboxes[&mailbox_hash].imap_path()).as_bytes(),
)?;
conn_lck.read_response(&mut response)?;
}
}
if folders[&folder_hash].is_subscribed() {
if mailboxes[&mailbox_hash].is_subscribed() {
conn_lck.send_command(
format!("UNSUBSCRIBE \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
format!("UNSUBSCRIBE \"{}\"", mailboxes[&mailbox_hash].imap_path()).as_bytes(),
)?;
conn_lck.read_response(&mut response)?;
}
conn_lck.send_command(
debug!(format!("DELETE \"{}\"", folders[&folder_hash].imap_path())).as_bytes(),
debug!(format!(
"DELETE \"{}\"",
mailboxes[&mailbox_hash].imap_path()
))
.as_bytes(),
)?;
conn_lck.read_response(&mut response)?;
}
let ret: Result<()> = ImapResponse::from(&response).into();
ret?;
folders.clear();
drop(folders);
self.folders().map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err).into())
mailboxes.clear();
drop(mailboxes);
self.mailboxes().map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err).into())
}
fn set_folder_subscription(&mut self, folder_hash: FolderHash, new_val: bool) -> Result<()> {
let mut folders = self.folders.write().unwrap();
if folders[&folder_hash].is_subscribed() == new_val {
fn set_mailbox_subscription(&mut self, mailbox_hash: MailboxHash, new_val: bool) -> Result<()> {
let mut mailboxes = self.mailboxes.write().unwrap();
if mailboxes[&mailbox_hash].is_subscribed() == new_val {
return Ok(());
}
@ -594,11 +604,11 @@ impl MailBackend for ImapType {
let mut conn_lck = self.connection.lock()?;
if new_val {
conn_lck.send_command(
format!("SUBSCRIBE \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
format!("SUBSCRIBE \"{}\"", mailboxes[&mailbox_hash].imap_path()).as_bytes(),
)?;
} else {
conn_lck.send_command(
format!("UNSUBSCRIBE \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
format!("UNSUBSCRIBE \"{}\"", mailboxes[&mailbox_hash].imap_path()).as_bytes(),
)?;
}
conn_lck.read_response(&mut response)?;
@ -606,24 +616,28 @@ impl MailBackend for ImapType {
let ret: Result<()> = ImapResponse::from(&response).into();
if ret.is_ok() {
folders.entry(folder_hash).and_modify(|entry| {
mailboxes.entry(mailbox_hash).and_modify(|entry| {
let _ = entry.set_is_subscribed(new_val);
});
}
ret
}
fn rename_folder(&mut self, folder_hash: FolderHash, mut new_path: String) -> Result<Folder> {
let mut folders = self.folders.write().unwrap();
let permissions = folders[&folder_hash].permissions();
fn rename_mailbox(
&mut self,
mailbox_hash: MailboxHash,
mut new_path: String,
) -> Result<Mailbox> {
let mut mailboxes = self.mailboxes.write().unwrap();
let permissions = mailboxes[&mailbox_hash].permissions();
if !permissions.delete_mailbox {
return Err(MeliError::new(format!("You do not have permission to rename folder `{}` (rename is equivalent to delete + create). Set permissions for this mailbox are {}", folders[&folder_hash].name(), permissions)));
return Err(MeliError::new(format!("You do not have permission to rename mailbox `{}` (rename is equivalent to delete + create). Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions)));
}
let mut response = String::with_capacity(8 * 1024);
if folders[&folder_hash].separator != b'/' {
if mailboxes[&mailbox_hash].separator != b'/' {
new_path = new_path.replace(
'/',
(folders[&folder_hash].separator as char).encode_utf8(&mut [0; 4]),
(mailboxes[&mailbox_hash].separator as char).encode_utf8(&mut [0; 4]),
);
}
{
@ -631,7 +645,7 @@ impl MailBackend for ImapType {
conn_lck.send_command(
debug!(format!(
"RENAME \"{}\" \"{}\"",
folders[&folder_hash].imap_path(),
mailboxes[&mailbox_hash].imap_path(),
new_path
))
.as_bytes(),
@ -641,23 +655,23 @@ impl MailBackend for ImapType {
let new_hash = get_path_hash!(new_path.as_str());
let ret: Result<()> = ImapResponse::from(&response).into();
ret?;
folders.clear();
drop(folders);
self.folders().map_err(|err| format!("Mailbox rename was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err))?;
Ok(BackendFolder::clone(
&self.folders.read().unwrap()[&new_hash],
mailboxes.clear();
drop(mailboxes);
self.mailboxes().map_err(|err| format!("Mailbox rename was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err))?;
Ok(BackendMailbox::clone(
&self.mailboxes.read().unwrap()[&new_hash],
))
}
fn set_folder_permissions(
fn set_mailbox_permissions(
&mut self,
folder_hash: FolderHash,
_val: crate::backends::FolderPermissions,
mailbox_hash: MailboxHash,
_val: crate::backends::MailboxPermissions,
) -> Result<()> {
let folders = self.folders.write().unwrap();
let permissions = folders[&folder_hash].permissions();
let mailboxes = self.mailboxes.write().unwrap();
let permissions = mailboxes[&mailbox_hash].permissions();
if !permissions.change_permissions {
return Err(MeliError::new(format!("You do not have permission to change permissions for folder `{}`. Set permissions for this mailbox are {}", folders[&folder_hash].name(), permissions)));
return Err(MeliError::new(format!("You do not have permission to change permissions for mailbox `{}`. Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions)));
}
Err(MeliError::new("Unimplemented."))
@ -698,7 +712,7 @@ impl ImapType {
can_create_flags: Arc::new(Mutex::new(false)),
tag_index: Arc::new(RwLock::new(Default::default())),
folders: Arc::new(RwLock::new(Default::default())),
mailboxes: Arc::new(RwLock::new(Default::default())),
connection: Arc::new(Mutex::new(connection)),
uid_store: Arc::new(UIDStore {
uidvalidity: Default::default(),
@ -742,10 +756,10 @@ impl ImapType {
}
}
pub fn imap_folders(
pub fn imap_mailboxes(
connection: &Arc<Mutex<ImapConnection>>,
) -> Result<FnvHashMap<FolderHash, ImapFolder>> {
let mut folders: FnvHashMap<FolderHash, ImapFolder> = Default::default();
) -> Result<FnvHashMap<MailboxHash, ImapMailbox>> {
let mut mailboxes: FnvHashMap<MailboxHash, ImapMailbox> = Default::default();
let mut res = String::with_capacity(8 * 1024);
let mut conn = connection.lock().unwrap();
conn.send_command(b"LIST \"\" \"*\"")?;
@ -755,33 +769,33 @@ impl ImapType {
/* Remove "M__ OK .." line */
lines.next_back();
for l in lines.map(|l| l.trim()) {
if let Ok(mut folder) =
protocol_parser::list_folder_result(l.as_bytes()).to_full_result()
if let Ok(mut mailbox) =
protocol_parser::list_mailbox_result(l.as_bytes()).to_full_result()
{
if let Some(parent) = folder.parent {
if folders.contains_key(&parent) {
folders
if let Some(parent) = mailbox.parent {
if mailboxes.contains_key(&parent) {
mailboxes
.entry(parent)
.and_modify(|e| e.children.push(folder.hash));
.and_modify(|e| e.children.push(mailbox.hash));
} else {
/* Insert dummy parent entry, populating only the children field. Later
* when we encounter the parent entry we will swap its children with
* dummy's */
folders.insert(
mailboxes.insert(
parent,
ImapFolder {
children: vec![folder.hash],
..ImapFolder::default()
ImapMailbox {
children: vec![mailbox.hash],
..ImapMailbox::default()
},
);
}
}
if folders.contains_key(&folder.hash) {
let entry = folders.entry(folder.hash).or_default();
std::mem::swap(&mut entry.children, &mut folder.children);
*entry = folder;
if mailboxes.contains_key(&mailbox.hash) {
let entry = mailboxes.entry(mailbox.hash).or_default();
std::mem::swap(&mut entry.children, &mut mailbox.children);
*entry = mailbox;
} else {
folders.insert(folder.hash, folder);
mailboxes.insert(mailbox.hash, mailbox);
}
} else {
debug!("parse error for {:?}", l);
@ -795,9 +809,9 @@ impl ImapType {
lines.next_back();
for l in lines.map(|l| l.trim()) {
if let Ok(subscription) =
protocol_parser::list_folder_result(l.as_bytes()).to_full_result()
protocol_parser::list_mailbox_result(l.as_bytes()).to_full_result()
{
if let Some(f) = folders.get_mut(&subscription.hash()) {
if let Some(f) = mailboxes.get_mut(&subscription.hash()) {
if subscription.no_select {
continue;
}
@ -807,7 +821,7 @@ impl ImapType {
debug!("parse error for {:?}", l);
}
}
Ok(debug!(folders))
Ok(debug!(mailboxes))
}
pub fn capabilities(&self) -> Vec<String> {
@ -823,13 +837,13 @@ impl ImapType {
pub fn search(
&self,
query: String,
folder_hash: FolderHash,
mailbox_hash: MailboxHash,
) -> Result<SmallVec<[EnvelopeHash; 512]>> {
let folders_lck = self.folders.read()?;
let mailboxes_lck = self.mailboxes.read()?;
let mut response = String::with_capacity(8 * 1024);
let mut conn = self.connection.lock()?;
conn.send_command(
format!("EXAMINE \"{}\"", folders_lck[&folder_hash].imap_path()).as_bytes(),
format!("EXAMINE \"{}\"", mailboxes_lck[&mailbox_hash].imap_path()).as_bytes(),
)?;
conn.read_response(&mut response)?;
conn.send_command(format!("UID SEARCH CHARSET UTF-8 {}", query).as_bytes())?;

28
melib/src/backends/imap/folder.rs → melib/src/backends/imap/mailbox.rs

@ -18,36 +18,38 @@
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::backends::{BackendFolder, Folder, FolderHash, FolderPermissions, SpecialUsageMailbox};
use crate::backends::{
BackendMailbox, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox,
};
use crate::error::*;
use std::sync::{Arc, Mutex, RwLock};
#[derive(Debug, Default, Clone)]
pub struct ImapFolder {
pub(super) hash: FolderHash,
pub struct ImapMailbox {
pub(super) hash: MailboxHash,
pub(super) imap_path: String,
pub(super) path: String,
pub(super) name: String,
pub(super) parent: Option<FolderHash>,
pub(super) children: Vec<FolderHash>,
pub(super) parent: Option<MailboxHash>,
pub(super) children: Vec<MailboxHash>,
pub separator: u8,
pub usage: Arc<RwLock<SpecialUsageMailbox>>,
pub no_select: bool,
pub is_subscribed: bool,
pub permissions: Arc<Mutex<FolderPermissions>>,
pub permissions: Arc<Mutex<MailboxPermissions>>,
pub exists: Arc<Mutex<usize>>,
pub unseen: Arc<Mutex<usize>>,
}
impl ImapFolder {
impl ImapMailbox {
pub fn imap_path(&self) -> &str {
&self.imap_path
}
}
impl BackendFolder for ImapFolder {
fn hash(&self) -> FolderHash {
impl BackendMailbox for ImapMailbox {
fn hash(&self) -> MailboxHash {
self.hash
}
@ -63,11 +65,11 @@ impl BackendFolder for ImapFolder {
self.name = s.to_string();
}
fn children(&self) -> &[FolderHash] {
fn children(&self) -> &[MailboxHash] {
&self.children
}
fn clone(&self) -> Folder {
fn clone(&self) -> Mailbox {
Box::new(std::clone::Clone::clone(self))
}
@ -75,11 +77,11 @@ impl BackendFolder for ImapFolder {
*self.usage.read().unwrap()
}
fn parent(&self) -> Option<FolderHash> {
fn parent(&self) -> Option<MailboxHash> {
self.parent
}
fn permissions(&self) -> FolderPermissions {
fn permissions(&self) -> MailboxPermissions {
*self.permissions.lock().unwrap()
}
fn is_subscribed(&self) -> bool {

14
melib/src/backends/imap/operations.rs

@ -34,7 +34,7 @@ pub struct ImapOp {
bytes: Option<String>,
headers: Option<String>,
body: Option<String>,
folder_path: String,
mailbox_path: String,
flags: Cell<Option<Flag>>,
connection: Arc<Mutex<ImapConnection>>,
uid_store: Arc<UIDStore>,
@ -44,7 +44,7 @@ pub struct ImapOp {
impl ImapOp {
pub fn new(
uid: usize,
folder_path: String,
mailbox_path: String,
connection: Arc<Mutex<ImapConnection>>,
uid_store: Arc<UIDStore>,
tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
@ -55,7 +55,7 @@ impl ImapOp {
bytes: None,
headers: None,
body: None,
folder_path,
mailbox_path,
flags: Cell::new(None),
uid_store,
tag_index,
@ -78,7 +78,7 @@ impl BackendOp for ImapOp {
let mut response = String::with_capacity(8 * 1024);
{
let mut conn = self.connection.lock().unwrap();
conn.send_command(format!("SELECT \"{}\"", &self.folder_path,).as_bytes())?;
conn.send_command(format!("SELECT \"{}\"", &self.mailbox_path,).as_bytes())?;
conn.read_response(&mut response)?;
conn.send_command(format!("UID FETCH {} (FLAGS RFC822)", self.uid).as_bytes())?;
conn.read_response(&mut response)?;
@ -116,7 +116,7 @@ impl BackendOp for ImapOp {
} else {
let mut response = String::with_capacity(8 * 1024);
let mut conn = self.connection.lock().unwrap();
conn.send_command(format!("EXAMINE \"{}\"", &self.folder_path,).as_bytes())
conn.send_command(format!("EXAMINE \"{}\"", &self.mailbox_path,).as_bytes())
.unwrap();
conn.read_response(&mut response).unwrap();
conn.send_command(format!("UID FETCH {} FLAGS", self.uid).as_bytes())
@ -154,7 +154,7 @@ impl BackendOp for ImapOp {
let mut response = String::with_capacity(8 * 1024);
let mut conn = self.connection.lock().unwrap();
conn.send_command(format!("SELECT \"{}\"", &self.folder_path,).as_bytes())?;