Browse Source

melib/imap: add mailbox creation ability

async
Manos Pitsidianakis 2 years ago
parent
commit
7f8c638361
Signed by: epilys GPG Key ID: 73627C2F690DF710
  1. 2
      melib/src/backends.rs
  2. 32
      melib/src/backends/imap.rs
  3. 384
      melib/src/backends/imap/connection.rs
  4. 119
      melib/src/backends/imap/protocol_parser.rs
  5. 199
      ui/src/conf/accounts.rs
  6. 44
      ui/src/execute.rs
  7. 1
      ui/src/lib.rs
  8. 4
      ui/src/plugins/backend.rs
  9. 8
      ui/src/state.rs
  10. 2
      ui/src/types.rs

2
melib/src/backends.rs

@ -264,7 +264,7 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp>;
fn save(&self, bytes: &[u8], folder: &str, flags: Option<Flag>) -> Result<()>;
fn create_folder(&mut self, name: NewFolderName) -> Result<Folder> {
fn create_folder(&mut self, path: String) -> Result<Folder> {
unimplemented!()
}
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {

32
melib/src/backends/imap.rs

@ -517,6 +517,38 @@ impl MailBackend for ImapType {
None
}
}
fn create_folder(&mut self, path: String) -> Result<Folder> {
let mut response = String::with_capacity(8 * 1024);
if self
.folders
.read()
.unwrap()
.values()
.any(|f| f.path == path)
{
return Err(MeliError::new(format!(
"Folder named `{}` in account `{}` already exists.",
path, self.account_name,
)));
}
let mut conn_lck = self.connection.lock()?;
conn_lck.send_command(debug!(format!("CREATE \"{}\"", path,)).as_bytes())?;
conn_lck.read_response(&mut response)?;
conn_lck.send_command(debug!(format!("SUBSCRIBE \"{}\"", path,)).as_bytes())?;
conn_lck.read_response(&mut response)?;
drop(conn_lck);
self.folders.write().unwrap().clear();
self.folders().and_then(|f| {
debug!(f)
.into_iter()
.find(|(_, f)| f.path() == path)
.map(|f| f.1)
.ok_or(MeliError::new(
"Internal error: could not find folder after creating it?",
))
})
}
}
impl ImapType {

384
melib/src/backends/imap/connection.rs

@ -19,7 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::protocol_parser::ImapLineSplit;
use super::protocol_parser::{ImapLineSplit, ImapResponse};
use crate::email::parser::BytesExt;
use crate::error::*;
use std::io::Read;
@ -27,6 +27,7 @@ use std::io::Write;
extern crate native_tls;
use fnv::FnvHashSet;
use native_tls::TlsConnector;
use std::borrow::Cow;
use std::iter::FromIterator;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
@ -56,96 +57,6 @@ impl Drop for ImapStream {
}
impl ImapStream {
pub fn read_response(&mut self, ret: &mut String) -> Result<()> {
let id = format!("M{} ", self.cmd_id - 1);
self.read_lines(ret, &id)
}
pub fn read_lines(&mut self, ret: &mut String, termination_string: &str) -> Result<()> {
let mut buf: [u8; 1024] = [0; 1024];
ret.clear();
let mut last_line_idx: usize = 0;
loop {
match self.stream.read(&mut buf) {
Ok(0) => break,
Ok(b) => {
ret.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..b]) });
if let Some(mut pos) = ret[last_line_idx..].rfind("\r\n") {
if ret[last_line_idx..].starts_with("* BYE") {
return Err(MeliError::new("Disconnected"));
}
if let Some(prev_line) =
ret[last_line_idx..pos + last_line_idx].rfind("\r\n")
{
last_line_idx += prev_line + 2;
pos -= prev_line + 2;
}
if pos + "\r\n".len() == ret[last_line_idx..].len() {
if !termination_string.is_empty()
&& ret[last_line_idx..].starts_with(termination_string)
{
debug!(&ret[last_line_idx..]);
ret.replace_range(last_line_idx.., "");
break;
} else if termination_string.is_empty() {
break;
}
}
last_line_idx += pos + 2;
}
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
return Err(MeliError::from(e));
}
}
}
Ok(())
}
pub fn wait_for_continuation_request(&mut self) -> Result<()> {
let term = "+ ".to_string();
let mut ret = String::new();
self.read_lines(&mut ret, &term)
}
pub fn send_command(&mut self, command: &[u8]) -> Result<usize> {
let command = command.trim();
self.stream.write_all(b"M")?;
self.stream.write_all(self.cmd_id.to_string().as_bytes())?;
self.stream.write_all(b" ")?;
let ret = self.cmd_id;
self.cmd_id += 1;
self.stream.write_all(command)?;
self.stream.write_all(b"\r\n")?;
debug!("sent: M{} {}", self.cmd_id - 1, unsafe {
std::str::from_utf8_unchecked(command)
});
Ok(ret)
}
pub fn send_literal(&mut self, data: &[u8]) -> Result<()> {
self.stream.write_all(data)?;
if !data.ends_with(b"\r\n") {
self.stream.write_all(b"\r\n")?;
}
self.stream.write_all(b"\r\n")?;
Ok(())
}
pub fn send_raw(&mut self, raw: &[u8]) -> Result<()> {
self.stream.write_all(raw)?;
self.stream.write_all(b"\r\n")?;
Ok(())
}
pub fn set_nonblocking(&mut self, val: bool) -> Result<()> {
self.stream.get_mut().set_nonblocking(val)?;
Ok(())
}
pub fn new_connection(server_conf: &ImapServerConf) -> Result<(Capabilities, ImapStream)> {
use std::io::prelude::*;
use std::net::TcpStream;
@ -167,8 +78,8 @@ impl ImapStream {
};
let mut socket = TcpStream::connect(&addr)?;
socket.set_read_timeout(Some(std::time::Duration::new(120, 0)))?;
socket.set_write_timeout(Some(std::time::Duration::new(120, 0)))?;
socket.set_read_timeout(Some(std::time::Duration::new(4, 0)))?;
socket.set_write_timeout(Some(std::time::Duration::new(4, 0)))?;
let cmd_id = 0;
if server_conf.use_starttls {
socket.write_all(format!("M{} STARTTLS\r\n", cmd_id).as_bytes())?;
@ -274,7 +185,7 @@ impl ImapStream {
let tag_start = format!("M{} ", (ret.cmd_id - 1));
loop {
ret.read_lines(&mut res, &String::new())?;
ret.read_lines(&mut res, &String::new(), false)?;
let mut should_break = false;
for l in res.split_rn() {
if l.starts_with("* CAPABILITY") {
@ -317,6 +228,102 @@ impl ImapStream {
Ok((capabilities, ret))
}
}
pub fn read_response(&mut self, ret: &mut String) -> Result<()> {
let id = format!("M{} ", self.cmd_id - 1);
self.read_lines(ret, &id, true)
}
pub fn read_lines(
&mut self,
ret: &mut String,
termination_string: &str,
keep_termination_string: bool,
) -> Result<()> {
let mut buf: [u8; 1024] = [0; 1024];
ret.clear();
let mut last_line_idx: usize = 0;
loop {
match self.stream.read(&mut buf) {
Ok(0) => break,
Ok(b) => {
ret.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..b]) });
if let Some(mut pos) = ret[last_line_idx..].rfind("\r\n") {
if ret[last_line_idx..].starts_with("* BYE") {
return Err(MeliError::new("Disconnected"));
}
if let Some(prev_line) =
ret[last_line_idx..pos + last_line_idx].rfind("\r\n")
{
last_line_idx += prev_line + 2;
pos -= prev_line + 2;
}
if pos + "\r\n".len() == ret[last_line_idx..].len() {
if !termination_string.is_empty()
&& ret[last_line_idx..].starts_with(termination_string)
{
debug!(&ret[last_line_idx..]);
if !keep_termination_string {
ret.replace_range(last_line_idx.., "");
}
break;
} else if termination_string.is_empty() {
break;
}
}
last_line_idx += pos + 2;
}
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
return Err(MeliError::from(e));
}
}
}
Ok(())
}
pub fn wait_for_continuation_request(&mut self) -> Result<()> {
let term = "+ ".to_string();
let mut ret = String::new();
self.read_lines(&mut ret, &term, false)
}
pub fn send_command(&mut self, command: &[u8]) -> Result<()> {
let command = command.trim();
self.stream.write_all(b"M")?;
self.stream.write_all(self.cmd_id.to_string().as_bytes())?;
self.stream.write_all(b" ")?;
self.cmd_id += 1;
self.stream.write_all(command)?;
self.stream.write_all(b"\r\n")?;
debug!("sent: M{} {}", self.cmd_id - 1, unsafe {
std::str::from_utf8_unchecked(command)
});
Ok(())
}
pub fn send_literal(&mut self, data: &[u8]) -> Result<()> {
self.stream.write_all(data)?;
if !data.ends_with(b"\r\n") {
self.stream.write_all(b"\r\n")?;
}
self.stream.write_all(b"\r\n")?;
Ok(())
}
pub fn send_raw(&mut self, raw: &[u8]) -> Result<()> {
self.stream.write_all(raw)?;
self.stream.write_all(b"\r\n")?;
Ok(())
}
pub fn set_nonblocking(&mut self, val: bool) -> Result<()> {
self.stream.get_mut().set_nonblocking(val)?;
Ok(())
}
}
impl ImapConnection {
@ -333,188 +340,39 @@ impl ImapConnection {
}
pub fn read_response(&mut self, ret: &mut String) -> Result<()> {
if let (instant, ref mut status @ Ok(())) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
self.stream = Err(MeliError::new("Connection timed out"));
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(_) = stream.read_response(ret) {
return Ok(());
}
}
let new_stream = ImapStream::new_connection(&self.server_conf);
if new_stream.is_err() {
*self.online.lock().unwrap() = (
Instant::now(),
Err(new_stream.as_ref().unwrap_err().clone()),
);
} else {
*self.online.lock().unwrap() = (Instant::now(), Ok(()));
}
let (capabilities, mut stream) = new_stream?;
let ret = stream.read_response(ret);
if ret.is_ok() {
self.stream = Ok(stream);
self.capabilities = capabilities;
}
ret
let res = self.try_send(|s| s.read_response(ret));
let r: Result<()> = ImapResponse::from(&ret).into();
r
}
pub fn read_lines(&mut self, ret: &mut String, termination_string: String) -> Result<()> {
if let (instant, ref mut status @ Ok(())) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
self.stream = Err(MeliError::new("Connection timed out"));
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(_) = stream.read_lines(ret, &termination_string) {
return Ok(());
}
}
let new_stream = ImapStream::new_connection(&self.server_conf);
if new_stream.is_err() {
*self.online.lock().unwrap() = (
Instant::now(),
Err(new_stream.as_ref().unwrap_err().clone()),
);
} else {
*self.online.lock().unwrap() = (Instant::now(), Ok(()));
}
let (capabilities, mut stream) = new_stream?;
let ret = stream.read_lines(ret, &termination_string);
if ret.is_ok() {
self.stream = Ok(stream);
self.capabilities = capabilities;
}
ret
self.try_send(|s| s.read_lines(ret, &termination_string, false))
}
pub fn wait_for_continuation_request(&mut self) -> Result<()> {
if let (instant, ref mut status @ Ok(())) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
self.stream = Err(MeliError::new("Connection timed out"));
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(_) = stream.wait_for_continuation_request() {
return Ok(());
}
}
let new_stream = ImapStream::new_connection(&self.server_conf);
if new_stream.is_err() {
*self.online.lock().unwrap() = (
Instant::now(),
Err(new_stream.as_ref().unwrap_err().clone()),
);
} else {
*self.online.lock().unwrap() = (Instant::now(), Ok(()));
}
let (capabilities, mut stream) = new_stream?;
let ret = stream.wait_for_continuation_request();
if ret.is_ok() {
self.stream = Ok(stream);
self.capabilities = capabilities;
}
ret
self.try_send(|s| s.wait_for_continuation_request())
}
pub fn send_command(&mut self, command: &[u8]) -> Result<usize> {
if let (instant, ref mut status) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
self.stream = Err(MeliError::new("Connection timed out"));
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(ret) = stream.send_command(command) {
return Ok(ret);
}
}
let new_stream = ImapStream::new_connection(&self.server_conf);
if new_stream.is_err() {
*self.online.lock().unwrap() = (
Instant::now(),
Err(new_stream.as_ref().unwrap_err().clone()),
);
} else {
*self.online.lock().unwrap() = (Instant::now(), Ok(()));
}
let (capabilities, mut stream) = new_stream?;
let ret = stream.send_command(command);
if ret.is_ok() {
self.stream = Ok(stream);
self.capabilities = capabilities;
}
ret
pub fn send_command(&mut self, command: &[u8]) -> Result<()> {
self.try_send(|s| s.send_command(command))
}
pub fn send_literal(&mut self, data: &[u8]) -> Result<()> {
if let (instant, ref mut status @ Ok(())) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
self.stream = Err(MeliError::new("Connection timed out"));
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(_) = stream.send_literal(data) {
return Ok(());
}
}
let new_stream = ImapStream::new_connection(&self.server_conf);
if new_stream.is_err() {
*self.online.lock().unwrap() = (
Instant::now(),
Err(new_stream.as_ref().unwrap_err().clone()),
);
} else {
*self.online.lock().unwrap() = (Instant::now(), Ok(()));
}
let (capabilities, mut stream) = new_stream?;
let ret = stream.send_literal(data);
if ret.is_ok() {
self.stream = Ok(stream);
self.capabilities = capabilities;
}
ret
self.try_send(|s| s.send_literal(data))
}
pub fn send_raw(&mut self, raw: &[u8]) -> Result<()> {
if let (instant, ref mut status @ Ok(())) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
self.stream = Err(MeliError::new("Connection timed out"));
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(_) = stream.send_raw(raw) {
return Ok(());
}
}
let new_stream = ImapStream::new_connection(&self.server_conf);
if new_stream.is_err() {
*self.online.lock().unwrap() = (
Instant::now(),
Err(new_stream.as_ref().unwrap_err().clone()),
);
} else {
*self.online.lock().unwrap() = (Instant::now(), Ok(()));
}
let (capabilities, mut stream) = new_stream?;
let ret = stream.send_raw(raw);
if ret.is_ok() {
self.stream = Ok(stream);
self.capabilities = capabilities;
}
ret
self.try_send(|s| s.send_raw(raw))
}
pub fn set_nonblocking(&mut self, val: bool) -> Result<()> {
self.try_send(|s| s.set_nonblocking(val))
}
pub fn try_send(
&mut self,
mut action: impl FnMut(&mut ImapStream) -> Result<()>,
) -> Result<()> {
if let (instant, ref mut status @ Ok(())) = *self.online.lock().unwrap() {
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
*status = Err(MeliError::new("Connection timed out"));
@ -522,7 +380,7 @@ impl ImapConnection {
}
}
if let Ok(ref mut stream) = self.stream {
if let Ok(_) = stream.set_nonblocking(val) {
if let Ok(_) = action(stream) {
return Ok(());
}
}
@ -536,7 +394,7 @@ impl ImapConnection {
*self.online.lock().unwrap() = (Instant::now(), Ok(()));
}
let (capabilities, mut stream) = new_stream?;
let ret = stream.set_nonblocking(val);
let ret = action(&mut stream);
if ret.is_ok() {
self.stream = Ok(stream);
self.capabilities = capabilities;
@ -562,7 +420,7 @@ impl From<ImapConnection> for ImapBlockingConnection {
.map(|s| {
s.stream
.get_mut()
.set_write_timeout(Some(std::time::Duration::new(5 * 60, 0)))
.set_write_timeout(Some(std::time::Duration::new(30, 0)))
.expect("set_write_timeout call failed")
})
.expect("set_write_timeout call failed");
@ -571,7 +429,7 @@ impl From<ImapConnection> for ImapBlockingConnection {
.map(|s| {
s.stream
.get_mut()
.set_read_timeout(Some(std::time::Duration::new(5 * 60, 0)))
.set_read_timeout(Some(std::time::Duration::new(30, 0)))
.expect("set_read_timeout call failed")
})
.expect("set_read_timeout call failed");

119
melib/src/backends/imap/protocol_parser.rs

@ -12,6 +12,125 @@ pub struct ImapLineIterator<'a> {
slice: &'a str,
}
#[derive(Debug, PartialEq)]
pub enum ResponseCode {
///The human-readable text contains a special alert that MUST be presented to the user in a fashion that calls the user's attention to the message.
Alert(String),
///Optionally followed by a parenthesized list of charsets. A SEARCH failed because the given charset is not supported by this implementation. If the optional list of charsets is given, this lists the charsets that are supported by this implementation.
Badcharset,
/// Followed by a list of capabilities. This can appear in the initial OK or PREAUTH response to transmit an initial capabilities list. This makes it unnecessary for a client to send a separate CAPABILITY command if it recognizes this response.
Capability,
/// The human-readable text represents an error in parsing the [RFC-2822] header or [MIME-IMB] headers of a message in the mailbox.
Parse(String),
/// Followed by a parenthesized list of flags, indicates which of the known flags the client can change permanently. Any flags that are in the FLAGS untagged response, but not the PERMANENTFLAGS list, can not be set permanently. If the client attempts to STORE a flag that is not in the PERMANENTFLAGS list, the server will either ignore the change or store the state change for the remainder of the current session only. The PERMANENTFLAGS list can also include the special flag \*, which indicates that it is possible to create new keywords by attempting to store those flags in the mailbox.
Permanentflags,
/// The mailbox is selected read-only, or its access while selected has changed from read-write to read-only.
ReadOnly,
/// The mailbox is selected read-write, or its access while selected has changed from read-only to read-write.
ReadWrite,
/// An APPEND or COPY attempt is failing because the target mailbox does not exist (as opposed to some other reason). This is a hint to the client that the operation can succeed if the mailbox is first created by the CREATE command.
Trycreate,
/// Followed by a decimal number, indicates the next unique identifier value. Refer to section 2.3.1.1 for more information.
Uidnext(UID),
/// Followed by a decimal number, indicates the unique identifier validity value. Refer to section 2.3.1.1 for more information.
Uidvalidity(UID),
/// Followed by a decimal number, indicates the number of the first message without the \Seen flag set.
Unseen(usize),
}
impl ResponseCode {
fn from(val: &str) -> Option<ResponseCode> {
if !val.starts_with("[") {
return None;
}
let val = &val[1..];
use ResponseCode::*;
if val.starts_with("BADCHARSET") {
Some(Badcharset)
} else if val.starts_with("READONLY") {
Some(ReadOnly)
} else if val.starts_with("READWRITE") {
Some(ReadWrite)
} else if val.starts_with("TRYCREATE") {
Some(Trycreate)
} else if val.starts_with("UIDNEXT") {
//FIXME
Some(Uidnext(0))
} else if val.starts_with("UIDVALIDITY") {
//FIXME
Some(Uidvalidity(0))
} else if val.starts_with("UNSEEN") {
//FIXME
Some(Unseen(0))
} else {
let msg = &val[val.as_bytes().find(b"] ").unwrap() + 1..].trim();
Some(Alert(msg.to_string()))
}
}
}
#[derive(Debug, PartialEq)]
pub enum ImapResponse {
Ok(Option<ResponseCode>),
No(Option<ResponseCode>),
Bad(Option<ResponseCode>),
Preauth(Option<ResponseCode>),
Bye(Option<ResponseCode>),
}
impl<T: AsRef<str>> From<T> for ImapResponse {
fn from(val: T) -> ImapResponse {
let val: &str = val.as_ref().split_rn().last().unwrap_or(val.as_ref());
debug!(&val);
assert!(val.starts_with("M"));
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
if val.ends_with(" secs).") {
val = &val[..val.as_bytes().rfind(b"(").unwrap()];
}
if val.starts_with("OK") {
Self::Ok(ResponseCode::from(&val["OK ".len()..]))
} else if val.starts_with("NO") {
Self::No(ResponseCode::from(&val["NO ".len()..]))
} else if val.starts_with("BAD") {
Self::Bad(ResponseCode::from(&val["BAD ".len()..]))
} else if val.starts_with("PREAUTH") {
Self::Preauth(ResponseCode::from(&val["PREAUTH ".len()..]))
} else if val.starts_with("BYE") {
Self::Bye(ResponseCode::from(&val["BYE ".len()..]))
} else {
panic!("Unknown IMAP response: `{}`", val);
}
}
}
impl Into<Result<()>> for ImapResponse {
fn into(self) -> Result<()> {
match self {
Self::Ok(_) | Self::Preauth(_) | Self::Bye(_) => Ok(()),
Self::No(Some(ResponseCode::Alert(msg)))
| Self::Bad(Some(ResponseCode::Alert(msg))) => Err(MeliError::new(msg)),
Self::No(_) => Err(MeliError::new("IMAP NO Response.")),
Self::Bad(_) => Err(MeliError::new("IMAP BAD Response.")),
}
}
}
#[test]
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()))));
}
impl<'a> Iterator for ImapLineIterator<'a> {
type Item = &'a str;

199
ui/src/conf/accounts.rs

@ -384,7 +384,6 @@ impl Account {
folder_names.insert(f.hash(), f.path().to_string());
}
let mut stack: SmallVec<[FolderHash; 8]> = SmallVec::new();
let mut tree: Vec<FolderNode> = Vec::new();
let mut collection: Collection = Collection::new(Default::default());
for (h, f) in ref_folders.iter() {
@ -392,28 +391,6 @@ impl Account {
/* Skip unsubscribed folder */
continue;
}
if f.parent().is_none() {
fn rec(h: FolderHash, ref_folders: &FnvHashMap<FolderHash, Folder>) -> FolderNode {
let mut node = FolderNode {
hash: h,
kids: Vec::new(),
};
for &c in ref_folders[&h].children() {
node.kids.push(rec(c, ref_folders));
}
node
};
tree.push(rec(*h, &ref_folders));
for &c in f.children() {
stack.push(c);
}
while let Some(next) = stack.pop() {
for c in ref_folders[&next].children() {
stack.push(*c);
}
}
}
folders.insert(
*h,
MailboxEntry::Parsing(Mailbox::new(f.clone(), &FnvHashMap::default()), 0, 0),
@ -430,40 +407,7 @@ impl Account {
collection.threads.insert(*h, Threads::default());
}
tree.sort_unstable_by(|a, b| {
if ref_folders[&b.hash].path().eq_ignore_ascii_case("INBOX") {
std::cmp::Ordering::Greater
} else if ref_folders[&a.hash].path().eq_ignore_ascii_case("INBOX") {
std::cmp::Ordering::Less
} else {
ref_folders[&a.hash]
.path()
.cmp(&ref_folders[&b.hash].path())
}
});
let mut stack: SmallVec<[Option<&FolderNode>; 8]> = SmallVec::new();
for n in tree.iter_mut() {
folders_order.push(n.hash);
n.kids.sort_unstable_by(|a, b| {
if ref_folders[&b.hash].path().eq_ignore_ascii_case("INBOX") {
std::cmp::Ordering::Greater
} else if ref_folders[&a.hash].path().eq_ignore_ascii_case("INBOX") {
std::cmp::Ordering::Less
} else {
ref_folders[&a.hash]
.path()
.cmp(&ref_folders[&b.hash].path())
}
});
stack.extend(n.kids.iter().rev().map(Some));
while let Some(Some(next)) = stack.pop() {
folders_order.push(next.hash);
stack.extend(next.kids.iter().rev().map(Some));
}
}
drop(stack);
build_folders_order(&folder_confs, &mut tree, &ref_folders, &mut folders_order);
self.folders = folders;
self.ref_folders = ref_folders;
self.folder_confs = folder_confs;
@ -784,11 +728,7 @@ impl Account {
self.folders.is_empty()
}
pub fn list_folders(&self) -> Vec<Folder> {
let mut folders = if let Ok(folders) = self.backend.read().unwrap().folders() {
folders
} else {
return Vec::new();
};
let mut folders = self.ref_folders.clone();
let folder_confs = &self.folder_confs;
//debug!("folder renames: {:?}", folder_renames);
for f in folders.values_mut() {
@ -1015,7 +955,68 @@ impl Account {
}
pub fn folder_operation(&mut self, path: &str, op: FolderOperation) -> Result<()> {
Err(MeliError::new("Not implemented."))
match op {
FolderOperation::Create => {
if self.settings.account.read_only() {
Err(MeliError::new("Account is read-only."))
} else {
let mut folder = self
.backend
.write()
.unwrap()
.create_folder(path.to_string())?;
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_names
.insert(folder.hash(), folder.path().to_string());
self.folders.insert(
folder.hash(),
MailboxEntry::Parsing(
Mailbox::new(folder.clone(), &FnvHashMap::default()),
0,
0,
),
);
self.workers.insert(
folder.hash(),
Account::new_worker(
folder.clone(),
&mut self.backend,
&self.work_context,
self.notify_fn.clone(),
),
);
self.collection
.threads
.insert(folder.hash(), Threads::default());
self.ref_folders.insert(folder.hash(), folder);
build_folders_order(
&self.folder_confs,
&mut self.tree,
&self.ref_folders,
&mut self.folders_order,
);
Ok(())
}
}
FolderOperation::Delete => Err(MeliError::new("Not implemented.")),
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.")),
}
}
pub fn folder_confs(&self, folder_hash: FolderHash) -> &FileFolderConf {
@ -1147,3 +1148,75 @@ impl IndexMut<usize> for Account {
self.folders.get_mut(&self.folders_order[index]).unwrap()
}
}
fn build_folders_order(
folder_confs: &FnvHashMap<FolderHash, FileFolderConf>,
tree: &mut Vec<FolderNode>,
ref_folders: &FnvHashMap<FolderHash, Folder>,
folders_order: &mut Vec<FolderHash>,
) {
let mut stack: SmallVec<[FolderHash; 8]> = SmallVec::new();
tree.clear();
folders_order.clear();
for (h, f) in ref_folders.iter() {
if !folder_confs.contains_key(&h) {
continue;
}
if f.parent().is_none() {
fn rec(h: FolderHash, ref_folders: &FnvHashMap<FolderHash, Folder>) -> FolderNode {
let mut node = FolderNode {
hash: h,
kids: Vec::new(),
};
for &c in ref_folders[&h].children() {
node.kids.push(rec(c, ref_folders));
}
node
};
tree.push(rec(*h, &ref_folders));
for &c in f.children() {
stack.push(c);
}
while let Some(next) = stack.pop() {
for c in ref_folders[&next].children() {
stack.push(*c);
}
}
}
}
tree.sort_unstable_by(|a, b| {
if ref_folders[&b.hash].path().eq_ignore_ascii_case("INBOX") {
std::cmp::Ordering::Greater
} else if ref_folders[&a.hash].path().eq_ignore_ascii_case("INBOX") {
std::cmp::Ordering::Less
} else {
ref_folders[&a.hash]
.path()
.cmp(&ref_folders[&b.hash].path())
}
});
let mut stack: SmallVec<[Option<&FolderNode>; 8]> = SmallVec::new();
for n in tree.iter_mut() {
folders_order.push(n.hash);
n.kids.sort_unstable_by(|a, b| {
if ref_folders[&b.hash].path().eq_ignore_ascii_case("INBOX") {
std::cmp::Ordering::Greater
} else if ref_folders[&a.hash].path().eq_ignore_ascii_case("INBOX") {
std::cmp::Ordering::Less
} else {
ref_folders[&a.hash]
.path()
.cmp(&ref_folders[&b.hash].path())
}
});
stack.extend(n.kids.iter().rev().map(Some));
while let Some(Some(next)) = stack.pop() {
folders_order.push(next.hash);
stack.extend(next.kids.iter().rev().map(Some));
}
}
}

44
ui/src/execute.rs

@ -23,7 +23,7 @@
*/
use melib::backends::FolderOperation;
pub use melib::thread::{SortField, SortOrder};
use nom::{digit, not_line_ending};
use nom::{digit, not_line_ending, IResult};
use std;
pub mod actions;
pub mod history;
@ -45,6 +45,26 @@ macro_rules! define_commands {
};
}
pub fn quoted_argument(input: &[u8]) -> IResult<&[u8], &str> {
if input.is_empty() {
return IResult::Error(nom::ErrorKind::Custom(0));
}
if input[0] == b'"' {
let mut i = 1;
while i < input.len() {
if input[i] == b'\"' && input[i - 1] != b'\\' {
return IResult::Done(&input[i + 1..], unsafe {
std::str::from_utf8_unchecked(&input[1..i])
});
}
i += 1;
}
return IResult::Error(nom::ErrorKind::Custom(0));
} else {
return map_res!(input, is_not!(" "), std::str::from_utf8);
}
}
define_commands!([
{ tags: ["set"],
desc: "set [seen/unseen], toggles message's Seen flag.",
@ -179,9 +199,9 @@ define_commands!([
ws!(tag!("pipe"))
>> bin: map_res!(is_not!(" "), std::str::from_utf8)
>> is_a!(" ")
>> args: separated_list!(is_a!(" "), is_not!(" "))
>> args: separated_list!(is_a!(" "), quoted_argument)
>> ({
View(Pipe(bin.to_string(), args.into_iter().map(|v| String::from_utf8(v.to_vec()).unwrap()).collect::<Vec<String>>()))
View(Pipe(bin.to_string(), args.into_iter().map(String::from).collect::<Vec<String>>()))
})) | do_parse!(
ws!(tag!("pipe"))
>> bin: ws!(map_res!(is_not!(" "), std::str::from_utf8))
@ -233,7 +253,7 @@ define_commands!([
named!( create_folder<Action>,
do_parse!(
ws!(tag!("create-folder"))
>> account: map_res!(is_not!(" "), std::str::from_utf8)
>> account: quoted_argument
>> is_a!(" ")
>> path: map_res!(call!(not_line_ending), std::str::from_utf8)
>> (Folder(account.to_string(), path.to_string(), FolderOperation::Create))
@ -247,7 +267,7 @@ define_commands!([
named!( sub_folder<Action>,
do_parse!(
ws!(tag!("subscribe-folder"))
>> account: map_res!(is_not!(" "), std::str::from_utf8)
>> account: quoted_argument
>> is_a!(" ")
>> path: map_res!(call!(not_line_ending), std::str::from_utf8)
>> (Folder(account.to_string(), path.to_string(), FolderOperation::Subscribe))
@ -261,7 +281,7 @@ define_commands!([
named!( unsub_folder<Action>,
do_parse!(
ws!(tag!("unsubscribe-folder"))
>> account: map_res!(is_not!(" "), std::str::from_utf8)
>> account: quoted_argument
>> is_a!(" ")
>> path: map_res!(call!(not_line_ending), std::str::from_utf8)
>> (Folder(account.to_string(), path.to_string(), FolderOperation::Unsubscribe))
@ -275,9 +295,9 @@ define_commands!([
named!( rename_folder<Action>,
do_parse!(
ws!(tag!("rename-folder"))
>> account: map_res!(is_not!(" "), std::str::from_utf8)
>> account: quoted_argument
>> is_a!(" ")
>> src: map_res!(is_not!(" "), std::str::from_utf8)
>> src: quoted_argument
>> is_a!(" ")
>> dest: map_res!(call!(not_line_ending), std::str::from_utf8)
>> (Folder(account.to_string(), src.to_string(), FolderOperation::Rename(dest.to_string())))
@ -291,9 +311,9 @@ define_commands!([
named!( delete_folder<Action>,
do_parse!(
ws!(tag!("delete-folder"))
>> account: map_res!(is_not!(" "), std::str::from_utf8)
>> account: quoted_argument
>> is_a!(" ")
>> path: map_res!(call!(not_line_ending), std::str::from_utf8)
>> path: quoted_argument
>> (Folder(account.to_string(), path.to_string(), FolderOperation::Delete))
)
);
@ -305,7 +325,7 @@ define_commands!([
named!( reindex<Action>,
do_parse!(
ws!(tag!("reindex"))
>> account: map_res!(is_not!(" "), std::str::from_utf8)
>> account: quoted_argument
>> (AccountAction(account.to_string(), ReIndex))
)
);
@ -329,7 +349,7 @@ define_commands!([
do_parse!(
ws!(tag!("save-attachment"))
>> idx: map_res!(map_res!(is_not!(" "), std::str::from_utf8), usize::from_str)
>> path: ws!(map_res!(call!(not_line_ending), std::str::from_utf8))
>> path: ws!(quoted_argument)
>> (View(SaveAttachment(idx, path.to_string())))
)
);

1
ui/src/lib.rs

@ -231,7 +231,6 @@ pub mod timer {
impl PosixTimer {
pub fn rearm(&mut self) {
debug!("posixtimer rearm");
let spec = itimerspec {
it_interval: timespec {
tv_sec: self.interval.as_secs().try_into().unwrap_or(0),

4
ui/src/plugins/backend.rs

@ -102,7 +102,7 @@ impl MailBackend for PluginBackend {
channel.expect_ack().unwrap();
loop {
let read_val: Result<PluginResult<Option<Vec<SimpleEnvelope>>>> =
debug!(channel.from_read());
channel.from_read();
match read_val.map(Into::into).and_then(std::convert::identity) {
Ok(Some(a)) => {
tx.send(AsyncStatus::Payload(Ok(a
@ -300,7 +300,7 @@ impl BackendOp for PluginOp {
debug!(channel.expect_ack())?;
channel.write_ref(&rmpv::ValueRef::Integer(self.hash.into()))?;
debug!(channel.expect_ack())?;
let bytes: Result<PluginResult<String>> = debug!(channel.from_read());
let bytes: Result<PluginResult<String>> = channel.from_read();
self.bytes = Some(bytes.map(Into::into).and_then(std::convert::identity)?);
if let Some(ref bytes) = self.bytes {
debug!(Envelope::from_bytes(bytes.as_bytes(), None));

8
ui/src/state.rs

@ -254,7 +254,6 @@ impl State {
let sender = sender;
loop {
thread::park();
debug!("unparked");
sender.send(ThreadEvent::Pulse).unwrap();
thread::sleep(std::time::Duration::from_millis(100));
@ -586,6 +585,13 @@ impl State {
self.context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(e.to_string()),
));
} else {
self.context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"{} succesfully created in `{}`",
path, account_name
)),
));
}
} else {
self.context.replies.push_back(UIEvent::StatusEvent(

2
ui/src/types.rs

@ -181,7 +181,7 @@ pub mod segment_tree {
let height = (f64::from(u32::try_from(val.len()).unwrap_or(0)))
.log2()
.ceil() as u32;
let max_size = 2 * (2_usize.pow(height)) - 1;
let max_size = 2 * (2_usize.pow(height));
let mut segment_tree: SmallVec<[u8; 1024]> =
SmallVec::from_iter(core::iter::repeat(0).take(max_size));

Loading…
Cancel
Save