meli/melib/src/backends.rs

772 lines
22 KiB
Rust
Raw Normal View History

/*
* meli - backends module
*
* 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 <http://www.gnu.org/licenses/>.
*/
pub mod utf7;
use smallvec::SmallVec;
#[cfg(feature = "imap")]
2018-07-27 18:37:56 +00:00
pub mod imap;
#[cfg(feature = "imap")]
pub mod nntp;
#[cfg(feature = "notmuch")]
pub mod notmuch;
#[cfg(feature = "notmuch")]
pub use self::notmuch::NotmuchDb;
#[cfg(feature = "jmap")]
2019-12-03 11:25:49 +00:00
pub mod jmap;
#[cfg(feature = "maildir")]
pub mod maildir;
#[cfg(feature = "mbox")]
pub mod mbox;
2023-04-30 16:39:41 +00:00
use std::{
any::Any,
borrow::Cow,
collections::{BTreeSet, HashMap},
fmt,
fmt::Debug,
future::Future,
ops::Deref,
pin::Pin,
sync::{Arc, RwLock},
};
use futures::stream::Stream;
#[cfg(feature = "imap")]
pub use self::imap::ImapType;
#[cfg(feature = "maildir")]
2019-04-04 11:21:52 +00:00
use self::maildir::MaildirType;
#[cfg(feature = "mbox")]
2019-07-11 08:44:27 +00:00
use self::mbox::MboxType;
#[cfg(feature = "imap")]
2023-04-30 16:39:41 +00:00
pub use self::nntp::NntpType;
2019-04-04 11:21:52 +00:00
use super::email::{Envelope, EnvelopeHash, Flag};
2023-04-30 16:39:41 +00:00
use crate::{
conf::AccountSettings,
error::{Error, ErrorKind, Result},
LogLevel,
2023-04-30 16:39:41 +00:00
};
2017-09-30 17:53:14 +00:00
#[macro_export]
macro_rules! get_path_hash {
($path:expr) => {{
2023-04-30 16:39:41 +00:00
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
let mut hasher = DefaultHasher::new();
$path.hash(&mut hasher);
hasher.finish()
}};
}
pub type BackendCreator = Box<
dyn Fn(
&AccountSettings,
Box<dyn Fn(&str) -> bool + Send + Sync>,
2020-08-19 22:55:24 +00:00
BackendEventConsumer,
) -> Result<Box<dyn MailBackend>>,
>;
2023-07-01 13:34:06 +00:00
pub type BackendValidateConfigFn = Box<dyn Fn(&mut AccountSettings) -> Result<()>>;
2017-09-30 17:53:14 +00:00
/// A hashmap containing all available mail backends.
2018-07-10 08:18:11 +00:00
/// An abstraction over any available backends.
pub struct Backends {
2020-05-10 18:14:49 +00:00
map: HashMap<std::string::String, Backend>,
}
pub struct Backend {
pub create_fn: Box<dyn Fn() -> BackendCreator>,
2023-07-01 13:34:06 +00:00
pub validate_conf_fn: BackendValidateConfigFn,
}
2018-08-23 12:36:52 +00:00
impl Default for Backends {
fn default() -> Self {
2023-07-01 13:34:06 +00:00
Self::new()
2018-08-23 12:36:52 +00:00
}
}
#[cfg(feature = "notmuch")]
2023-04-30 16:39:41 +00:00
pub const NOTMUCH_ERROR_MSG: &str = "libnotmuch5 was not found in your system. Make sure it is \
installed and in the library paths. For a custom file path, \
use `library_file_path` setting in your notmuch account.\n";
#[cfg(not(feature = "notmuch"))]
2023-04-30 16:39:41 +00:00
pub const NOTMUCH_ERROR_MSG: &str = "this version of meli is not compiled with notmuch support. \
Use an appropriate version and make sure libnotmuch5 is \
installed and in the library paths.\n";
2020-02-26 12:18:00 +00:00
#[cfg(not(feature = "notmuch"))]
pub const NOTMUCH_ERROR_DETAILS: &str = "";
#[cfg(all(feature = "notmuch", target_os = "unix"))]
pub const NOTMUCH_ERROR_DETAILS: &str = r#"If you have installed the library manually, try setting the `LD_LIBRARY_PATH` environment variable to its `lib` directory. Otherwise, set it to the location of libnotmuch.5.so. Example:
LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/path/to/notmuch/lib" meli
or, put this in your shell init script (.bashenv, .zshenv, .bashrc, .zshrc, .profile):
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/path/to/notmuch/lib"
You can also set any location by specifying the library file path with the configuration flag `library_file_path`."#;
#[cfg(all(feature = "notmuch", target_os = "macos"))]
pub const NOTMUCH_ERROR_DETAILS: &str = r#"If you have installed the library via homebrew, try setting the `DYLD_LIBRARY_PATH` environment variable to its `lib` directory. Otherwise, set it to the location of libnotmuch.5.dylib. Example:
DYLD_LIBRARY_PATH="$(brew --prefix)/lib" meli
or, put this in your shell init script (.bashenv, .zshenv, .bashrc, .zshrc, .profile):
export DYLD_LIBRARY_PATH="$(brew --prefix)/lib"
Make sure to append to DYLD_LIBRARY_PATH if it's not empty, by prepending a colon to the libnotmuch5.dylib location:
export DYLD_LIBRARY_PATH="$DYLD_LIBRARY_PATH:$(brew --prefix)/lib"
You can also set any location by specifying the library file path with the configuration flag `library_file_path`."#;
#[cfg(all(feature = "notmuch", not(any(target_os = "unix", target_os = "macos"))))]
pub const NOTMUCH_ERROR_DETAILS: &str = r#"If notmuch is installed but the library isn't found, consult your system's documentation on how to make dynamic libraries discoverable."#;
impl Backends {
pub fn new() -> Self {
2023-07-01 13:34:06 +00:00
let mut b = Self {
2020-05-10 18:14:49 +00:00
map: HashMap::with_capacity_and_hasher(1, Default::default()),
2017-09-30 17:53:14 +00:00
};
#[cfg(feature = "maildir")]
{
b.register(
"maildir".to_string(),
Backend {
2020-08-19 22:55:24 +00:00
create_fn: Box::new(|| Box::new(|f, i, ev| MaildirType::new(f, i, ev))),
validate_conf_fn: Box::new(MaildirType::validate_config),
},
);
}
#[cfg(feature = "mbox")]
{
b.register(
"mbox".to_string(),
Backend {
2020-08-19 22:55:24 +00:00
create_fn: Box::new(|| Box::new(|f, i, ev| MboxType::new(f, i, ev))),
validate_conf_fn: Box::new(MboxType::validate_config),
},
);
}
#[cfg(feature = "imap")]
{
b.register(
"imap".to_string(),
Backend {
2020-08-19 22:55:24 +00:00
create_fn: Box::new(|| Box::new(|f, i, ev| imap::ImapType::new(f, i, ev))),
validate_conf_fn: Box::new(imap::ImapType::validate_config),
},
);
b.register(
"nntp".to_string(),
Backend {
2020-08-19 22:55:24 +00:00
create_fn: Box::new(|| Box::new(|f, i, ev| nntp::NntpType::new(f, i, ev))),
validate_conf_fn: Box::new(nntp::NntpType::validate_config),
},
);
}
#[cfg(feature = "notmuch")]
{
b.register(
"notmuch".to_string(),
Backend {
create_fn: Box::new(|| Box::new(|f, i, ev| NotmuchDb::new(f, i, ev))),
validate_conf_fn: Box::new(NotmuchDb::validate_config),
},
);
}
#[cfg(feature = "jmap")]
2019-12-03 11:25:49 +00:00
{
b.register(
"jmap".to_string(),
Backend {
2020-08-19 22:55:24 +00:00
create_fn: Box::new(|| Box::new(|f, i, ev| jmap::JmapType::new(f, i, ev))),
validate_conf_fn: Box::new(jmap::JmapType::validate_config),
2019-12-03 11:25:49 +00:00
},
);
}
2017-09-30 17:53:14 +00:00
b
}
pub fn get(&self, key: &str) -> BackendCreator {
if !self.map.contains_key(key) {
2020-02-26 12:18:00 +00:00
if key == "notmuch" {
eprint!("{}", NOTMUCH_ERROR_MSG);
#[cfg(feature = "notmuch")]
{
eprint!("{}", NOTMUCH_ERROR_DETAILS);
}
2020-02-26 12:18:00 +00:00
}
2017-09-30 17:53:14 +00:00
panic!("{} is not a valid mail backend", key);
}
(self.map[key].create_fn)()
}
pub fn register(&mut self, key: String, backend: Backend) {
if self.map.contains_key(&key) {
2017-09-30 17:53:14 +00:00
panic!("{} is an already registered backend", key);
}
self.map.insert(key, backend);
}
pub fn validate_config(&self, key: &str, s: &mut AccountSettings) -> Result<()> {
(self
.map
.get(key)
2020-02-26 12:18:00 +00:00
.ok_or_else(|| {
2022-12-08 20:20:05 +00:00
Error::new(format!(
"{}{} is not a valid mail backend. {}",
2020-02-26 12:18:00 +00:00
if key == "notmuch" {
NOTMUCH_ERROR_MSG
} else {
""
},
key,
if cfg!(feature = "notmuch") && key == "notmuch" {
NOTMUCH_ERROR_DETAILS
} else {
""
},
2020-02-26 12:18:00 +00:00
))
})?
.validate_conf_fn)(s)
}
}
2020-08-19 22:55:24 +00:00
#[derive(Debug, Clone)]
pub enum BackendEvent {
Notice {
description: String,
content: Option<String>,
level: LogLevel,
2020-08-19 22:55:24 +00:00
},
Refresh(RefreshEvent),
AccountStateChange {
message: Cow<'static, str>,
},
2020-08-19 22:55:24 +00:00
//Job(Box<Future<Output = Result<()>> + Send + 'static>)
}
2022-12-08 20:20:05 +00:00
impl From<Error> for BackendEvent {
2023-07-01 13:34:06 +00:00
fn from(val: Error) -> Self {
Self::Notice {
description: val.summary.to_string(),
content: Some(val.to_string()),
level: LogLevel::ERROR,
2020-08-27 21:24:43 +00:00
}
}
}
#[derive(Debug, Clone)]
2018-09-05 13:08:11 +00:00
pub enum RefreshEventKind {
Update(EnvelopeHash, Box<Envelope>),
2018-10-14 16:49:16 +00:00
/// Rename(old_hash, new_hash)
Rename(EnvelopeHash, EnvelopeHash),
Create(Box<Envelope>),
Remove(EnvelopeHash),
NewFlags(EnvelopeHash, (Flag, Vec<String>)),
2018-09-05 13:08:11 +00:00
Rescan,
2022-12-08 20:20:05 +00:00
Failure(Error),
MailboxCreate(Mailbox),
MailboxDelete(MailboxHash),
MailboxRename {
old_mailbox_hash: MailboxHash,
new_mailbox: Mailbox,
},
MailboxSubscribe(MailboxHash),
MailboxUnsubscribe(MailboxHash),
2018-09-05 13:08:11 +00:00
}
#[derive(Debug, Clone)]
2017-09-28 15:06:35 +00:00
pub struct RefreshEvent {
2020-08-26 16:13:18 +00:00
pub mailbox_hash: MailboxHash,
pub account_hash: AccountHash,
pub kind: RefreshEventKind,
2017-09-05 13:41:29 +00:00
}
2020-08-19 22:55:24 +00:00
#[derive(Clone)]
2020-08-25 13:39:12 +00:00
pub struct BackendEventConsumer(Arc<dyn Fn(AccountHash, BackendEvent) + Send + Sync>);
2023-07-01 13:34:06 +00:00
2020-08-19 22:55:24 +00:00
impl BackendEventConsumer {
2020-08-25 13:39:12 +00:00
pub fn new(b: Arc<dyn Fn(AccountHash, BackendEvent) + Send + Sync>) -> Self {
2023-07-01 13:34:06 +00:00
Self(b)
2017-09-28 15:06:35 +00:00
}
}
2018-09-07 12:36:42 +00:00
2020-08-19 22:55:24 +00:00
impl fmt::Debug for BackendEventConsumer {
2018-09-07 12:36:42 +00:00
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2020-08-19 22:55:24 +00:00
write!(f, "BackendEventConsumer")
2018-09-07 12:36:42 +00:00
}
}
2020-08-19 22:55:24 +00:00
impl Deref for BackendEventConsumer {
2020-08-25 13:39:12 +00:00
type Target = dyn Fn(AccountHash, BackendEvent) + Send + Sync;
2018-09-07 12:36:42 +00:00
2020-08-19 22:55:24 +00:00
fn deref(&self) -> &Self::Target {
&(*self.0)
2018-09-07 12:36:42 +00:00
}
}
#[derive(Debug, Clone)]
pub struct MailBackendCapabilities {
pub is_async: bool,
pub is_remote: bool,
pub extensions: Option<Vec<(String, MailBackendExtensionStatus)>>,
pub supports_search: bool,
pub supports_tags: bool,
pub supports_submission: bool,
}
#[derive(Debug, Copy, Clone)]
pub enum MailBackendExtensionStatus {
Unsupported { comment: Option<&'static str> },
Supported { comment: Option<&'static str> },
Enabled { comment: Option<&'static str> },
}
pub type ResultFuture<T> = Result<Pin<Box<dyn Future<Output = Result<T>> + Send + 'static>>>;
pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
fn capabilities(&self) -> MailBackendCapabilities;
fn is_online(&self) -> ResultFuture<()> {
Ok(Box::pin(async { Ok(()) }))
2020-07-05 16:56:17 +00:00
}
2023-07-01 13:34:06 +00:00
#[allow(clippy::type_complexity)]
fn fetch(
2020-06-22 08:29:36 +00:00
&mut self,
mailbox_hash: MailboxHash,
) -> Result<Pin<Box<dyn Stream<Item = Result<Vec<Envelope>>> + Send + 'static>>>;
fn refresh(&mut self, mailbox_hash: MailboxHash) -> ResultFuture<()>;
fn watch(&self) -> ResultFuture<()>;
fn mailboxes(&self) -> ResultFuture<HashMap<MailboxHash, Mailbox>>;
fn operation(&self, hash: EnvelopeHash) -> Result<Box<dyn BackendOp>>;
fn save(
&self,
bytes: Vec<u8>,
mailbox_hash: MailboxHash,
flags: Option<Flag>,
) -> ResultFuture<()>;
fn copy_messages(
&mut self,
env_hashes: EnvelopeHashBatch,
source_mailbox_hash: MailboxHash,
destination_mailbox_hash: MailboxHash,
move_: bool,
) -> ResultFuture<()>;
fn set_flags(
&mut self,
env_hashes: EnvelopeHashBatch,
mailbox_hash: MailboxHash,
flags: SmallVec<[(std::result::Result<Flag, String>, bool); 8]>,
) -> ResultFuture<()>;
fn delete_messages(
&mut self,
env_hashes: EnvelopeHashBatch,
mailbox_hash: MailboxHash,
) -> ResultFuture<()>;
fn collection(&self) -> crate::Collection;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn create_mailbox(
&mut self,
2022-11-14 17:14:19 +00:00
path: String,
) -> ResultFuture<(MailboxHash, HashMap<MailboxHash, Mailbox>)>;
fn delete_mailbox(
&mut self,
2022-11-14 17:14:19 +00:00
mailbox_hash: MailboxHash,
) -> ResultFuture<HashMap<MailboxHash, Mailbox>>;
fn set_mailbox_subscription(
&mut self,
2022-11-14 17:14:19 +00:00
mailbox_hash: MailboxHash,
val: bool,
) -> ResultFuture<()>;
fn rename_mailbox(
&mut self,
2022-11-14 17:14:19 +00:00
mailbox_hash: MailboxHash,
new_path: String,
) -> ResultFuture<Mailbox>;
fn set_mailbox_permissions(
&mut self,
2022-11-14 17:14:19 +00:00
mailbox_hash: MailboxHash,
val: MailboxPermissions,
) -> ResultFuture<()>;
fn search(
&self,
2022-11-14 17:14:19 +00:00
query: crate::search::Query,
mailbox_hash: Option<MailboxHash>,
) -> ResultFuture<SmallVec<[EnvelopeHash; 512]>>;
2021-09-03 21:32:57 +00:00
fn submit(
&self,
2021-09-04 13:52:17 +00:00
_bytes: Vec<u8>,
_mailbox_hash: Option<MailboxHash>,
_flags: Option<Flag>,
2021-09-03 21:32:57 +00:00
) -> ResultFuture<()> {
2022-12-08 20:20:05 +00:00
Err(Error::new("Submission not supported in this backend.")
2022-11-14 17:14:19 +00:00
.set_kind(ErrorKind::NotSupported))
2021-09-03 21:32:57 +00:00
}
2017-09-28 15:06:35 +00:00
}
2023-04-30 16:39:41 +00:00
/// A `BackendOp` manages common operations for the various mail backends. They
/// only live for the duration of the operation. They are generated by the
/// `operation` method of `Mailbackend` trait.
///
/// # Motivation
///
2023-04-30 16:39:41 +00:00
/// We need a way to do various operations on individual mails regardless of
/// what backend they come from (eg local or imap).
///
2019-03-26 13:27:02 +00:00
/// # Creation
2019-12-11 14:07:08 +00:00
/// ```ignore
2019-03-26 13:27:02 +00:00
/// /* Create operation from Backend */
///
/// let op = backend.operation(message.hash(), mailbox.hash());
2019-03-26 13:27:02 +00:00
/// ```
///
/// # Example
2019-12-11 14:07:08 +00:00
/// ```ignore
/// use melib::backends::{BackendOp};
2017-10-01 14:31:20 +00:00
/// use melib::Result;
/// use melib::{Envelope, Flag};
///
/// #[derive(Debug)]
/// struct FooOp {}
///
/// impl BackendOp for FooOp {
/// fn as_bytes(&mut self) -> Result<&[u8]> {
/// unimplemented!()
/// }
/// }
///
2019-07-18 17:14:14 +00:00
/// let operation = Box::new(FooOp {});
/// ```
pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send {
2020-07-04 14:38:57 +00:00
fn as_bytes(&mut self) -> ResultFuture<Vec<u8>>;
}
2019-07-18 17:16:51 +00:00
/// Wrapper for BackendOps that are to be set read-only.
///
2023-04-30 16:39:41 +00:00
/// Warning: Backend implementations may still cause side-effects (for example
/// IMAP can set the Seen flag when fetching an envelope)
2019-07-18 17:16:51 +00:00
#[derive(Debug)]
pub struct ReadOnlyOp {
op: Box<dyn BackendOp>,
2019-07-18 17:16:51 +00:00
}
impl ReadOnlyOp {
2023-07-01 13:34:06 +00:00
#[allow(clippy::new_ret_no_self)]
pub fn new(op: Box<dyn BackendOp>) -> Box<dyn BackendOp> {
2023-07-01 13:34:06 +00:00
Box::new(Self { op })
2019-07-18 17:16:51 +00:00
}
}
impl BackendOp for ReadOnlyOp {
2020-07-04 14:38:57 +00:00
fn as_bytes(&mut self) -> ResultFuture<Vec<u8>> {
2019-07-18 17:16:51 +00:00
self.op.as_bytes()
}
}
2023-07-01 13:34:06 +00:00
#[derive(Default, Debug, Copy, Hash, Eq, Clone, Serialize, Deserialize, PartialEq)]
pub enum SpecialUsageMailbox {
2023-07-01 13:34:06 +00:00
#[default]
Normal,
Inbox,
Archive,
Drafts,
Flagged,
Junk,
Sent,
Trash,
}
impl std::fmt::Display for SpecialUsageMailbox {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use SpecialUsageMailbox::*;
write!(
f,
"{}",
match self {
Normal => "Normal",
Inbox => "Inbox",
Archive => "Archive",
Drafts => "Drafts",
Flagged => "Flagged",
Junk => "Junk",
Sent => "Sent",
Trash => "Trash",
}
)
}
}
impl SpecialUsageMailbox {
2023-07-01 13:34:06 +00:00
pub fn detect_usage(name: &str) -> Option<Self> {
if name.eq_ignore_ascii_case("inbox") {
2023-07-01 13:34:06 +00:00
Some(Self::Inbox)
} else if name.eq_ignore_ascii_case("archive") {
2023-07-01 13:34:06 +00:00
Some(Self::Archive)
} else if name.eq_ignore_ascii_case("drafts") {
2023-07-01 13:34:06 +00:00
Some(Self::Drafts)
2020-07-05 12:28:55 +00:00
} else if name.eq_ignore_ascii_case("junk") || name.eq_ignore_ascii_case("spam") {
2023-07-01 13:34:06 +00:00
Some(Self::Junk)
} else if name.eq_ignore_ascii_case("sent") {
2023-07-01 13:34:06 +00:00
Some(Self::Sent)
} else if name.eq_ignore_ascii_case("trash") {
2023-07-01 13:34:06 +00:00
Some(Self::Trash)
} else {
2023-07-01 13:34:06 +00:00
Some(Self::Normal)
}
}
}
pub trait BackendMailbox: Debug {
fn hash(&self) -> MailboxHash;
/// Final component of `path`.
fn name(&self) -> &str;
/// Path of mailbox within the mailbox hierarchy, with `/` as separator.
2019-08-23 18:32:32 +00:00
fn path(&self) -> &str;
fn clone(&self) -> Mailbox;
fn children(&self) -> &[MailboxHash];
fn parent(&self) -> Option<MailboxHash>;
fn is_subscribed(&self) -> bool;
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()>;
fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()>;
fn special_usage(&self) -> SpecialUsageMailbox;
fn permissions(&self) -> MailboxPermissions;
fn count(&self) -> Result<(usize, usize)>;
}
crate::declare_u64_hash!(AccountHash);
crate::declare_u64_hash!(MailboxHash);
crate::declare_u64_hash!(TagHash);
pub type Mailbox = Box<dyn BackendMailbox + Send + Sync>;
impl Clone for Mailbox {
fn clone(&self) -> Self {
BackendMailbox::clone(self.deref())
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct MailboxPermissions {
pub create_messages: bool,
pub remove_messages: bool,
pub set_flags: bool,
pub create_child: bool,
pub rename_messages: bool,
pub delete_messages: bool,
pub delete_mailbox: bool,
pub change_permissions: bool,
}
impl Default for MailboxPermissions {
fn default() -> Self {
2023-07-01 13:34:06 +00:00
Self {
create_messages: false,
remove_messages: false,
set_flags: false,
create_child: false,
rename_messages: false,
delete_messages: false,
delete_mailbox: true,
change_permissions: false,
}
}
}
impl std::fmt::Display for MailboxPermissions {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "{:#?}", self)
}
}
2022-11-14 17:14:19 +00:00
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnvelopeHashBatch {
pub first: EnvelopeHash,
pub rest: SmallVec<[EnvelopeHash; 64]>,
}
impl From<EnvelopeHash> for EnvelopeHashBatch {
fn from(value: EnvelopeHash) -> Self {
2023-07-01 13:34:06 +00:00
Self {
first: value,
rest: SmallVec::new(),
}
}
}
impl std::convert::TryFrom<&[EnvelopeHash]> for EnvelopeHashBatch {
type Error = ();
fn try_from(value: &[EnvelopeHash]) -> std::result::Result<Self, Self::Error> {
if value.is_empty() {
return Err(());
}
2023-07-01 13:34:06 +00:00
Ok(Self {
first: value[0],
rest: value[1..].iter().cloned().collect(),
})
}
}
2023-07-01 13:34:06 +00:00
impl From<&EnvelopeHashBatch> for BTreeSet<EnvelopeHash> {
fn from(val: &EnvelopeHashBatch) -> Self {
val.iter().collect()
}
}
impl EnvelopeHashBatch {
pub fn iter(&self) -> impl std::iter::Iterator<Item = EnvelopeHash> + '_ {
std::iter::once(self.first).chain(self.rest.iter().cloned())
}
2023-07-01 13:34:06 +00:00
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
1 + self.rest.len()
}
pub fn to_set(&self) -> BTreeSet<EnvelopeHash> {
self.into()
}
}
#[derive(Default, Clone)]
pub struct LazyCountSet {
not_yet_seen: usize,
set: BTreeSet<EnvelopeHash>,
}
impl fmt::Debug for LazyCountSet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("LazyCountSet")
.field("not_yet_seen", &self.not_yet_seen)
.field("set", &self.set.len())
.field("total_len", &self.len())
.finish()
}
}
impl LazyCountSet {
pub fn new() -> Self {
Self::default()
}
pub fn set_not_yet_seen(&mut self, new_val: usize) {
self.not_yet_seen = new_val;
}
pub fn insert_existing(&mut self, new_val: EnvelopeHash) -> bool {
if self.not_yet_seen == 0 {
false
} else {
if !self.set.contains(&new_val) {
self.not_yet_seen -= 1;
}
self.set.insert(new_val);
true
}
}
pub fn insert_existing_set(&mut self, set: BTreeSet<EnvelopeHash>) {
let old_len = self.set.len();
self.set.extend(set.into_iter());
self.not_yet_seen = self.not_yet_seen.saturating_sub(self.set.len() - old_len);
}
2023-07-01 13:34:06 +00:00
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline(always)]
pub fn len(&self) -> usize {
self.set.len() + self.not_yet_seen
}
#[inline(always)]
pub fn clear(&mut self) {
self.set.clear();
self.not_yet_seen = 0;
}
pub fn insert_new(&mut self, new_val: EnvelopeHash) {
self.set.insert(new_val);
}
pub fn insert_set(&mut self, set: BTreeSet<EnvelopeHash>) {
self.set.extend(set.into_iter());
}
pub fn remove(&mut self, env_hash: EnvelopeHash) -> bool {
self.set.remove(&env_hash)
}
}
#[test]
fn test_lazy_count_set() {
let mut new = LazyCountSet::default();
assert_eq!(new.len(), 0);
new.set_not_yet_seen(10);
assert_eq!(new.len(), 10);
for i in 0..10 {
assert!(new.insert_existing(EnvelopeHash(i)));
}
assert_eq!(new.len(), 10);
assert!(!new.insert_existing(EnvelopeHash(10)));
assert_eq!(new.len(), 10);
}
pub struct IsSubscribedFn(Box<dyn Fn(&str) -> bool + Send + Sync>);
impl std::fmt::Debug for IsSubscribedFn {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "IsSubscribedFn Box")
}
}
impl std::ops::Deref for IsSubscribedFn {
type Target = Box<dyn Fn(&str) -> bool + Send + Sync>;
fn deref(&self) -> &Box<dyn Fn(&str) -> bool + Send + Sync> {
&self.0
}
}