use model::prelude::*;
use parking_lot::RwLock;
use std::collections::{
hash_map::Entry,
HashMap,
HashSet,
VecDeque,
};
use std::{
default::Default,
sync::Arc
};
mod cache_update;
mod settings;
pub use self::cache_update::CacheUpdate;
pub use self::settings::Settings;
type MessageCache = HashMap<ChannelId, HashMap<MessageId, Message>>;
#[derive(Clone, Debug)]
pub struct Cache {
pub channels: HashMap<ChannelId, Arc<RwLock<GuildChannel>>>,
pub categories: HashMap<ChannelId, Arc<RwLock<ChannelCategory>>>,
pub groups: HashMap<ChannelId, Arc<RwLock<Group>>>,
pub guilds: HashMap<GuildId, Arc<RwLock<Guild>>>,
pub messages: MessageCache,
pub notes: HashMap<UserId, String>,
pub presences: HashMap<UserId, Presence>,
pub private_channels: HashMap<ChannelId, Arc<RwLock<PrivateChannel>>>,
pub shard_count: u64,
pub unavailable_guilds: HashSet<GuildId>,
pub user: CurrentUser,
pub users: HashMap<UserId, Arc<RwLock<User>>>,
pub(crate) message_queue: HashMap<ChannelId, VecDeque<MessageId>>,
settings: Settings,
__nonexhaustive: (),
}
impl Cache {
#[inline]
pub fn new() -> Self {
Self::default()
}
pub fn new_with_settings(settings: Settings) -> Self {
Self {
settings,
..Default::default()
}
}
pub fn unknown_members(&self) -> u64 {
let mut total = 0;
for guild in self.guilds.values() {
let guild = guild.read();
let members = guild.members.len() as u64;
if guild.member_count > members {
total += guild.member_count - members;
}
}
total
}
pub fn all_private_channels(&self) -> Vec<&ChannelId> {
self.groups
.keys()
.chain(self.private_channels.keys())
.collect()
}
pub fn all_guilds(&self) -> Vec<&GuildId> {
self.guilds
.keys()
.chain(self.unavailable_guilds.iter())
.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) {
return Some(Channel::Guild(Arc::clone(channel)));
}
if let Some(private_channel) = self.private_channels.get(&id) {
return Some(Channel::Private(Arc::clone(private_channel)));
}
if let Some(group) = self.groups.get(&id) {
return Some(Channel::Group(Arc::clone(group)));
}
None
}
#[inline]
pub fn guild<G: Into<GuildId>>(&self, id: G) -> Option<Arc<RwLock<Guild>>> {
self._guild(id.into())
}
fn _guild(&self, id: GuildId) -> Option<Arc<RwLock<Guild>>> {
self.guilds.get(&id).cloned()
}
#[inline]
pub fn guild_channel<C: Into<ChannelId>>(&self, id: C) -> Option<Arc<RwLock<GuildChannel>>> {
self._guild_channel(id.into())
}
fn _guild_channel(&self, id: ChannelId) -> Option<Arc<RwLock<GuildChannel>>> {
self.channels.get(&id).cloned()
}
#[inline]
pub fn group<C: Into<ChannelId>>(&self, id: C) -> Option<Arc<RwLock<Group>>> {
self._group(id.into())
}
fn _group(&self, id: ChannelId) -> Option<Arc<RwLock<Group>>> {
self.groups.get(&id).cloned()
}
#[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> {
self.guilds.get(&guild_id).and_then(|guild| {
guild.read().members.get(&user_id).cloned()
})
}
#[inline]
pub fn private_channel<C: Into<ChannelId>>(&self,
channel_id: C)
-> Option<Arc<RwLock<PrivateChannel>>> {
self._private_channel(channel_id.into())
}
fn _private_channel(&self, channel_id: ChannelId) -> Option<Arc<RwLock<PrivateChannel>>> {
self.private_channels.get(&channel_id).cloned()
}
#[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.read().roles.get(&role_id).cloned())
}
pub fn settings(&self) -> &Settings {
&self.settings
}
pub fn settings_mut(&mut self) -> &mut Settings {
&mut self.settings
}
#[inline]
pub fn user<U: Into<UserId>>(&self, user_id: U) -> Option<Arc<RwLock<User>>> {
self._user(user_id.into())
}
fn _user(&self, user_id: UserId) -> Option<Arc<RwLock<User>>> {
self.users.get(&user_id).cloned()
}
#[inline]
pub fn categories<C: Into<ChannelId>>(&self,
channel_id: C)
-> Option<Arc<RwLock<ChannelCategory>>> {
self._categories(channel_id.into())
}
fn _categories(&self, channel_id: ChannelId) -> Option<Arc<RwLock<ChannelCategory>>> {
self.categories.get(&channel_id).cloned()
}
pub fn update<E: CacheUpdate>(&mut self, e: &mut E) -> Option<E::Output> {
e.update(self)
}
pub(crate) fn update_user_entry(&mut self, user: &User) {
match self.users.entry(user.id) {
Entry::Vacant(e) => {
e.insert(Arc::new(RwLock::new(user.clone())));
},
Entry::Occupied(mut e) => {
e.get_mut().write().clone_from(user);
},
}
}
}
impl Default for Cache {
fn default() -> Cache {
Cache {
channels: HashMap::default(),
categories: HashMap::default(),
groups: HashMap::with_capacity(128),
guilds: HashMap::default(),
messages: HashMap::default(),
notes: HashMap::default(),
presences: HashMap::default(),
private_channels: HashMap::with_capacity(128),
settings: Settings::default(),
shard_count: 1,
unavailable_guilds: HashSet::default(),
user: CurrentUser::default(),
users: HashMap::default(),
message_queue: HashMap::default(),
__nonexhaustive: (),
}
}
}
#[cfg(test)]
mod test {
use chrono::DateTime;
use serde_json::{Number, Value};
use std::{
collections::HashMap,
sync::Arc,
};
use {
cache::{Cache, CacheUpdate, Settings},
model::prelude::*,
prelude::RwLock,
};
#[test]
fn test_cache_messages() {
let mut settings = Settings::new();
settings.max_messages(2);
let mut cache = Cache::new_with_settings(settings);
let datetime = DateTime::parse_from_str(
"1983 Apr 13 12:09:14.274 +0000",
"%Y %b %d %H:%M:%S%.3f %z",
).unwrap();
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(),
},
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![],
mentions: vec![],
nonce: Value::Number(Number::from(1)),
pinned: false,
reactions: vec![],
timestamp: datetime.clone(),
tts: false,
webhook_id: None,
},
};
assert!(!cache.messages.contains_key(&event.message.channel_id));
assert!(event.update(&mut cache).is_none());
assert!(event.update(&mut cache).is_none());
assert_eq!(cache.messages.get(&event.message.channel_id).unwrap().len(), 1);
event.message.id = MessageId(4);
assert!(event.update(&mut cache).is_none());
assert_eq!(cache.messages.get(&event.message.channel_id).unwrap().len(), 2);
event.message.id = MessageId(5);
assert!(event.update(&mut 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 guild_channel = GuildChannel {
id: event.message.channel_id,
bitrate: None,
category_id: None,
guild_id: event.message.guild_id.unwrap(),
kind: ChannelType::Text,
last_message_id: None,
last_pin_timestamp: None,
name: String::new(),
permission_overwrites: vec![],
position: 0,
topic: None,
user_limit: None,
nsfw: false,
};
let mut delete = ChannelDeleteEvent {
channel: Channel::Guild(Arc::new(RwLock::new(guild_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), Arc::new(RwLock::new(guild_channel.clone())));
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(),
region: String::new(),
roles: HashMap::new(),
splash: None,
system_channel_id: None,
verification_level: VerificationLevel::Low,
voice_states: HashMap::new(),
channels,
},
}
};
assert!(cache.update(&mut guild_create).is_none());
assert!(cache.update(&mut event).is_none());
let mut guild_delete = GuildDeleteEvent {
guild: PartialGuild {
id: GuildId(1),
afk_channel_id: None,
afk_timeout: 0,
default_message_notifications: DefaultMessageNotificationLevel::All,
embed_channel_id: None,
embed_enabled: false,
emojis: HashMap::new(),
features: vec![],
icon: None,
mfa_level: MfaLevel::None,
name: String::new(),
owner_id: UserId(3),
region: String::new(),
roles: HashMap::new(),
splash: None,
verification_level: VerificationLevel::Low,
},
};
assert!(cache.update(&mut guild_delete).is_some());
assert!(!cache.messages.contains_key(&ChannelId(2)));
}
}