use std::collections::hash_map::RandomState;
use std::collections::{HashMap, VecDeque};
use std::hash::BuildHasher;
use std::str::FromStr;
#[cfg(feature = "temp_cache")]
use std::time::Duration;
use dashmap::iter::Iter;
use dashmap::mapref::entry::Entry;
use dashmap::mapref::multiple::RefMulti;
use dashmap::{DashMap, DashSet};
#[cfg(feature = "temp_cache")]
use moka::dash::Cache as DashCache;
use parking_lot::RwLock;
use tracing::instrument;
use crate::model::prelude::*;
mod cache_update;
mod event;
mod settings;
pub use self::cache_update::CacheUpdate;
pub use self::settings::Settings;
type MessageCache = DashMap<ChannelId, DashMap<MessageId, Message>>;
pub trait FromStrAndCache: Sized {
type Err;
#[allow(clippy::missing_errors_doc)]
fn from_str<CRL>(cache: CRL, s: &str) -> Result<Self, Self::Err>
where
CRL: AsRef<Cache> + Send + Sync;
}
pub trait StrExt: Sized {
#[allow(clippy::missing_errors_doc)]
fn parse_cached<CRL, F: FromStrAndCache>(&self, cache: CRL) -> Result<F, F::Err>
where
CRL: AsRef<Cache> + Send + Sync;
}
impl StrExt for &str {
#[allow(clippy::missing_errors_doc)]
fn parse_cached<CRL, F: FromStrAndCache>(&self, cache: CRL) -> Result<F, F::Err>
where
CRL: AsRef<Cache> + Send + Sync,
{
F::from_str(&cache, self)
}
}
impl<F: FromStr> FromStrAndCache for F {
type Err = F::Err;
#[allow(clippy::missing_errors_doc)]
fn from_str<CRL>(_cache: CRL, s: &str) -> Result<Self, Self::Err>
where
CRL: AsRef<Cache> + Send + Sync,
{
s.parse::<F>()
}
}
#[derive(Clone)]
pub struct MessageIterator<'a, S: BuildHasher + Clone>(
Iter<'a, MessageId, Message, S, DashMap<MessageId, Message, S>>,
);
impl<'a, S: 'a + BuildHasher + Clone> Iterator for MessageIterator<'a, S> {
type Item = RefMulti<'a, MessageId, Message, S>;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct Cache {
pub(crate) channels: DashMap<ChannelId, GuildChannel>,
#[cfg(feature = "temp_cache")]
pub(crate) temp_channels: DashCache<ChannelId, GuildChannel>,
pub(crate) categories: DashMap<ChannelId, ChannelCategory>,
pub(crate) guilds: DashMap<GuildId, Guild>,
pub(crate) messages: MessageCache,
pub(crate) presences: DashMap<UserId, Presence>,
pub(crate) private_channels: DashMap<ChannelId, PrivateChannel>,
pub(crate) shard_count: RwLock<u64>,
pub(crate) unavailable_guilds: DashSet<GuildId>,
pub(crate) user: RwLock<CurrentUser>,
pub(crate) users: DashMap<UserId, User>,
pub(crate) message_queue: DashMap<ChannelId, VecDeque<MessageId>>,
#[cfg(feature = "temp_cache")]
pub(crate) temp_users: DashCache<UserId, User>,
settings: RwLock<Settings>,
}
impl Cache {
#[inline]
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[instrument]
pub fn new_with_settings(settings: Settings) -> Self {
Self {
settings: RwLock::new(settings),
..Default::default()
}
}
pub fn unknown_members(&self) -> u64 {
let mut total = 0;
for guild_entry in self.guilds.iter() {
let guild = guild_entry.value();
let members = guild.members.len() as u64;
if guild.member_count > members {
total += guild.member_count - members;
}
}
total
}
pub fn private_channels(&self) -> DashMap<ChannelId, PrivateChannel> {
self.private_channels.clone()
}
pub fn guilds(&self) -> Vec<GuildId> {
let chain = self.unavailable_guilds.clone().into_iter();
self.guilds.iter().map(|i| *i.key()).chain(chain).collect()
}
#[inline]
pub fn channel<C: Into<ChannelId>>(&self, id: C) -> Option<Channel> {
self._channel(id.into())
}
fn _channel(&self, id: ChannelId) -> Option<Channel> {
if let Some(channel) = self.channels.get(&id) {
let channel = channel.clone();
return Some(Channel::Guild(channel));
}
#[cfg(feature = "temp_cache")]
{
if let Some(channel) = self.temp_channels.get(&id) {
return Some(Channel::Guild(channel));
}
}
if let Some(private_channel) = self.private_channels.get(&id) {
return Some(Channel::Private(private_channel.clone()));
}
None
}
pub fn channel_messages_field<T>(
&self,
channel_id: impl Into<ChannelId>,
selector: impl FnOnce(MessageIterator<'_, RandomState>) -> T,
) -> Option<T> {
let msg = self.messages.get(&channel_id.into())?;
let message_iter = MessageIterator(msg.iter());
Some(selector(message_iter))
}
#[inline]
pub fn guild<G: Into<GuildId>>(&self, id: G) -> Option<Guild> {
self._guild(id.into())
}
fn _guild(&self, id: GuildId) -> Option<Guild> {
self.guilds.get(&id).map(|i| i.clone())
}
#[inline]
pub fn guild_field<Ret, Fun>(&self, id: impl Into<GuildId>, field_selector: Fun) -> Option<Ret>
where
Fun: FnOnce(&Guild) -> Ret,
{
self._guild_field(id.into(), field_selector)
}
fn _guild_field<Ret, Fun>(&self, id: GuildId, field_accessor: Fun) -> Option<Ret>
where
Fun: FnOnce(&Guild) -> Ret,
{
let guild = self.guilds.get(&id)?;
Some(field_accessor(&guild))
}
pub fn guild_count(&self) -> usize {
self.guilds.len()
}
#[inline]
pub fn guild_channel<C: Into<ChannelId>>(&self, id: C) -> Option<GuildChannel> {
self._guild_channel(id.into())
}
fn _guild_channel(&self, id: ChannelId) -> Option<GuildChannel> {
self.channels.get(&id).map(|i| i.clone())
}
#[inline]
pub fn guild_channel_field<Ret, Fun>(
&self,
id: impl Into<ChannelId>,
field_selector: Fun,
) -> Option<Ret>
where
Fun: FnOnce(&GuildChannel) -> Ret,
{
self._guild_channel_field(id.into(), field_selector)
}
fn _guild_channel_field<Ret, Fun>(&self, id: ChannelId, field_selector: Fun) -> Option<Ret>
where
Fun: FnOnce(&GuildChannel) -> Ret,
{
let channel = self.channels.get(&id)?;
Some(field_selector(&channel))
}
#[inline]
pub fn member<G, U>(&self, guild_id: G, user_id: U) -> Option<Member>
where
G: Into<GuildId>,
U: Into<UserId>,
{
self._member(guild_id.into(), user_id.into())
}
fn _member(&self, guild_id: GuildId, user_id: UserId) -> Option<Member> {
match self.guilds.get(&guild_id) {
Some(guild) => guild.members.get(&user_id).cloned(),
None => None,
}
}
#[inline]
pub fn member_field<Ret, Fun>(
&self,
guild_id: impl Into<GuildId>,
user_id: impl Into<UserId>,
field_selector: Fun,
) -> Option<Ret>
where
Fun: FnOnce(&Member) -> Ret,
{
self._member_field(guild_id.into(), user_id.into(), field_selector)
}
fn _member_field<Ret, Fun>(
&self,
guild_id: GuildId,
user_id: UserId,
field_selector: Fun,
) -> Option<Ret>
where
Fun: FnOnce(&Member) -> Ret,
{
let guild = self.guilds.get(&guild_id)?;
let member = guild.members.get(&user_id)?;
Some(field_selector(member))
}
#[inline]
pub fn guild_roles(&self, guild_id: impl Into<GuildId>) -> Option<HashMap<RoleId, Role>> {
self._guild_roles(guild_id.into())
}
fn _guild_roles(&self, guild_id: GuildId) -> Option<HashMap<RoleId, Role>> {
self.guilds.get(&guild_id).map(|g| g.roles.clone())
}
#[inline]
pub fn unavailable_guilds(&self) -> DashSet<GuildId> {
self.unavailable_guilds.clone()
}
#[inline]
pub fn guild_channels(
&self,
guild_id: impl Into<GuildId>,
) -> Option<DashMap<ChannelId, GuildChannel>> {
self._guild_channels(guild_id.into())
}
fn _guild_channels(&self, guild_id: GuildId) -> Option<DashMap<ChannelId, GuildChannel>> {
self.guilds.get(&guild_id).map(|g| {
g.channels
.iter()
.filter_map(|c| match c.1 {
Channel::Guild(channel) => Some((channel.id, channel.clone())),
_ => None,
})
.collect()
})
}
pub fn guild_channel_count(&self) -> usize {
self.channels.len()
}
#[inline]
pub fn guild_categories(
&self,
guild_id: impl Into<GuildId>,
) -> Option<DashMap<ChannelId, ChannelCategory>> {
self._guild_categories(guild_id.into())
}
fn _guild_categories(&self, guild_id: GuildId) -> Option<DashMap<ChannelId, ChannelCategory>> {
self.guilds.get(&guild_id).map(|g| {
g.channels
.iter()
.filter_map(|c| match c.1 {
Channel::Category(category) => Some((category.id, category.clone())),
_ => None,
})
.collect()
})
}
#[inline]
pub fn shard_count(&self) -> u64 {
*self.shard_count.read()
}
#[inline]
pub fn message<C, M>(&self, channel_id: C, message_id: M) -> Option<Message>
where
C: Into<ChannelId>,
M: Into<MessageId>,
{
self._message(channel_id.into(), message_id.into())
}
fn _message(&self, channel_id: ChannelId, message_id: MessageId) -> Option<Message> {
self.messages
.get(&channel_id)
.and_then(|messages| messages.get(&message_id).map(|i| i.clone()))
}
#[inline]
pub fn private_channel(&self, channel_id: impl Into<ChannelId>) -> Option<PrivateChannel> {
self._private_channel(channel_id.into())
}
fn _private_channel(&self, channel_id: ChannelId) -> Option<PrivateChannel> {
self.private_channels.get(&channel_id).map(|i| i.clone())
}
#[inline]
pub fn role<G, R>(&self, guild_id: G, role_id: R) -> Option<Role>
where
G: Into<GuildId>,
R: Into<RoleId>,
{
self._role(guild_id.into(), role_id.into())
}
fn _role(&self, guild_id: GuildId, role_id: RoleId) -> Option<Role> {
self.guilds.get(&guild_id).and_then(|g| g.roles.get(&role_id).cloned())
}
pub fn settings(&self) -> Settings {
self.settings.read().clone()
}
pub fn set_max_messages(&self, max: usize) {
self.settings.write().max_messages = max;
}
#[inline]
pub fn user<U: Into<UserId>>(&self, user_id: U) -> Option<User> {
self._user(user_id.into())
}
#[cfg(feature = "temp_cache")]
fn _user(&self, user_id: UserId) -> Option<User> {
if let Some(user) = self.users.get(&user_id) {
Some(user.clone())
} else {
self.temp_users.get(&user_id)
}
}
#[cfg(not(feature = "temp_cache"))]
fn _user(&self, user_id: UserId) -> Option<User> {
self.users.get(&user_id).map(|u| u.clone())
}
#[inline]
pub fn users(&self) -> DashMap<UserId, User> {
self.users.clone()
}
#[inline]
pub fn user_count(&self) -> usize {
self.users.len()
}
#[inline]
pub fn category<C: Into<ChannelId>>(&self, channel_id: C) -> Option<ChannelCategory> {
self._category(channel_id.into())
}
fn _category(&self, channel_id: ChannelId) -> Option<ChannelCategory> {
self.categories.get(&channel_id).map(|i| i.clone())
}
#[inline]
pub fn categories(&self) -> DashMap<ChannelId, ChannelCategory> {
self.categories.clone()
}
#[inline]
pub fn category_count(&self) -> usize {
self.categories.len()
}
#[inline]
pub fn channel_category_id(&self, channel_id: ChannelId) -> Option<ChannelId> {
self.categories.get(&channel_id).map(|category| category.id)
}
#[inline]
pub fn current_user(&self) -> CurrentUser {
self.user.read().clone()
}
#[inline]
pub fn current_user_id(&self) -> UserId {
self.user.read().id
}
#[inline]
pub fn current_user_field<Ret: Clone, Fun>(&self, field_selector: Fun) -> Ret
where
Fun: FnOnce(&CurrentUser) -> Ret,
{
let user = self.user.read();
field_selector(&user)
}
#[instrument(skip(self, e))]
pub fn update<E: CacheUpdate>(&self, e: &mut E) -> Option<E::Output> {
e.update(self)
}
pub(crate) fn update_user_entry(&self, user: &User) {
match self.users.entry(user.id) {
Entry::Vacant(e) => {
e.insert(user.clone());
},
Entry::Occupied(mut e) => {
e.get_mut().clone_from(user);
},
}
}
}
impl Default for Cache {
fn default() -> Cache {
Cache {
channels: DashMap::default(),
#[cfg(feature = "temp_cache")]
temp_channels: DashCache::builder().time_to_live(Duration::from_secs(60 * 60)).build(),
categories: DashMap::default(),
guilds: DashMap::default(),
messages: DashMap::default(),
presences: DashMap::default(),
private_channels: DashMap::with_capacity(128),
settings: RwLock::new(Settings::default()),
shard_count: RwLock::new(1),
unavailable_guilds: DashSet::default(),
user: RwLock::new(CurrentUser::default()),
users: DashMap::default(),
#[cfg(feature = "temp_cache")]
temp_users: DashCache::builder().time_to_live(Duration::from_secs(60 * 60)).build(),
message_queue: DashMap::default(),
}
}
}
#[cfg(test)]
mod test {
use std::collections::HashMap;
use crate::cache::{Cache, CacheUpdate, Settings};
use crate::json::from_number;
use crate::model::prelude::*;
#[test]
fn test_cache_messages() {
let mut settings = Settings::new();
settings.max_messages(2);
let cache = Cache::new_with_settings(settings);
let datetime = Timestamp::now();
let mut event = MessageCreateEvent {
message: Message {
id: MessageId(3),
attachments: vec![],
author: User {
id: UserId(2),
avatar: None,
bot: false,
discriminator: 1,
name: "user 1".to_owned(),
public_flags: None,
banner: None,
member: None,
accent_colour: None,
},
channel_id: ChannelId(2),
guild_id: Some(GuildId(1)),
content: String::new(),
edited_timestamp: None,
embeds: vec![],
kind: MessageType::Regular,
member: None,
mention_everyone: false,
mention_roles: vec![],
mention_channels: vec![],
mentions: vec![],
nonce: from_number(1),
pinned: false,
reactions: vec![],
timestamp: datetime,
tts: false,
webhook_id: None,
activity: None,
application: None,
message_reference: None,
flags: None,
sticker_items: vec![],
referenced_message: None,
interaction: None,
components: vec![],
application_id: None,
thread: None,
},
};
assert!(!cache.messages.contains_key(&event.message.channel_id));
assert!(event.update(&cache).is_none());
assert!(event.update(&cache).is_none());
assert_eq!(cache.messages.get(&event.message.channel_id).unwrap().len(), 1);
event.message.id = MessageId(4);
assert!(event.update(&cache).is_none());
assert_eq!(cache.messages.get(&event.message.channel_id).unwrap().len(), 2);
event.message.id = MessageId(5);
assert!(event.update(&cache).is_some());
{
let channel = cache.messages.get(&event.message.channel_id).unwrap();
assert_eq!(channel.len(), 2);
assert!(!channel.contains_key(&MessageId(3)));
}
let channel = Channel::Guild(GuildChannel {
id: event.message.channel_id,
bitrate: None,
parent_id: None,
guild_id: event.message.guild_id.unwrap(),
kind: ChannelType::Text,
owner_id: None,
last_message_id: None,
last_pin_timestamp: None,
name: String::new(),
permission_overwrites: vec![],
position: 0,
topic: None,
user_limit: None,
nsfw: false,
rate_limit_per_user: Some(0),
rtc_region: None,
video_quality_mode: None,
message_count: None,
member_count: None,
thread_metadata: None,
member: None,
default_auto_archive_duration: None,
flags: ChannelFlags::empty(),
total_message_sent: None,
available_tags: Vec::new(),
applied_tags: Vec::new(),
default_reaction_emoji: None,
default_thread_rate_limit_per_user: None,
default_sort_order: None,
});
let mut delete = ChannelDeleteEvent {
channel: channel.clone(),
};
assert!(cache.update(&mut delete).is_none());
assert!(!cache.messages.contains_key(&delete.channel.id()));
let mut guild_create = {
let mut channels = HashMap::new();
channels.insert(ChannelId(2), channel);
GuildCreateEvent {
guild: Guild {
id: GuildId(1),
afk_channel_id: None,
afk_timeout: 0,
application_id: None,
default_message_notifications: DefaultMessageNotificationLevel::All,
emojis: HashMap::new(),
explicit_content_filter: ExplicitContentFilter::None,
features: vec![],
icon: None,
joined_at: datetime,
large: false,
member_count: 0,
members: HashMap::new(),
mfa_level: MfaLevel::None,
name: String::new(),
owner_id: UserId(3),
presences: HashMap::new(),
roles: HashMap::new(),
splash: None,
discovery_splash: None,
system_channel_id: None,
system_channel_flags: SystemChannelFlags::default(),
rules_channel_id: None,
public_updates_channel_id: None,
verification_level: VerificationLevel::Low,
voice_states: HashMap::new(),
description: None,
premium_tier: PremiumTier::Tier0,
channels,
premium_subscription_count: 0,
banner: None,
vanity_url_code: Some("bruhmoment".to_string()),
preferred_locale: "en-US".to_string(),
welcome_screen: None,
approximate_member_count: None,
approximate_presence_count: None,
nsfw_level: NsfwLevel::Default,
max_video_channel_users: None,
max_presences: None,
max_members: None,
widget_enabled: Some(false),
widget_channel_id: None,
stage_instances: vec![],
threads: vec![],
stickers: HashMap::new(),
},
}
};
assert!(cache.update(&mut guild_create).is_none());
assert!(cache.update(&mut event).is_none());
let mut guild_delete = GuildDeleteEvent {
guild: UnavailableGuild {
id: GuildId(1),
unavailable: false,
},
};
assert!(cache.update(&mut guild_delete).is_some());
assert!(!cache.messages.contains_key(&ChannelId(2)));
}
}