use crate::{CacheableGuild, CacheableModels, InMemoryCache, UpdateCache, config::ResourceType};
use dashmap::DashMap;
use std::{collections::HashSet, hash::Hash, mem};
use twilight_model::{
gateway::payload::incoming::{GuildCreate, GuildDelete, GuildUpdate},
guild::Guild,
id::{Id, marker::GuildMarker},
};
impl<CacheModels: CacheableModels> InMemoryCache<CacheModels> {
#[allow(clippy::too_many_lines)]
fn cache_guild(&self, mut guild: Guild) {
if self.wants(ResourceType::CHANNEL) {
self.guild_channels.insert(guild.id, HashSet::new());
let mut channels = mem::take(&mut guild.channels);
let mut threads = mem::take(&mut guild.threads);
for channel in &mut channels {
channel.guild_id = Some(guild.id);
}
for channel in &mut threads {
channel.guild_id = Some(guild.id);
}
self.cache_channels(channels);
self.cache_channels(threads);
}
if self.wants(ResourceType::EMOJI) {
self.guild_emojis.insert(guild.id, HashSet::new());
self.cache_emojis(guild.id, mem::take(&mut guild.emojis));
}
if self.wants(ResourceType::MEMBER) {
self.guild_members.insert(guild.id, HashSet::new());
self.cache_members(guild.id, mem::take(&mut guild.members));
}
if self.wants(ResourceType::PRESENCE) {
self.guild_presences.insert(guild.id, HashSet::new());
self.cache_presences(guild.id, mem::take(&mut guild.presences));
}
if self.wants(ResourceType::ROLE) {
self.guild_roles.insert(guild.id, HashSet::new());
self.cache_roles(guild.id, mem::take(&mut guild.roles));
}
if self.wants(ResourceType::STICKER) {
self.guild_stickers.insert(guild.id, HashSet::new());
self.cache_stickers(guild.id, mem::take(&mut guild.stickers));
}
if self.wants(ResourceType::VOICE_STATE) {
self.voice_state_guilds.insert(guild.id, HashSet::new());
self.cache_voice_states(mem::take(&mut guild.voice_states));
}
if self.wants(ResourceType::STAGE_INSTANCE) {
self.guild_stage_instances.insert(guild.id, HashSet::new());
self.cache_stage_instances(guild.id, mem::take(&mut guild.stage_instances));
}
if self.wants(ResourceType::GUILD_SCHEDULED_EVENT) {
self.guild_scheduled_events.insert(guild.id, HashSet::new());
self.cache_guild_scheduled_events(
guild.id,
mem::take(&mut guild.guild_scheduled_events),
);
}
if self.wants(ResourceType::GUILD) {
let guild = CacheModels::Guild::from(guild);
self.unavailable_guilds.remove(&guild.id());
self.guilds.insert(guild.id(), guild);
}
}
pub(crate) fn delete_guild(&self, id: Id<GuildMarker>, unavailable: bool) {
fn remove_ids<T: Eq + Hash, U>(
guild_map: &DashMap<Id<GuildMarker>, HashSet<T>>,
container: &DashMap<T, U>,
guild_id: Id<GuildMarker>,
) {
if let Some((_, ids)) = guild_map.remove(&guild_id) {
for id in ids {
container.remove(&id);
}
}
}
if self.wants(ResourceType::GUILD) {
if unavailable {
if let Some(mut guild) = self.guilds.get_mut(&id) {
guild.set_unavailable(Some(true));
}
} else {
self.guilds.remove(&id);
}
}
if self.wants(ResourceType::CHANNEL) {
remove_ids(&self.guild_channels, &self.channels, id);
}
if self.wants(ResourceType::EMOJI) {
remove_ids(&self.guild_emojis, &self.emojis, id);
}
if self.wants(ResourceType::ROLE) {
remove_ids(&self.guild_roles, &self.roles, id);
}
if self.wants(ResourceType::STICKER) {
remove_ids(&self.guild_stickers, &self.stickers, id);
}
if self.wants(ResourceType::VOICE_STATE) {
self.voice_state_guilds.remove(&id);
}
if self.wants(ResourceType::MEMBER)
&& let Some((_, ids)) = self.guild_members.remove(&id)
{
for user_id in ids {
self.members.remove(&(id, user_id));
}
}
if self.wants(ResourceType::PRESENCE)
&& let Some((_, ids)) = self.guild_presences.remove(&id)
{
for user_id in ids {
self.presences.remove(&(id, user_id));
}
}
}
}
impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for GuildCreate {
fn update(&self, cache: &InMemoryCache<CacheModels>) {
match self {
GuildCreate::Available(g) => cache.cache_guild(g.clone()),
GuildCreate::Unavailable(g) => {
cache.unavailable_guild(g.id);
}
}
}
}
impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for GuildDelete {
fn update(&self, cache: &InMemoryCache<CacheModels>) {
cache.delete_guild(self.id, false);
}
}
impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for GuildUpdate {
fn update(&self, cache: &InMemoryCache<CacheModels>) {
if !cache.wants(ResourceType::GUILD) {
return;
}
if let Some(mut guild) = cache.guilds.get_mut(&self.0.id) {
guild.update_with_guild_update(self);
}
}
}
#[cfg(test)]
mod tests {
use crate::{DefaultInMemoryCache, test};
use std::str::FromStr;
use twilight_model::{
channel::{
Channel, ChannelType,
thread::{AutoArchiveDuration, ThreadMember, ThreadMetadata},
},
gateway::payload::incoming::{
GuildCreate, GuildUpdate, MemberAdd, MemberRemove, UnavailableGuild,
},
guild::{
AfkTimeout, DefaultMessageNotificationLevel, ExplicitContentFilter, Guild, MfaLevel,
NSFWLevel, PartialGuild, Permissions, PremiumTier, SystemChannelFlags,
VerificationLevel,
},
id::Id,
util::datetime::{Timestamp, TimestampParseError},
};
#[allow(clippy::too_many_lines)]
#[test]
fn guild_create_channels_have_guild_ids() -> Result<(), TimestampParseError> {
const DATETIME: &str = "2021-09-19T14:17:32.000000+00:00";
let timestamp = Timestamp::from_str(DATETIME)?;
let channels = Vec::from([Channel {
application_id: None,
applied_tags: None,
available_tags: None,
bitrate: None,
default_auto_archive_duration: None,
default_forum_layout: None,
default_reaction_emoji: None,
default_sort_order: None,
default_thread_rate_limit_per_user: None,
flags: None,
guild_id: None,
icon: None,
id: Id::new(111),
invitable: None,
kind: ChannelType::GuildText,
last_message_id: None,
last_pin_timestamp: None,
managed: None,
member: None,
member_count: None,
message_count: None,
name: Some("guild channel with no guild id".to_owned()),
newly_created: None,
nsfw: Some(true),
owner_id: None,
parent_id: None,
permission_overwrites: Some(Vec::new()),
position: Some(1),
rate_limit_per_user: None,
recipients: None,
rtc_region: None,
thread_metadata: None,
topic: None,
user_limit: None,
video_quality_mode: None,
}]);
let threads = Vec::from([Channel {
application_id: None,
applied_tags: None,
available_tags: None,
bitrate: None,
default_auto_archive_duration: None,
default_forum_layout: None,
default_reaction_emoji: None,
default_sort_order: None,
default_thread_rate_limit_per_user: None,
flags: None,
guild_id: None,
icon: None,
id: Id::new(222),
invitable: None,
kind: ChannelType::PublicThread,
last_message_id: None,
last_pin_timestamp: None,
managed: Some(true),
member: Some(ThreadMember {
flags: 0,
id: Some(Id::new(1)),
join_timestamp: timestamp,
member: None,
presence: None,
user_id: Some(Id::new(2)),
}),
member_count: Some(0),
message_count: Some(0),
name: Some("guild thread with no guild id".to_owned()),
newly_created: None,
nsfw: None,
owner_id: None,
parent_id: None,
permission_overwrites: None,
position: None,
rate_limit_per_user: None,
recipients: None,
rtc_region: None,
thread_metadata: Some(ThreadMetadata {
archived: false,
auto_archive_duration: AutoArchiveDuration::Hour,
archive_timestamp: timestamp,
create_timestamp: Some(timestamp),
invitable: None,
locked: false,
}),
topic: None,
user_limit: None,
video_quality_mode: None,
}]);
let guild = Guild {
afk_channel_id: None,
afk_timeout: AfkTimeout::FIFTEEN_MINUTES,
application_id: None,
approximate_member_count: None,
approximate_presence_count: None,
banner: None,
channels,
default_message_notifications: DefaultMessageNotificationLevel::Mentions,
description: None,
discovery_splash: None,
emojis: Vec::new(),
explicit_content_filter: ExplicitContentFilter::AllMembers,
features: vec![],
guild_scheduled_events: Vec::new(),
icon: None,
id: Id::new(123),
joined_at: Some(Timestamp::from_secs(1_632_072_645).expect("non zero")),
large: false,
max_members: Some(50),
max_presences: Some(100),
max_stage_video_channel_users: Some(10),
max_video_channel_users: None,
member_count: Some(25),
members: Vec::new(),
mfa_level: MfaLevel::Elevated,
name: "this is a guild".to_owned(),
nsfw_level: NSFWLevel::AgeRestricted,
owner_id: Id::new(456),
owner: Some(false),
permissions: Some(Permissions::SEND_MESSAGES),
preferred_locale: "en-GB".to_owned(),
premium_progress_bar_enabled: true,
premium_subscription_count: Some(0),
premium_tier: PremiumTier::None,
presences: Vec::new(),
public_updates_channel_id: None,
roles: Vec::new(),
rules_channel_id: None,
safety_alerts_channel_id: Some(Id::new(789)),
splash: None,
stage_instances: Vec::new(),
stickers: Vec::new(),
system_channel_flags: SystemChannelFlags::SUPPRESS_JOIN_NOTIFICATIONS,
system_channel_id: None,
threads,
unavailable: Some(false),
vanity_url_code: None,
verification_level: VerificationLevel::VeryHigh,
voice_states: Vec::new(),
widget_channel_id: None,
widget_enabled: None,
};
let cache = DefaultInMemoryCache::new();
cache.cache_guild(guild);
let channel = cache.channel(Id::new(111)).unwrap();
let thread = cache.channel(Id::new(222)).unwrap();
assert_eq!(Some(Id::new(123)), channel.guild_id);
assert_eq!(Some(Id::new(123)), thread.guild_id);
Ok(())
}
#[test]
fn unavailable_available_guild() {
let cache = DefaultInMemoryCache::new();
let guild = test::guild(Id::new(1), None);
cache.update(&GuildCreate::Unavailable(
twilight_model::guild::UnavailableGuild {
id: guild.id,
unavailable: true,
},
));
assert!(cache.unavailable_guilds.get(&guild.id).is_some());
cache.update(&GuildCreate::Available(guild.clone()));
assert_eq!(*cache.guilds.get(&guild.id).unwrap(), guild);
assert!(cache.unavailable_guilds.get(&guild.id).is_none());
cache.update(&GuildCreate::Unavailable(
twilight_model::guild::UnavailableGuild {
id: guild.id,
unavailable: true,
},
));
assert!(cache.unavailable_guilds.get(&guild.id).is_some());
assert!(cache.guilds.get(&guild.id).unwrap().unavailable.unwrap());
cache.update(&GuildCreate::Available(guild.clone()));
assert!(
!cache
.guilds
.get(&guild.id)
.unwrap()
.unavailable
.unwrap_or(false)
);
assert!(cache.unavailable_guilds.get(&guild.id).is_none());
}
#[test]
fn guild_update() {
let cache = DefaultInMemoryCache::new();
let guild = test::guild(Id::new(1), None);
cache.update(&GuildCreate::Available(guild.clone()));
let mutation = PartialGuild {
id: guild.id,
afk_channel_id: guild.afk_channel_id,
afk_timeout: guild.afk_timeout,
application_id: guild.application_id,
banner: guild.banner,
default_message_notifications: guild.default_message_notifications,
description: guild.description,
discovery_splash: guild.discovery_splash,
emojis: guild.emojis,
explicit_content_filter: guild.explicit_content_filter,
features: guild.features,
icon: guild.icon,
max_members: guild.max_members,
max_presences: guild.max_presences,
member_count: guild.member_count,
mfa_level: guild.mfa_level,
name: "test2222".to_owned(),
nsfw_level: guild.nsfw_level,
owner_id: Id::new(2),
owner: guild.owner,
permissions: guild.permissions,
preferred_locale: guild.preferred_locale,
premium_progress_bar_enabled: guild.premium_progress_bar_enabled,
premium_subscription_count: guild.premium_subscription_count,
premium_tier: guild.premium_tier,
public_updates_channel_id: None,
roles: guild.roles,
rules_channel_id: guild.rules_channel_id,
splash: guild.splash,
system_channel_flags: guild.system_channel_flags,
system_channel_id: guild.system_channel_id,
verification_level: guild.verification_level,
vanity_url_code: guild.vanity_url_code,
widget_channel_id: guild.widget_channel_id,
widget_enabled: guild.widget_enabled,
};
cache.update(&GuildUpdate(mutation.clone()));
assert_eq!(cache.guild(guild.id).unwrap().name, mutation.name);
assert_eq!(cache.guild(guild.id).unwrap().owner_id, mutation.owner_id);
assert_eq!(cache.guild(guild.id).unwrap().id, mutation.id);
}
#[test]
fn guild_member_count() {
let user_id = Id::new(2);
let guild_id = Id::new(1);
let cache = DefaultInMemoryCache::new();
let user = test::user(user_id);
let member = test::member(user_id);
let guild = test::guild(guild_id, Some(1));
cache.update(&GuildCreate::Available(guild));
cache.update(&MemberAdd { guild_id, member });
assert_eq!(cache.guild(guild_id).unwrap().member_count, Some(2));
cache.update(&MemberRemove { guild_id, user });
assert_eq!(cache.guild(guild_id).unwrap().member_count, Some(1));
}
#[test]
fn guild_members_size_after_unavailable() {
let user_id = Id::new(2);
let guild_id = Id::new(1);
let cache = DefaultInMemoryCache::new();
let member = test::member(user_id);
let mut guild = test::guild(guild_id, Some(1));
guild.members.push(member);
cache.update(&GuildCreate::Available(guild.clone()));
assert_eq!(
1,
cache
.guild_members(guild_id)
.map(|members| members.len())
.unwrap_or_default()
);
cache.update(&UnavailableGuild { id: guild_id });
assert_eq!(
0,
cache
.guild_members(guild_id)
.map(|members| members.len())
.unwrap_or_default()
);
assert!(cache.guild(guild_id).unwrap().unavailable.unwrap());
cache.update(&GuildCreate::Available(guild));
assert_eq!(
1,
cache
.guild_members(guild_id)
.map(|members| members.len())
.unwrap_or_default()
);
assert!(!cache.guild(guild_id).unwrap().unavailable.unwrap_or(false));
}
}