melib: add mailbox delete/create to IMAP
parent
d6f04c9ed3
commit
f208948651
|
@ -64,6 +64,17 @@ use std::sync::{Arc, RwLock};
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use std;
|
use std;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! get_path_hash {
|
||||||
|
($path:expr) => {{
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
$path.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
pub type BackendCreator = Box<
|
pub type BackendCreator = Box<
|
||||||
dyn Fn(
|
dyn Fn(
|
||||||
&AccountSettings,
|
&AccountSettings,
|
||||||
|
@ -232,18 +243,6 @@ impl NotifyFn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
|
||||||
pub enum FolderOperation {
|
|
||||||
Create,
|
|
||||||
Delete,
|
|
||||||
Subscribe,
|
|
||||||
Unsubscribe,
|
|
||||||
Rename(NewFolderName),
|
|
||||||
SetPermissions(FolderPermissions),
|
|
||||||
}
|
|
||||||
|
|
||||||
type NewFolderName = String;
|
|
||||||
|
|
||||||
pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
|
pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
|
||||||
fn is_online(&self) -> Result<()>;
|
fn is_online(&self) -> Result<()>;
|
||||||
fn connect(&mut self) {}
|
fn connect(&mut self) {}
|
||||||
|
@ -264,9 +263,6 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
|
||||||
fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp>;
|
fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp>;
|
||||||
|
|
||||||
fn save(&self, bytes: &[u8], folder: &str, flags: Option<Flag>) -> Result<()>;
|
fn save(&self, bytes: &[u8], folder: &str, flags: Option<Flag>) -> Result<()>;
|
||||||
fn create_folder(&mut self, _path: String) -> Result<Folder> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
|
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -275,6 +271,30 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_folder(&mut self, _path: String) -> Result<Folder> {
|
||||||
|
Err(MeliError::new("Unimplemented."))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_folder(&mut self, _folder_hash: FolderHash) -> Result<()> {
|
||||||
|
Err(MeliError::new("Unimplemented."))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_folder_subscription(&mut self, _folder_hash: FolderHash, _val: bool) -> Result<()> {
|
||||||
|
Err(MeliError::new("Unimplemented."))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rename_folder(&mut self, _folder_hash: FolderHash, _new_path: String) -> Result<Folder> {
|
||||||
|
Err(MeliError::new("Unimplemented."))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_folder_permissions(
|
||||||
|
&mut self,
|
||||||
|
_folder_hash: FolderHash,
|
||||||
|
_val: FolderPermissions,
|
||||||
|
) -> Result<()> {
|
||||||
|
Err(MeliError::new("Unimplemented."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `BackendOp` manages common operations for the various mail backends. They only live for the
|
/// A `BackendOp` manages common operations for the various mail backends. They only live for the
|
||||||
|
@ -319,8 +339,6 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
|
||||||
pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send {
|
pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send {
|
||||||
fn description(&self) -> String;
|
fn description(&self) -> String;
|
||||||
fn as_bytes(&mut self) -> Result<&[u8]>;
|
fn as_bytes(&mut self) -> Result<&[u8]>;
|
||||||
//fn delete(&self) -> ();
|
|
||||||
//fn copy(&self
|
|
||||||
fn fetch_flags(&self) -> Flag;
|
fn fetch_flags(&self) -> Flag;
|
||||||
fn set_flag(&mut self, envelope: &mut Envelope, flag: Flag, value: bool) -> Result<()>;
|
fn set_flag(&mut self, envelope: &mut Envelope, flag: Flag, value: bool) -> Result<()>;
|
||||||
fn set_tag(&mut self, envelope: &mut Envelope, tag: String, value: bool) -> Result<()>;
|
fn set_tag(&mut self, envelope: &mut Envelope, tag: String, value: bool) -> Result<()>;
|
||||||
|
@ -536,3 +554,9 @@ impl Default for FolderPermissions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for FolderPermissions {
|
||||||
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(fmt, "{:#?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use crate::get_path_hash;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod protocol_parser;
|
mod protocol_parser;
|
||||||
|
@ -158,12 +159,12 @@ impl MailBackend for ImapType {
|
||||||
let uid_store = self.uid_store.clone();
|
let uid_store = self.uid_store.clone();
|
||||||
let tag_index = self.tag_index.clone();
|
let tag_index = self.tag_index.clone();
|
||||||
let can_create_flags = self.can_create_flags.clone();
|
let can_create_flags = self.can_create_flags.clone();
|
||||||
let folder_path = folder.path().to_string();
|
|
||||||
let folder_hash = folder.hash();
|
let folder_hash = folder.hash();
|
||||||
let (permissions, folder_exists, no_select, unseen) = {
|
let (permissions, folder_path, folder_exists, no_select, unseen) = {
|
||||||
let f = &self.folders.read().unwrap()[&folder_hash];
|
let f = &self.folders.read().unwrap()[&folder_hash];
|
||||||
(
|
(
|
||||||
f.permissions.clone(),
|
f.permissions.clone(),
|
||||||
|
f.imap_path().to_string(),
|
||||||
f.exists.clone(),
|
f.exists.clone(),
|
||||||
f.no_select,
|
f.no_select,
|
||||||
f.unseen.clone(),
|
f.unseen.clone(),
|
||||||
|
@ -374,7 +375,7 @@ impl MailBackend for ImapType {
|
||||||
Box::new(ImapOp::new(
|
Box::new(ImapOp::new(
|
||||||
uid,
|
uid,
|
||||||
self.folders.read().unwrap()[&folder_hash]
|
self.folders.read().unwrap()[&folder_hash]
|
||||||
.path()
|
.imap_path()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
self.connection.clone(),
|
self.connection.clone(),
|
||||||
self.uid_store.clone(),
|
self.uid_store.clone(),
|
||||||
|
@ -400,7 +401,7 @@ impl MailBackend for ImapType {
|
||||||
}
|
}
|
||||||
|
|
||||||
f_result
|
f_result
|
||||||
.map(|v| v.path().to_string())
|
.map(|v| v.imap_path().to_string())
|
||||||
.ok_or(MeliError::new(format!(
|
.ok_or(MeliError::new(format!(
|
||||||
"Folder with name {} not found.",
|
"Folder with name {} not found.",
|
||||||
folder
|
folder
|
||||||
|
@ -425,70 +426,6 @@ impl MailBackend for ImapType {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
fn folder_operation(&mut self, path: &str, op: FolderOperation) -> Result<()> {
|
|
||||||
use FolderOperation::*;
|
|
||||||
|
|
||||||
match (
|
|
||||||
&op,
|
|
||||||
self.folders
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.values()
|
|
||||||
.any(|f| f.path == path),
|
|
||||||
) {
|
|
||||||
(Create, true) => {
|
|
||||||
return Err(MeliError::new(format!(
|
|
||||||
"Folder named `{}` in account `{}` already exists.",
|
|
||||||
path, self.account_name,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
(op, false) if *op != Create => {
|
|
||||||
return Err(MeliError::new(format!(
|
|
||||||
"No folder named `{}` in account `{}`",
|
|
||||||
path, self.account_name,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
|
||||||
match op {
|
|
||||||
Create => {
|
|
||||||
let mut conn = self.connection.lock()?;
|
|
||||||
conn.send_command(format!("CREATE \"{}\"", path,).as_bytes())?;
|
|
||||||
conn.read_response(&mut response)?;
|
|
||||||
conn.send_command(format!("SUBSCRIBE \"{}\"", path,).as_bytes())?;
|
|
||||||
conn.read_response(&mut response)?;
|
|
||||||
}
|
|
||||||
Rename(dest) => {
|
|
||||||
let mut conn = self.connection.lock()?;
|
|
||||||
conn.send_command(format!("RENAME \"{}\" \"{}\"", path, dest).as_bytes())?;
|
|
||||||
conn.read_response(&mut response)?;
|
|
||||||
}
|
|
||||||
Delete => {
|
|
||||||
let mut conn = self.connection.lock()?;
|
|
||||||
conn.send_command(format!("DELETE \"{}\"", path,).as_bytes())?;
|
|
||||||
conn.read_response(&mut response)?;
|
|
||||||
}
|
|
||||||
Subscribe => {
|
|
||||||
let mut conn = self.connection.lock()?;
|
|
||||||
conn.send_command(format!("SUBSCRIBE \"{}\"", path,).as_bytes())?;
|
|
||||||
conn.read_response(&mut response)?;
|
|
||||||
}
|
|
||||||
Unsubscribe => {
|
|
||||||
let mut conn = self.connection.lock()?;
|
|
||||||
conn.send_command(format!("UNSUBSCRIBE \"{}\"", path,).as_bytes())?;
|
|
||||||
conn.read_response(&mut response)?;
|
|
||||||
}
|
|
||||||
SetPermissions(_new_val) => {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn::std::any::Any {
|
fn as_any(&self) -> &dyn::std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -505,36 +442,191 @@ impl MailBackend for ImapType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_folder(&mut self, path: String) -> Result<Folder> {
|
fn create_folder(&mut self, mut path: String) -> Result<Folder> {
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
/* Must transform path to something the IMAP server will accept
|
||||||
if self
|
*
|
||||||
.folders
|
* Each root mailbox has a hierarchy delimeter reported by the LIST entry. All paths
|
||||||
.read()
|
* must use this delimeter to indicate children of this mailbox.
|
||||||
.unwrap()
|
*
|
||||||
.values()
|
* A new root mailbox should have the default delimeter, which can be found out by issuing
|
||||||
.any(|f| f.path == path)
|
* an empty LIST command as described in RFC3501:
|
||||||
{
|
* C: A101 LIST "" ""
|
||||||
|
* S: * LIST (\Noselect) "/" ""
|
||||||
|
*
|
||||||
|
* The default delimiter for us is '/' just like UNIX paths. I apologise if this
|
||||||
|
* 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);
|
||||||
|
path = path.replace(
|
||||||
|
'/',
|
||||||
|
(root_folder.separator as char).encode_utf8(&mut [0; 4]),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if folders.values().any(|f| f.path == path) {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"Folder named `{}` in account `{}` already exists.",
|
"Folder named `{}` in account `{}` already exists.",
|
||||||
path, self.account_name,
|
path, self.account_name,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
let mut conn_lck = self.connection.lock()?;
|
|
||||||
conn_lck.send_command(debug!(format!("CREATE \"{}\"", path,)).as_bytes())?;
|
let mut response = String::with_capacity(8 * 1024);
|
||||||
conn_lck.read_response(&mut response)?;
|
{
|
||||||
conn_lck.send_command(debug!(format!("SUBSCRIBE \"{}\"", path,)).as_bytes())?;
|
let mut conn_lck = self.connection.lock()?;
|
||||||
conn_lck.read_response(&mut response)?;
|
|
||||||
drop(conn_lck);
|
conn_lck.send_command(format!("CREATE \"{}\"", path,).as_bytes())?;
|
||||||
self.folders.write().unwrap().clear();
|
conn_lck.read_response(&mut response)?;
|
||||||
self.folders().and_then(|f| {
|
conn_lck.send_command(format!("SUBSCRIBE \"{}\"", path,).as_bytes())?;
|
||||||
debug!(f)
|
conn_lck.read_response(&mut response)?;
|
||||||
.into_iter()
|
}
|
||||||
.find(|(_, f)| f.path() == path)
|
let ret: Result<()> = ImapResponse::from(&response).into();
|
||||||
.map(|f| f.1)
|
ret?;
|
||||||
.ok_or(MeliError::new(
|
folders.clear();
|
||||||
"Internal error: could not find folder after creating it?",
|
drop(folders);
|
||||||
|
self.folders().map_err(|err| format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err))?;
|
||||||
|
let new_hash = get_path_hash!(path.as_str());
|
||||||
|
Ok(BackendFolder::clone(
|
||||||
|
&self.folders.read().unwrap()[&new_hash],
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_folder(&mut self, folder_hash: FolderHash) -> Result<()> {
|
||||||
|
let mut folders = self.folders.write().unwrap();
|
||||||
|
let permissions = folders[&folder_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)));
|
||||||
|
}
|
||||||
|
let mut response = String::with_capacity(8 * 1024);
|
||||||
|
{
|
||||||
|
let mut conn_lck = self.connection.lock()?;
|
||||||
|
if folders[&folder_hash].is_subscribed() {
|
||||||
|
conn_lck.send_command(
|
||||||
|
format!("UNSUBSCRIBE \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
|
||||||
|
)?;
|
||||||
|
conn_lck.read_response(&mut response)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !folders[&folder_hash].no_select {
|
||||||
|
/* make sure mailbox is not selected before it gets deleted, otherwise
|
||||||
|
* connection gets dropped by server */
|
||||||
|
if conn_lck
|
||||||
|
.capabilities
|
||||||
|
.iter()
|
||||||
|
.any(|cap| cap.eq_ignore_ascii_case(b"UNSELECT"))
|
||||||
|
{
|
||||||
|
conn_lck.send_command(
|
||||||
|
format!("UNSELECT \"{}\"", folders[&folder_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(),
|
||||||
|
)?;
|
||||||
|
conn_lck.read_response(&mut response)?;
|
||||||
|
conn_lck.send_command(
|
||||||
|
format!("EXAMINE \"{}\"", folders[&folder_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(),
|
||||||
|
)?;
|
||||||
|
conn_lck.read_response(&mut response)?;
|
||||||
|
}
|
||||||
|
let ret: Result<()> = ImapResponse::from(&response).into();
|
||||||
|
if ret.is_ok() {
|
||||||
|
folders.clear();
|
||||||
|
drop(folders);
|
||||||
|
self.folders().map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err))?;
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut response = String::with_capacity(8 * 1024);
|
||||||
|
{
|
||||||
|
let mut conn_lck = self.connection.lock()?;
|
||||||
|
if new_val {
|
||||||
|
conn_lck.send_command(
|
||||||
|
format!("SUBSCRIBE \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
conn_lck.send_command(
|
||||||
|
format!("UNSUBSCRIBE \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
conn_lck.read_response(&mut response)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret: Result<()> = ImapResponse::from(&response).into();
|
||||||
|
if ret.is_ok() {
|
||||||
|
folders.entry(folder_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();
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
let mut response = String::with_capacity(8 * 1024);
|
||||||
|
if folders[&folder_hash].separator != b'/' {
|
||||||
|
new_path = new_path.replace(
|
||||||
|
'/',
|
||||||
|
(folders[&folder_hash].separator as char).encode_utf8(&mut [0; 4]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut conn_lck = self.connection.lock()?;
|
||||||
|
conn_lck.send_command(
|
||||||
|
debug!(format!(
|
||||||
|
"RENAME \"{}\" \"{}\"",
|
||||||
|
folders[&folder_hash].imap_path(),
|
||||||
|
new_path
|
||||||
))
|
))
|
||||||
})
|
.as_bytes(),
|
||||||
|
)?;
|
||||||
|
conn_lck.read_response(&mut response)?;
|
||||||
|
}
|
||||||
|
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],
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_folder_permissions(
|
||||||
|
&mut self,
|
||||||
|
folder_hash: FolderHash,
|
||||||
|
_val: crate::backends::FolderPermissions,
|
||||||
|
) -> Result<()> {
|
||||||
|
let folders = self.folders.write().unwrap();
|
||||||
|
let permissions = folders[&folder_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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(MeliError::new("Unimplemented."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -707,7 +799,9 @@ impl ImapType {
|
||||||
let folders_lck = self.folders.read()?;
|
let folders_lck = self.folders.read()?;
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = String::with_capacity(8 * 1024);
|
||||||
let mut conn = self.connection.lock()?;
|
let mut conn = self.connection.lock()?;
|
||||||
conn.send_command(format!("EXAMINE \"{}\"", folders_lck[&folder_hash].path()).as_bytes())?;
|
conn.send_command(
|
||||||
|
format!("EXAMINE \"{}\"", folders_lck[&folder_hash].imap_path()).as_bytes(),
|
||||||
|
)?;
|
||||||
conn.read_response(&mut response)?;
|
conn.read_response(&mut response)?;
|
||||||
conn.send_command(format!("UID SEARCH CHARSET UTF-8 {}", query).as_bytes())?;
|
conn.send_command(format!("UID SEARCH CHARSET UTF-8 {}", query).as_bytes())?;
|
||||||
conn.read_response(&mut response)?;
|
conn.read_response(&mut response)?;
|
||||||
|
|
|
@ -340,8 +340,14 @@ impl ImapConnection {
|
||||||
|
|
||||||
pub fn read_response(&mut self, ret: &mut String) -> Result<()> {
|
pub fn read_response(&mut self, ret: &mut String) -> Result<()> {
|
||||||
self.try_send(|s| s.read_response(ret))?;
|
self.try_send(|s| s.read_response(ret))?;
|
||||||
let r: Result<()> = ImapResponse::from(&ret).into();
|
let r: ImapResponse = ImapResponse::from(&ret);
|
||||||
r
|
if let ImapResponse::Bye(ref response_code) = r {
|
||||||
|
self.stream = Err(MeliError::new(format!(
|
||||||
|
"Offline: received BYE: {:?}",
|
||||||
|
response_code
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
r.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_lines(&mut self, ret: &mut String, termination_string: String) -> Result<()> {
|
pub fn read_lines(&mut self, ret: &mut String, termination_string: String) -> Result<()> {
|
||||||
|
|
|
@ -25,10 +25,12 @@ use std::sync::{Arc, Mutex, RwLock};
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct ImapFolder {
|
pub struct ImapFolder {
|
||||||
pub(super) hash: FolderHash,
|
pub(super) hash: FolderHash,
|
||||||
|
pub(super) imap_path: String,
|
||||||
pub(super) path: String,
|
pub(super) path: String,
|
||||||
pub(super) name: String,
|
pub(super) name: String,
|
||||||
pub(super) parent: Option<FolderHash>,
|
pub(super) parent: Option<FolderHash>,
|
||||||
pub(super) children: Vec<FolderHash>,
|
pub(super) children: Vec<FolderHash>,
|
||||||
|
pub separator: u8,
|
||||||
pub usage: Arc<RwLock<SpecialUsageMailbox>>,
|
pub usage: Arc<RwLock<SpecialUsageMailbox>>,
|
||||||
pub no_select: bool,
|
pub no_select: bool,
|
||||||
pub is_subscribed: bool,
|
pub is_subscribed: bool,
|
||||||
|
@ -38,6 +40,12 @@ pub struct ImapFolder {
|
||||||
pub unseen: Arc<Mutex<usize>>,
|
pub unseen: Arc<Mutex<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ImapFolder {
|
||||||
|
pub fn imap_path(&self) -> &str {
|
||||||
|
&self.imap_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BackendFolder for ImapFolder {
|
impl BackendFolder for ImapFolder {
|
||||||
fn hash(&self) -> FolderHash {
|
fn hash(&self) -> FolderHash {
|
||||||
self.hash
|
self.hash
|
||||||
|
@ -79,7 +87,6 @@ impl BackendFolder for ImapFolder {
|
||||||
}
|
}
|
||||||
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> {
|
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> {
|
||||||
self.is_subscribed = new_val;
|
self.is_subscribed = new_val;
|
||||||
// FIXME: imap subscribe
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,8 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::email::parser::BytesExt;
|
use crate::email::parser::BytesExt;
|
||||||
|
use crate::get_path_hash;
|
||||||
use nom::{digit, is_digit, rest, IResult};
|
use nom::{digit, is_digit, rest, IResult};
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -65,54 +64,55 @@ pub enum ResponseCode {
|
||||||
Uidvalidity(UID),
|
Uidvalidity(UID),
|
||||||
/// Followed by a decimal number, indicates the number of the first message without the \Seen flag set.
|
/// Followed by a decimal number, indicates the number of the first message without the \Seen flag set.
|
||||||
Unseen(usize),
|
Unseen(usize),
|
||||||
|
|
||||||
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseCode {
|
impl ResponseCode {
|
||||||
fn from(val: &str) -> Option<ResponseCode> {
|
fn from(val: &str) -> ResponseCode {
|
||||||
|
use ResponseCode::*;
|
||||||
if !val.starts_with("[") {
|
if !val.starts_with("[") {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let val = &val[1..];
|
let val = &val[1..];
|
||||||
use ResponseCode::*;
|
|
||||||
if val.starts_with("BADCHARSET") {
|
if val.starts_with("BADCHARSET") {
|
||||||
Some(Badcharset)
|
Badcharset
|
||||||
} else if val.starts_with("READONLY") {
|
} else if val.starts_with("READONLY") {
|
||||||
Some(ReadOnly)
|
ReadOnly
|
||||||
} else if val.starts_with("READWRITE") {
|
} else if val.starts_with("READWRITE") {
|
||||||
Some(ReadWrite)
|
ReadWrite
|
||||||
} else if val.starts_with("TRYCREATE") {
|
} else if val.starts_with("TRYCREATE") {
|
||||||
Some(Trycreate)
|
Trycreate
|
||||||
} else if val.starts_with("UIDNEXT") {
|
} else if val.starts_with("UIDNEXT") {
|
||||||
//FIXME
|
//FIXME
|
||||||
Some(Uidnext(0))
|
Uidnext(0)
|
||||||
} else if val.starts_with("UIDVALIDITY") {
|
} else if val.starts_with("UIDVALIDITY") {
|
||||||
//FIXME
|
//FIXME
|
||||||
Some(Uidvalidity(0))
|
Uidvalidity(0)
|
||||||
} else if val.starts_with("UNSEEN") {
|
} else if val.starts_with("UNSEEN") {
|
||||||
//FIXME
|
//FIXME
|
||||||
Some(Unseen(0))
|
Unseen(0)
|
||||||
} else {
|
} else {
|
||||||
let msg = &val[val.as_bytes().find(b"] ").unwrap() + 1..].trim();
|
let msg = &val[val.as_bytes().find(b"] ").unwrap() + 1..].trim();
|
||||||
Some(Alert(msg.to_string()))
|
Alert(msg.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum ImapResponse {
|
pub enum ImapResponse {
|
||||||
Ok(Option<ResponseCode>),
|
Ok(ResponseCode),
|
||||||
No(Option<ResponseCode>),
|
No(ResponseCode),
|
||||||
Bad(Option<ResponseCode>),
|
Bad(ResponseCode),
|
||||||
Preauth(Option<ResponseCode>),
|
Preauth(ResponseCode),
|
||||||
Bye(Option<ResponseCode>),
|
Bye(ResponseCode),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsRef<str>> From<T> for ImapResponse {
|
impl<T: AsRef<str>> From<T> for ImapResponse {
|
||||||
fn from(val: T) -> ImapResponse {
|
fn from(val: T) -> ImapResponse {
|
||||||
let val: &str = val.as_ref().split_rn().last().unwrap_or(val.as_ref());
|
let val: &str = val.as_ref().split_rn().last().unwrap_or(val.as_ref());
|
||||||
debug!(&val);
|
debug!(&val);
|
||||||
assert!(val.starts_with("M"));
|
|
||||||
let mut val = val[val.as_bytes().find(b" ").unwrap() + 1..].trim();
|
let mut val = val[val.as_bytes().find(b" ").unwrap() + 1..].trim();
|
||||||
// M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n
|
// M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n
|
||||||
if val.ends_with(" secs).") {
|
if val.ends_with(" secs).") {
|
||||||
|
@ -139,8 +139,9 @@ impl Into<Result<()>> for ImapResponse {
|
||||||
fn into(self) -> Result<()> {
|
fn into(self) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Self::Ok(_) | Self::Preauth(_) | Self::Bye(_) => Ok(()),
|
Self::Ok(_) | Self::Preauth(_) | Self::Bye(_) => Ok(()),
|
||||||
Self::No(Some(ResponseCode::Alert(msg)))
|
Self::No(ResponseCode::Alert(msg)) | Self::Bad(ResponseCode::Alert(msg)) => {
|
||||||
| Self::Bad(Some(ResponseCode::Alert(msg))) => Err(MeliError::new(msg)),
|
Err(MeliError::new(msg))
|
||||||
|
}
|
||||||
Self::No(_) => Err(MeliError::new("IMAP NO Response.")),
|
Self::No(_) => Err(MeliError::new("IMAP NO Response.")),
|
||||||
Self::Bad(_) => Err(MeliError::new("IMAP BAD Response.")),
|
Self::Bad(_) => Err(MeliError::new("IMAP BAD Response.")),
|
||||||
}
|
}
|
||||||
|
@ -149,7 +150,7 @@ impl Into<Result<()>> for ImapResponse {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_imap_response() {
|
fn test_imap_response() {
|
||||||
assert_eq!(ImapResponse::from("M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n"), ImapResponse::No(Some(ResponseCode::Alert("Invalid mailbox name: Name must not have '/' characters".to_string()))));
|
assert_eq!(ImapResponse::from("M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n"), ImapResponse::No(ResponseCode::Alert("Invalid mailbox name: Name must not have '/' characters".to_string())));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for ImapLineIterator<'a> {
|
impl<'a> Iterator for ImapLineIterator<'a> {
|
||||||
|
@ -206,13 +207,7 @@ macro_rules! dbg_dmp (
|
||||||
dbg_dmp!($i, call!($f));
|
dbg_dmp!($i, call!($f));
|
||||||
);
|
);
|
||||||
);
|
);
|
||||||
macro_rules! get_path_hash {
|
|
||||||
($path:expr) => {{
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
$path.hash(&mut hasher);
|
|
||||||
hasher.finish()
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
* LIST (\HasNoChildren) "." INBOX.Sent
|
* LIST (\HasNoChildren) "." INBOX.Sent
|
||||||
* LIST (\HasChildren) "." INBOX
|
* LIST (\HasChildren) "." INBOX
|
||||||
|
@ -243,14 +238,20 @@ named!(
|
||||||
let _ = f.set_special_usage(SpecialUsageMailbox::Drafts);
|
let _ = f.set_special_usage(SpecialUsageMailbox::Drafts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f.hash = get_path_hash!(path);
|
f.imap_path = String::from_utf8_lossy(path).into();
|
||||||
f.path = String::from_utf8_lossy(path).into();
|
f.hash = get_path_hash!(&f.imap_path);
|
||||||
f.name = if let Some(pos) = path.iter().rposition(|&c| c == separator) {
|
f.path = if separator == b'/' {
|
||||||
f.parent = Some(get_path_hash!(&path[..pos]));
|
f.imap_path.clone()
|
||||||
String::from_utf8_lossy(&path[pos + 1..]).into()
|
|
||||||
} else {
|
} else {
|
||||||
f.path.clone()
|
f.imap_path.replace(separator as char, "/")
|
||||||
};
|
};
|
||||||
|
f.name = if let Some(pos) = f.imap_path.as_bytes().iter().rposition(|&c| c == separator) {
|
||||||
|
f.parent = Some(get_path_hash!(&f.imap_path[..pos]));
|
||||||
|
f.imap_path[pos + 1..].to_string()
|
||||||
|
} else {
|
||||||
|
f.imap_path.clone()
|
||||||
|
};
|
||||||
|
f.separator = separator;
|
||||||
|
|
||||||
debug!(f)
|
debug!(f)
|
||||||
})
|
})
|
||||||
|
|
|
@ -149,7 +149,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
|
||||||
folder_hash,
|
folder_hash,
|
||||||
work_context,
|
work_context,
|
||||||
thread_id,
|
thread_id,
|
||||||
conn.send_command(format!("SELECT \"{}\"", folder.path()).as_bytes())
|
conn.send_command(format!("SELECT \"{}\"", folder.imap_path()).as_bytes())
|
||||||
conn.read_response(&mut response)
|
conn.read_response(&mut response)
|
||||||
);
|
);
|
||||||
debug!("select response {}", &response);
|
debug!("select response {}", &response);
|
||||||
|
@ -531,7 +531,7 @@ fn examine_updates(
|
||||||
folder_hash,
|
folder_hash,
|
||||||
work_context,
|
work_context,
|
||||||
thread_id,
|
thread_id,
|
||||||
conn.send_command(format!("EXAMINE \"{}\"", folder.path()).as_bytes())
|
conn.send_command(format!("EXAMINE \"{}\"", folder.imap_path()).as_bytes())
|
||||||
conn.read_response(&mut response)
|
conn.read_response(&mut response)
|
||||||
);
|
);
|
||||||
match protocol_parser::select_response(&response) {
|
match protocol_parser::select_response(&response) {
|
||||||
|
|
|
@ -63,16 +63,6 @@ pub trait Method<OBJ: Object>: Serialize {
|
||||||
const NAME: &'static str;
|
const NAME: &'static str;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! get_path_hash {
|
|
||||||
($path:expr) => {{
|
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
$path.hash(&mut hasher);
|
|
||||||
hasher.finish()
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
static USING: &'static [&'static str] = &["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"];
|
static USING: &'static [&'static str] = &["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"];
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
|
|
@ -34,12 +34,7 @@ extern crate notify;
|
||||||
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use std::sync::mpsc::channel;
|
|
||||||
//use std::sync::mpsc::sync_channel;
|
|
||||||
//use std::sync::mpsc::SyncSender;
|
|
||||||
//use std::time::Duration;
|
|
||||||
use fnv::{FnvHashMap, FnvHashSet, FnvHasher};
|
use fnv::{FnvHashMap, FnvHashSet, FnvHasher};
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
@ -48,6 +43,7 @@ use std::ops::{Deref, DerefMut};
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
use std::result;
|
use std::result;
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
|
@ -132,7 +128,6 @@ macro_rules! path_is_new {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! get_path_hash {
|
macro_rules! get_path_hash {
|
||||||
($path:expr) => {{
|
($path:expr) => {{
|
||||||
let mut path = $path.clone();
|
let mut path = $path.clone();
|
||||||
|
@ -145,9 +140,7 @@ macro_rules! get_path_hash {
|
||||||
path.pop();
|
path.pop();
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut hasher = DefaultHasher::new();
|
crate::get_path_hash!(path)
|
||||||
path.hash(&mut hasher);
|
|
||||||
hasher.finish()
|
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -642,6 +635,64 @@ impl MailBackend for MaildirType {
|
||||||
fn as_any(&self) -> &dyn::std::any::Any {
|
fn as_any(&self) -> &dyn::std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_folder(&mut self, new_path: String) -> Result<Folder> {
|
||||||
|
let mut path = self.path.clone();
|
||||||
|
path.push(&new_path);
|
||||||
|
if !path.starts_with(&self.path) {
|
||||||
|
return Err(MeliError::new(format!("Path given (`{}`) is absolute. Please provide a path relative to the account's root folder.", &new_path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::create_dir(&path)?;
|
||||||
|
/* create_dir does not create intermediate directories (like `mkdir -p`), so the parent must be a valid
|
||||||
|
* folder at this point. */
|
||||||
|
|
||||||
|
let parent = path.parent().and_then(|p| {
|
||||||
|
self.folders
|
||||||
|
.iter()
|
||||||
|
.find(|(_, f)| f.fs_path == p)
|
||||||
|
.map(|item| *item.0)
|
||||||
|
});
|
||||||
|
|
||||||
|
let folder_hash = get_path_hash!(&path);
|
||||||
|
let new_folder = MaildirFolder {
|
||||||
|
hash: folder_hash,
|
||||||
|
path: PathBuf::from(&new_path),
|
||||||
|
name: new_path,
|
||||||
|
fs_path: path,
|
||||||
|
parent,
|
||||||
|
children: vec![],
|
||||||
|
usage: Default::default(),
|
||||||
|
is_subscribed: true,
|
||||||
|
permissions: Default::default(),
|
||||||
|
unseen: Default::default(),
|
||||||
|
total: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ret = BackendFolder::clone(debug!(&new_folder));
|
||||||
|
self.folders.insert(folder_hash, new_folder);
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_folder(&mut self, _folder_hash: FolderHash) -> Result<()> {
|
||||||
|
Err(MeliError::new("Unimplemented."))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_folder_subscription(&mut self, _folder_hash: FolderHash, _val: bool) -> Result<()> {
|
||||||
|
Err(MeliError::new("Unimplemented."))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rename_folder(&mut self, _folder_hash: FolderHash, _new_path: String) -> Result<Folder> {
|
||||||
|
Err(MeliError::new("Unimplemented."))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_folder_permissions(
|
||||||
|
&mut self,
|
||||||
|
_folder_hash: FolderHash,
|
||||||
|
_val: crate::backends::FolderPermissions,
|
||||||
|
) -> Result<()> {
|
||||||
|
Err(MeliError::new("Unimplemented."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaildirType {
|
impl MaildirType {
|
||||||
|
|
|
@ -34,6 +34,7 @@ use crate::conf::AccountSettings;
|
||||||
use crate::email::parser::BytesExt;
|
use crate::email::parser::BytesExt;
|
||||||
use crate::email::*;
|
use crate::email::*;
|
||||||
use crate::error::{MeliError, Result};
|
use crate::error::{MeliError, Result};
|
||||||
|
use crate::get_path_hash;
|
||||||
use crate::shellexpand::ShellExpandTrait;
|
use crate::shellexpand::ShellExpandTrait;
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use libc;
|
use libc;
|
||||||
|
@ -41,9 +42,7 @@ use memmap::{Mmap, Protection};
|
||||||
use nom::{IResult, Needed};
|
use nom::{IResult, Needed};
|
||||||
extern crate notify;
|
extern crate notify;
|
||||||
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
|
@ -74,14 +73,6 @@ fn get_rw_lock_blocking(f: &File) {
|
||||||
assert!(-1 != ret_val);
|
assert!(-1 != ret_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! get_path_hash {
|
|
||||||
($path:expr) => {{
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
$path.hash(&mut hasher);
|
|
||||||
hasher.finish()
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct MboxFolder {
|
struct MboxFolder {
|
||||||
hash: FolderHash,
|
hash: FolderHash,
|
||||||
|
|
|
@ -27,8 +27,8 @@ use super::{AccountConf, FileFolderConf};
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
|
use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
|
||||||
use melib::backends::{
|
use melib::backends::{
|
||||||
BackendOp, Backends, Folder, FolderHash, FolderOperation, MailBackend, NotifyFn, ReadOnlyOp,
|
BackendOp, Backends, Folder, FolderHash, MailBackend, NotifyFn, ReadOnlyOp, RefreshEvent,
|
||||||
RefreshEvent, RefreshEventConsumer, RefreshEventKind, SpecialUsageMailbox,
|
RefreshEventConsumer, RefreshEventKind, SpecialUsageMailbox,
|
||||||
};
|
};
|
||||||
use melib::error::{MeliError, Result};
|
use melib::error::{MeliError, Result};
|
||||||
use melib::mailbox::*;
|
use melib::mailbox::*;
|
||||||
|
@ -964,67 +964,112 @@ impl Account {
|
||||||
&self.collection.threads[&f].thread_nodes()[&h]
|
&self.collection.threads[&f].thread_nodes()[&h]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn folder_operation(&mut self, path: &str, op: FolderOperation) -> Result<()> {
|
pub fn folder_operation(
|
||||||
|
&mut self,
|
||||||
|
op: crate::execute::actions::FolderOperation,
|
||||||
|
) -> Result<String> {
|
||||||
|
use crate::execute::actions::FolderOperation;
|
||||||
|
if self.settings.account.read_only() {
|
||||||
|
return Err(MeliError::new("Account is read-only."));
|
||||||
|
}
|
||||||
match op {
|
match op {
|
||||||
FolderOperation::Create => {
|
FolderOperation::Create(path) => {
|
||||||
if self.settings.account.read_only() {
|
let mut folder = self
|
||||||
Err(MeliError::new("Account is read-only."))
|
.backend
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.create_folder(path.to_string())?;
|
||||||
|
self.sender
|
||||||
|
.send(ThreadEvent::UIEvent(UIEvent::MailboxCreate((
|
||||||
|
self.index,
|
||||||
|
folder.hash(),
|
||||||
|
))))
|
||||||
|
.unwrap();
|
||||||
|
let mut new = FileFolderConf::default();
|
||||||
|
new.folder_conf.subscribe = super::ToggleFlag::InternalVal(true);
|
||||||
|
new.folder_conf.usage = if folder.special_usage() != SpecialUsageMailbox::Normal {
|
||||||
|
Some(folder.special_usage())
|
||||||
} else {
|
} else {
|
||||||
let mut folder = self
|
let tmp = SpecialUsageMailbox::detect_usage(folder.name());
|
||||||
.backend
|
if tmp != Some(SpecialUsageMailbox::Normal) && tmp != None {
|
||||||
.write()
|
let _ = folder.set_special_usage(tmp.unwrap());
|
||||||
.unwrap()
|
}
|
||||||
.create_folder(path.to_string())?;
|
tmp
|
||||||
let mut new = FileFolderConf::default();
|
};
|
||||||
new.folder_conf.subscribe = super::ToggleFlag::InternalVal(true);
|
|
||||||
new.folder_conf.usage = if folder.special_usage() != SpecialUsageMailbox::Normal
|
|
||||||
{
|
|
||||||
Some(folder.special_usage())
|
|
||||||
} else {
|
|
||||||
let tmp = SpecialUsageMailbox::detect_usage(folder.name());
|
|
||||||
if tmp != Some(SpecialUsageMailbox::Normal) && tmp != None {
|
|
||||||
let _ = folder.set_special_usage(tmp.unwrap());
|
|
||||||
}
|
|
||||||
tmp
|
|
||||||
};
|
|
||||||
|
|
||||||
self.folder_confs.insert(folder.hash(), new);
|
self.folder_confs.insert(folder.hash(), new);
|
||||||
self.folder_names
|
self.folder_names
|
||||||
.insert(folder.hash(), folder.path().to_string());
|
.insert(folder.hash(), folder.path().to_string());
|
||||||
self.folders.insert(
|
self.folders.insert(
|
||||||
folder.hash(),
|
folder.hash(),
|
||||||
MailboxEntry::Parsing(
|
MailboxEntry::Parsing(
|
||||||
Mailbox::new(folder.clone(), &FnvHashMap::default()),
|
Mailbox::new(folder.clone(), &FnvHashMap::default()),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
self.workers.insert(
|
self.workers.insert(
|
||||||
folder.hash(),
|
folder.hash(),
|
||||||
Account::new_worker(
|
Account::new_worker(
|
||||||
folder.clone(),
|
folder.clone(),
|
||||||
&mut self.backend,
|
&mut self.backend,
|
||||||
&self.work_context,
|
&self.work_context,
|
||||||
self.notify_fn.clone(),
|
self.notify_fn.clone(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
self.collection
|
self.collection
|
||||||
.threads
|
.threads
|
||||||
.insert(folder.hash(), Threads::default());
|
.insert(folder.hash(), Threads::default());
|
||||||
self.ref_folders.insert(folder.hash(), folder);
|
self.ref_folders = self.backend.read().unwrap().folders()?;
|
||||||
build_folders_order(
|
build_folders_order(
|
||||||
&self.folder_confs,
|
&self.folder_confs,
|
||||||
&mut self.tree,
|
&mut self.tree,
|
||||||
&self.ref_folders,
|
&self.ref_folders,
|
||||||
&mut self.folders_order,
|
&mut self.folders_order,
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(format!("`{}` successfully created.", &path))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
FolderOperation::Delete => Err(MeliError::new("Not implemented.")),
|
FolderOperation::Delete(path) => {
|
||||||
FolderOperation::Subscribe => Err(MeliError::new("Not implemented.")),
|
if self.ref_folders.len() == 1 {
|
||||||
FolderOperation::Unsubscribe => Err(MeliError::new("Not implemented.")),
|
return Err(MeliError::new("Cannot delete only mailbox."));
|
||||||
FolderOperation::Rename(_) => Err(MeliError::new("Not implemented.")),
|
}
|
||||||
|
let folder_hash = if let Some((folder_hash, _)) =
|
||||||
|
self.ref_folders.iter().find(|(_, f)| f.path() == path)
|
||||||
|
{
|
||||||
|
*folder_hash
|
||||||
|
} else {
|
||||||
|
return Err(MeliError::new("Mailbox with that path not found."));
|
||||||
|
};
|
||||||
|
self.backend.write().unwrap().delete_folder(folder_hash)?;
|
||||||
|
self.sender
|
||||||
|
.send(ThreadEvent::UIEvent(UIEvent::MailboxDelete((
|
||||||
|
self.index,
|
||||||
|
folder_hash,
|
||||||
|
))))
|
||||||
|
.unwrap();
|
||||||
|
self.folders.remove(&folder_hash);
|
||||||
|
self.ref_folders = self.backend.read().unwrap().folders()?;
|
||||||
|
self.folder_confs.remove(&folder_hash);
|
||||||
|
if let Some(pos) = self.folders_order.iter().position(|&h| h == folder_hash) {
|
||||||
|
self.folders_order.remove(pos);
|
||||||
|
}
|
||||||
|
self.folder_names.remove(&folder_hash);
|
||||||
|
if let Some(pos) = self.tree.iter().position(|n| n.hash == folder_hash) {
|
||||||
|
self.tree.remove(pos);
|
||||||
|
}
|
||||||
|
if self.sent_folder == Some(folder_hash) {
|
||||||
|
self.sent_folder = None;
|
||||||
|
}
|
||||||
|
self.collection.threads.remove(&folder_hash);
|
||||||
|
self.workers.remove(&folder_hash); // FIXME Kill worker as well
|
||||||
|
|
||||||
|
// FIXME remove from settings as well
|
||||||
|
|
||||||
|
Ok(format!("'`{}` has been deleted.", &path))
|
||||||
|
}
|
||||||
|
FolderOperation::Subscribe(_) => Err(MeliError::new("Not implemented.")),
|
||||||
|
FolderOperation::Unsubscribe(_) => Err(MeliError::new("Not implemented.")),
|
||||||
|
FolderOperation::Rename(_, _) => Err(MeliError::new("Not implemented.")),
|
||||||
FolderOperation::SetPermissions(_) => Err(MeliError::new("Not implemented.")),
|
FolderOperation::SetPermissions(_) => Err(MeliError::new("Not implemented.")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,11 @@
|
||||||
|
|
||||||
/*! A parser module for user commands passed through the Execute mode.
|
/*! A parser module for user commands passed through the Execute mode.
|
||||||
*/
|
*/
|
||||||
use melib::backends::FolderOperation;
|
|
||||||
pub use melib::thread::{SortField, SortOrder};
|
pub use melib::thread::{SortField, SortOrder};
|
||||||
use nom::{digit, not_line_ending, IResult};
|
use nom::{digit, not_line_ending, IResult};
|
||||||
use std;
|
use std;
|
||||||
pub mod actions;
|
pub mod actions;
|
||||||
|
use actions::FolderOperation;
|
||||||
pub mod history;
|
pub mod history;
|
||||||
pub use crate::actions::AccountAction::{self, *};
|
pub use crate::actions::AccountAction::{self, *};
|
||||||
pub use crate::actions::Action::{self, *};
|
pub use crate::actions::Action::{self, *};
|
||||||
|
@ -78,7 +78,7 @@ define_commands!([
|
||||||
map!(ws!(tag!("seen")), |_| Listing(SetSeen))
|
map!(ws!(tag!("seen")), |_| Listing(SetSeen))
|
||||||
| map!(ws!(tag!("unseen")), |_| Listing(SetUnseen))
|
| map!(ws!(tag!("unseen")), |_| Listing(SetUnseen))
|
||||||
)
|
)
|
||||||
) | map!(ws!(tag!("delete")), |_| Listing(Delete))
|
) | map!(preceded!(tag!("delete"), eof!()), |_| Listing(Delete))
|
||||||
)
|
)
|
||||||
); )
|
); )
|
||||||
},
|
},
|
||||||
|
@ -255,8 +255,8 @@ define_commands!([
|
||||||
ws!(tag!("create-folder"))
|
ws!(tag!("create-folder"))
|
||||||
>> account: quoted_argument
|
>> account: quoted_argument
|
||||||
>> is_a!(" ")
|
>> is_a!(" ")
|
||||||
>> path: map_res!(call!(not_line_ending), std::str::from_utf8)
|
>> path: quoted_argument
|
||||||
>> (Folder(account.to_string(), path.to_string(), FolderOperation::Create))
|
>> (Folder(account.to_string(), FolderOperation::Create(path.to_string())))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
)
|
)
|
||||||
|
@ -269,8 +269,8 @@ define_commands!([
|
||||||
ws!(tag!("subscribe-folder"))
|
ws!(tag!("subscribe-folder"))
|
||||||
>> account: quoted_argument
|
>> account: quoted_argument
|
||||||
>> is_a!(" ")
|
>> is_a!(" ")
|
||||||
>> path: map_res!(call!(not_line_ending), std::str::from_utf8)
|
>> path: quoted_argument
|
||||||
>> (Folder(account.to_string(), path.to_string(), FolderOperation::Subscribe))
|
>> (Folder(account.to_string(), FolderOperation::Subscribe(path.to_string())))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
)
|
)
|
||||||
|
@ -283,8 +283,8 @@ define_commands!([
|
||||||
ws!(tag!("unsubscribe-folder"))
|
ws!(tag!("unsubscribe-folder"))
|
||||||
>> account: quoted_argument
|
>> account: quoted_argument
|
||||||
>> is_a!(" ")
|
>> is_a!(" ")
|
||||||
>> path: map_res!(call!(not_line_ending), std::str::from_utf8)
|
>> path: quoted_argument
|
||||||
>> (Folder(account.to_string(), path.to_string(), FolderOperation::Unsubscribe))
|
>> (Folder(account.to_string(), FolderOperation::Unsubscribe(path.to_string())))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
)
|
)
|
||||||
|
@ -299,8 +299,8 @@ define_commands!([
|
||||||
>> is_a!(" ")
|
>> is_a!(" ")
|
||||||
>> src: quoted_argument
|
>> src: quoted_argument
|
||||||
>> is_a!(" ")
|
>> is_a!(" ")
|
||||||
>> dest: map_res!(call!(not_line_ending), std::str::from_utf8)
|
>> dest: quoted_argument
|
||||||
>> (Folder(account.to_string(), src.to_string(), FolderOperation::Rename(dest.to_string())))
|
>> (Folder(account.to_string(), FolderOperation::Rename(src.to_string(), dest.to_string())))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
)
|
)
|
||||||
|
@ -314,7 +314,7 @@ define_commands!([
|
||||||
>> account: quoted_argument
|
>> account: quoted_argument
|
||||||
>> is_a!(" ")
|
>> is_a!(" ")
|
||||||
>> path: quoted_argument
|
>> path: quoted_argument
|
||||||
>> (Folder(account.to_string(), path.to_string(), FolderOperation::Delete))
|
>> (Folder(account.to_string(), FolderOperation::Delete(path.to_string())))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::components::Component;
|
use crate::components::Component;
|
||||||
use melib::backends::{FolderHash, FolderOperation};
|
use melib::backends::FolderHash;
|
||||||
pub use melib::thread::{SortField, SortOrder};
|
pub use melib::thread::{SortField, SortOrder};
|
||||||
use melib::{Draft, EnvelopeHash};
|
use melib::{Draft, EnvelopeHash};
|
||||||
|
|
||||||
|
@ -86,6 +86,17 @@ pub enum AccountAction {
|
||||||
ReIndex,
|
ReIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FolderOperation {
|
||||||
|
Create(NewFolderPath),
|
||||||
|
Delete(FolderPath),
|
||||||
|
Subscribe(FolderPath),
|
||||||
|
Unsubscribe(FolderPath),
|
||||||
|
Rename(FolderPath, NewFolderPath),
|
||||||
|
// Placeholder
|
||||||
|
SetPermissions(FolderPath),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Listing(ListingAction),
|
Listing(ListingAction),
|
||||||
|
@ -99,9 +110,10 @@ pub enum Action {
|
||||||
SetEnv(String, String),
|
SetEnv(String, String),
|
||||||
PrintEnv(String),
|
PrintEnv(String),
|
||||||
Compose(ComposeAction),
|
Compose(ComposeAction),
|
||||||
Folder(AccountName, FolderPath, FolderOperation),
|
Folder(AccountName, FolderOperation),
|
||||||
AccountAction(AccountName, AccountAction),
|
AccountAction(AccountName, AccountAction),
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountName = String;
|
type AccountName = String;
|
||||||
type FolderPath = String;
|
type FolderPath = String;
|
||||||
|
type NewFolderPath = String;
|
||||||
|
|
27
src/state.rs
27
src/state.rs
|
@ -612,24 +612,27 @@ impl State {
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Folder(account_name, path, op) => {
|
Folder(account_name, op) => {
|
||||||
if let Some(account) = self
|
if let Some(account) = self
|
||||||
.context
|
.context
|
||||||
.accounts
|
.accounts
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.find(|a| a.name() == account_name)
|
.find(|a| a.name() == account_name)
|
||||||
{
|
{
|
||||||
if let Err(e) = account.folder_operation(&path, op) {
|
match account.folder_operation(op) {
|
||||||
self.context.replies.push_back(UIEvent::StatusEvent(
|
Err(err) => {
|
||||||
StatusEvent::DisplayMessage(e.to_string()),
|
self.context.replies.push_back(UIEvent::StatusEvent(
|
||||||
));
|
StatusEvent::DisplayMessage(err.to_string()),
|
||||||
} else {
|
));
|
||||||
self.context.replies.push_back(UIEvent::StatusEvent(
|
}
|
||||||
StatusEvent::DisplayMessage(format!(
|
Ok(msg) => {
|
||||||
"{} succesfully created in `{}`",
|
self.context.replies.push_back(UIEvent::StatusEvent(
|
||||||
path, account_name
|
StatusEvent::DisplayMessage(format!(
|
||||||
)),
|
"`{}`: {}",
|
||||||
));
|
account_name, msg
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.context.replies.push_back(UIEvent::StatusEvent(
|
self.context.replies.push_back(UIEvent::StatusEvent(
|
||||||
|
|
|
@ -111,6 +111,8 @@ pub enum UIEvent {
|
||||||
Action(Action),
|
Action(Action),
|
||||||
StatusEvent(StatusEvent),
|
StatusEvent(StatusEvent),
|
||||||
MailboxUpdate((usize, FolderHash)), // (account_idx, mailbox_idx)
|
MailboxUpdate((usize, FolderHash)), // (account_idx, mailbox_idx)
|
||||||
|
MailboxDelete((usize, FolderHash)),
|
||||||
|
MailboxCreate((usize, FolderHash)),
|
||||||
ComponentKill(Uuid),
|
ComponentKill(Uuid),
|
||||||
WorkerProgress(FolderHash),
|
WorkerProgress(FolderHash),
|
||||||
StartupCheck(FolderHash),
|
StartupCheck(FolderHash),
|
||||||
|
|
Loading…
Reference in New Issue