/* * meli - lib.rs * * Copyright 2017 Manos Pitsidianakis * * This file is part of meli. * * meli is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * meli is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with meli. If not, see . */ //! A crate that performs mail client operations such as //! - Hold an [`Envelope`](./email/struct.Envelope.html) with methods convenient for mail client use. (see module [`email`](./email/index.html)) //! - Abstract through mail storages through the [`MailBackend`](./backends/trait.MailBackend.html) trait, and handle read/writes/updates through it. (see module [`backends`](./backends/index.html)) //! - Decode attachments (see module [`email::attachments`](./email/attachments/index.html)) //! - Create new mail (see [`email::Draft`](./email/compose/struct.Draft.html)) //! - Send mail with an SMTP client (see module [`smtp`](./smtp/index.html)) //! - Manage an `addressbook` i.e. have contacts (see module [`addressbook`](./addressbook/index.html)) //! - Build thread structures out of a list of mail via their `In-Reply-To` and `References` header values (see module [`thread`](./thread/index.html)) //! //! Other exports are //! - Basic mail account configuration to use with [`backends`](./backends/index.html) (see module [`conf`](./conf/index.html)) //! - Parser combinators (see module [`parsec`](./parsec/index.html)) //! - A `ShellExpandTrait` to expand paths like a shell. //! - A `debug` macro that works like `std::dbg` but for multiple threads. (see [`debug` macro](./macro.debug.html)) #[macro_use] pub mod dbg { #[macro_export] macro_rules! log_tag { () => { eprint!( "[{}][{:?}] {}:{}_{}: ", crate::datetime::timestamp_to_string( crate::datetime::now(), Some("%Y-%m-%d %T"), false ), std::thread::current() .name() .map(std::string::ToString::to_string) .unwrap_or_else(|| format!("{:?}", std::thread::current().id())), file!(), line!(), column!() ); }; } #[allow(clippy::redundant_closure)] #[macro_export] macro_rules! debug { ($val:literal) => { { if cfg!(feature="debug-tracing") { log_tag!(); eprintln!($val); } $val } }; ($val:expr) => { if cfg!(feature="debug-tracing") { let stringify = stringify!($val); // Use of `match` here is intentional because it affects the lifetimes // of temporaries - https://stackoverflow.com/a/48732525/1063961 match $val { tmp => { log_tag!(); eprintln!("{} = {:?}", stringify, tmp); tmp } } } else { $val } }; ($fmt:literal, $($arg:tt)*) => { if cfg!(feature="debug-tracing") { log_tag!(); eprintln!($fmt, $($arg)*); } }; } } #[cfg(feature = "unicode_algorithms")] pub mod text_processing; pub mod datetime; pub use datetime::UnixTimestamp; #[macro_use] mod logging; pub use self::logging::LoggingLevel::*; pub use self::logging::*; pub mod addressbook; pub use addressbook::*; pub mod backends; pub use backends::*; mod collection; pub use collection::*; pub mod conf; pub use conf::*; pub mod email; pub use email::*; pub mod error; pub use crate::error::*; pub mod thread; pub use thread::*; pub mod connections; pub mod parsec; pub mod search; #[cfg(feature = "gpgme")] pub mod gpgme; #[cfg(feature = "smtp")] pub mod smtp; #[cfg(feature = "sqlite3")] pub mod sqlite3; #[macro_use] extern crate serde_derive; /* parser */ extern crate data_encoding; extern crate encoding; pub extern crate nom; #[macro_use] extern crate bitflags; pub extern crate futures; pub extern crate indexmap; pub extern crate smallvec; pub extern crate smol; pub extern crate uuid; pub extern crate xdg_utils; #[derive(Debug, Copy, Clone)] pub struct Bytes(pub usize); impl Bytes { pub const KILOBYTE: f64 = 1024.0; pub const MEGABYTE: f64 = Self::KILOBYTE * 1024.0; pub const GIGABYTE: f64 = Self::MEGABYTE * 1024.0; pub const PETABYTE: f64 = Self::GIGABYTE * 1024.0; } impl core::fmt::Display for Bytes { fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { let bytes: f64 = self.0 as f64; if bytes == 0.0 { write!(fmt, "0") } else if bytes < Self::KILOBYTE { write!(fmt, "{:.2} bytes", bytes) } else if bytes < Self::MEGABYTE { write!(fmt, "{:.2} KiB", bytes / Self::KILOBYTE) } else if bytes < Self::GIGABYTE { write!(fmt, "{:.2} MiB", bytes / Self::MEGABYTE) } else if bytes < Self::PETABYTE { write!(fmt, "{:.2} GiB", bytes / Self::GIGABYTE) } else { write!(fmt, "{:.2} PiB", bytes / Self::PETABYTE) } } } pub use shellexpand::ShellExpandTrait; pub mod shellexpand { use smallvec::SmallVec; use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; #[cfg(not(target_os = "netbsd"))] use std::os::unix::io::AsRawFd; use std::path::{Path, PathBuf}; pub trait ShellExpandTrait { fn expand(&self) -> PathBuf; fn complete(&self, force: bool) -> SmallVec<[String; 128]>; } impl ShellExpandTrait for Path { fn expand(&self) -> PathBuf { let mut ret = PathBuf::new(); for c in self.components() { let c_to_str = c.as_os_str().to_str(); match c_to_str { Some("~") => { if let Ok(home_dir) = std::env::var("HOME") { ret.push(home_dir) } else { return PathBuf::new(); } } Some(var) if var.starts_with('$') => { let env_name = var.split_at(1).1; if env_name.chars().all(char::is_uppercase) { ret.push(std::env::var(env_name).unwrap_or_default()); } else { ret.push(c); } } Some(_) => { ret.push(c); } None => { /* path is invalid */ return PathBuf::new(); } } } ret } #[cfg(target_os = "linux")] fn complete(&self, force: bool) -> SmallVec<[String; 128]> { use libc::dirent64; use nix::fcntl::OFlag; const BUF_SIZE: ::libc::size_t = 8 << 10; let (prefix, _match) = if self.as_os_str().as_bytes().ends_with(b"/.") { (self.components().as_path(), OsStr::from_bytes(b".")) } else { if self.exists() && (!force || self.as_os_str().as_bytes().ends_with(b"/")) { // println!("{} {:?}", self.display(), self.components().last()); return SmallVec::new(); } else { let last_component = self .components() .last() .map(|c| c.as_os_str()) .unwrap_or(OsStr::from_bytes(b"")); let prefix = if let Some(p) = self.parent() { p } else { return SmallVec::new(); }; (prefix, last_component) } }; let dir = match ::nix::dir::Dir::openat( ::libc::AT_FDCWD, prefix, OFlag::O_DIRECTORY | OFlag::O_NOATIME | OFlag::O_RDONLY | OFlag::O_CLOEXEC, ::nix::sys::stat::Mode::S_IRUSR | ::nix::sys::stat::Mode::S_IXUSR, ) .or_else(|_| { ::nix::dir::Dir::openat( ::libc::AT_FDCWD, prefix, OFlag::O_DIRECTORY | OFlag::O_RDONLY | OFlag::O_CLOEXEC, ::nix::sys::stat::Mode::S_IRUSR | ::nix::sys::stat::Mode::S_IXUSR, ) }) { Ok(dir) => dir, Err(err) => { debug!(prefix); debug!(err); return SmallVec::new(); } }; let mut buf: Vec = Vec::with_capacity(BUF_SIZE); let mut entries = SmallVec::new(); loop { let n: i64 = unsafe { ::libc::syscall( ::libc::SYS_getdents64, dir.as_raw_fd(), buf.as_ptr(), BUF_SIZE - 256, ) }; if n < 0 { return SmallVec::new(); } else if n == 0 { break; } let n = n as usize; unsafe { buf.set_len(n); } let mut pos = 0; while pos < n { let dir = unsafe { std::mem::transmute::<&[u8], &[dirent64]>(&buf[pos..]) }; let entry = unsafe { std::ffi::CStr::from_ptr(dir[0].d_name.as_ptr()) }; if entry.to_bytes() != b"." && entry.to_bytes() != b".." { if entry.to_bytes().starts_with(_match.as_bytes()) { if dir[0].d_type == ::libc::DT_DIR && !entry.to_bytes().ends_with(b"/") { let mut s = unsafe { String::from_utf8_unchecked( entry.to_bytes()[_match.as_bytes().len()..].to_vec(), ) }; s.push('/'); entries.push(s); } else { entries.push(unsafe { String::from_utf8_unchecked( entry.to_bytes()[_match.as_bytes().len()..].to_vec(), ) }); } } } pos += dir[0].d_reclen as usize; } // https://github.com/romkatv/gitstatus/blob/caf44f7aaf33d0f46e6749e50595323c277e0908/src/dir.cc // "It's tempting to bail here if n + sizeof(linux_dirent64) + 512 <= n. After all, there // was enough space for another entry but SYS_getdents64 didn't write it, so this must be // the end of the directory listing, right? Unfortunately, no. SYS_getdents64 is finicky. // It sometimes writes a partial list of entries even if the full list would fit." } entries } #[cfg(not(target_os = "linux"))] fn complete(&self, force: bool) -> SmallVec<[String; 128]> { let mut entries = SmallVec::new(); let (prefix, _match) = { if self.exists() && (!force || self.as_os_str().as_bytes().ends_with(b"/")) { // println!("{} {:?}", self.display(), self.components().last()); return entries; } else { let last_component = self .components() .last() .map(|c| c.as_os_str()) .unwrap_or(OsStr::from_bytes(b"")); let prefix = if let Some(p) = self.parent() { p } else { return entries; }; (prefix, last_component) } }; if force && self.is_dir() && !self.as_os_str().as_bytes().ends_with(b"/") { entries.push("/".to_string()); } if let Ok(iter) = std::fs::read_dir(&prefix) { for entry in iter { if let Ok(entry) = entry { if entry.path().as_os_str().as_bytes() != b"." && entry.path().as_os_str().as_bytes() != b".." && entry .path() .as_os_str() .as_bytes() .starts_with(_match.as_bytes()) { if entry.path().is_dir() && !entry.path().as_os_str().as_bytes().ends_with(b"/") { let mut s = unsafe { String::from_utf8_unchecked( entry.path().as_os_str().as_bytes() [_match.as_bytes().len()..] .to_vec(), ) }; s.push('/'); entries.push(s); } else { entries.push(unsafe { String::from_utf8_unchecked( entry.path().as_os_str().as_bytes() [_match.as_bytes().len()..] .to_vec(), ) }); } } } } } entries } } #[test] fn test_shellexpandtrait() { assert!(Path::new("~").expand().complete(false).is_empty()); assert!(!Path::new("~").expand().complete(true).is_empty()); } }