1use crate::{CacheableGuild, CacheableModels, InMemoryCache, UpdateCache, config::ResourceType};
2use dashmap::DashMap;
3use std::{collections::HashSet, hash::Hash, mem};
4use twilight_model::{
5 gateway::payload::incoming::{GuildCreate, GuildDelete, GuildUpdate},
6 guild::Guild,
7 id::{Id, marker::GuildMarker},
8};
9
10impl<CacheModels: CacheableModels> InMemoryCache<CacheModels> {
11 #[allow(clippy::too_many_lines)]
12 fn cache_guild(&self, mut guild: Guild) {
13 if self.wants(ResourceType::CHANNEL) {
16 self.guild_channels.insert(guild.id, HashSet::new());
17
18 let mut channels = mem::take(&mut guild.channels);
19 let mut threads = mem::take(&mut guild.threads);
20
21 for channel in &mut channels {
22 channel.guild_id = Some(guild.id);
23 }
24
25 for channel in &mut threads {
26 channel.guild_id = Some(guild.id);
27 }
28
29 self.cache_channels(channels);
30 self.cache_channels(threads);
31 }
32
33 if self.wants(ResourceType::EMOJI) {
34 self.guild_emojis.insert(guild.id, HashSet::new());
35 self.cache_emojis(guild.id, mem::take(&mut guild.emojis));
36 }
37
38 if self.wants(ResourceType::MEMBER) {
39 self.guild_members.insert(guild.id, HashSet::new());
40 self.cache_members(guild.id, mem::take(&mut guild.members));
41 }
42
43 if self.wants(ResourceType::PRESENCE) {
44 self.guild_presences.insert(guild.id, HashSet::new());
45 self.cache_presences(guild.id, mem::take(&mut guild.presences));
46 }
47
48 if self.wants(ResourceType::ROLE) {
49 self.guild_roles.insert(guild.id, HashSet::new());
50 self.cache_roles(guild.id, mem::take(&mut guild.roles));
51 }
52
53 if self.wants(ResourceType::STICKER) {
54 self.guild_stickers.insert(guild.id, HashSet::new());
55 self.cache_stickers(guild.id, mem::take(&mut guild.stickers));
56 }
57
58 if self.wants(ResourceType::VOICE_STATE) {
59 self.voice_state_guilds.insert(guild.id, HashSet::new());
60 self.cache_voice_states(mem::take(&mut guild.voice_states));
61 }
62
63 if self.wants(ResourceType::STAGE_INSTANCE) {
64 self.guild_stage_instances.insert(guild.id, HashSet::new());
65 self.cache_stage_instances(guild.id, mem::take(&mut guild.stage_instances));
66 }
67
68 if self.wants(ResourceType::GUILD_SCHEDULED_EVENT) {
69 self.guild_scheduled_events.insert(guild.id, HashSet::new());
70 self.cache_guild_scheduled_events(
71 guild.id,
72 mem::take(&mut guild.guild_scheduled_events),
73 );
74 }
75
76 if self.wants(ResourceType::GUILD) {
77 let guild = CacheModels::Guild::from(guild);
78 self.unavailable_guilds.remove(&guild.id());
79 self.guilds.insert(guild.id(), guild);
80 }
81 }
82
83 pub(crate) fn delete_guild(&self, id: Id<GuildMarker>, unavailable: bool) {
84 fn remove_ids<T: Eq + Hash, U>(
85 guild_map: &DashMap<Id<GuildMarker>, HashSet<T>>,
86 container: &DashMap<T, U>,
87 guild_id: Id<GuildMarker>,
88 ) {
89 if let Some((_, ids)) = guild_map.remove(&guild_id) {
90 for id in ids {
91 container.remove(&id);
92 }
93 }
94 }
95
96 if self.wants(ResourceType::GUILD) {
97 if unavailable {
98 if let Some(mut guild) = self.guilds.get_mut(&id) {
99 guild.set_unavailable(Some(true));
100 }
101 } else {
102 self.guilds.remove(&id);
103 }
104 }
105
106 if self.wants(ResourceType::CHANNEL) {
107 remove_ids(&self.guild_channels, &self.channels, id);
108 }
109
110 if self.wants(ResourceType::EMOJI) {
111 remove_ids(&self.guild_emojis, &self.emojis, id);
112 }
113
114 if self.wants(ResourceType::ROLE) {
115 remove_ids(&self.guild_roles, &self.roles, id);
116 }
117
118 if self.wants(ResourceType::STICKER) {
119 remove_ids(&self.guild_stickers, &self.stickers, id);
120 }
121
122 if self.wants(ResourceType::VOICE_STATE) {
123 self.voice_state_guilds.remove(&id);
125 }
126
127 if self.wants(ResourceType::MEMBER)
128 && let Some((_, ids)) = self.guild_members.remove(&id)
129 {
130 for user_id in ids {
131 self.members.remove(&(id, user_id));
132 }
133 }
134
135 if self.wants(ResourceType::PRESENCE)
136 && let Some((_, ids)) = self.guild_presences.remove(&id)
137 {
138 for user_id in ids {
139 self.presences.remove(&(id, user_id));
140 }
141 }
142 }
143}
144
145impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for GuildCreate {
146 fn update(&self, cache: &InMemoryCache<CacheModels>) {
147 match self {
148 GuildCreate::Available(g) => cache.cache_guild(g.clone()),
149 GuildCreate::Unavailable(g) => {
150 cache.unavailable_guild(g.id);
151 }
152 }
153 }
154}
155
156impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for GuildDelete {
157 fn update(&self, cache: &InMemoryCache<CacheModels>) {
158 cache.delete_guild(self.id, false);
159 }
160}
161
162impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for GuildUpdate {
163 fn update(&self, cache: &InMemoryCache<CacheModels>) {
164 if !cache.wants(ResourceType::GUILD) {
165 return;
166 }
167
168 if let Some(mut guild) = cache.guilds.get_mut(&self.0.id) {
169 guild.update_with_guild_update(self);
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use crate::{DefaultInMemoryCache, test};
177 use std::str::FromStr;
178 use twilight_model::{
179 channel::{
180 Channel, ChannelType,
181 thread::{AutoArchiveDuration, ThreadMember, ThreadMetadata},
182 },
183 gateway::payload::incoming::{
184 GuildCreate, GuildUpdate, MemberAdd, MemberRemove, UnavailableGuild,
185 },
186 guild::{
187 AfkTimeout, DefaultMessageNotificationLevel, ExplicitContentFilter, Guild, MfaLevel,
188 NSFWLevel, PartialGuild, Permissions, PremiumTier, SystemChannelFlags,
189 VerificationLevel,
190 },
191 id::Id,
192 util::datetime::{Timestamp, TimestampParseError},
193 };
194
195 #[allow(clippy::too_many_lines)]
196 #[test]
197 fn guild_create_channels_have_guild_ids() -> Result<(), TimestampParseError> {
198 const DATETIME: &str = "2021-09-19T14:17:32.000000+00:00";
199
200 let timestamp = Timestamp::from_str(DATETIME)?;
201
202 let channels = Vec::from([Channel {
203 application_id: None,
204 applied_tags: None,
205 available_tags: None,
206 bitrate: None,
207 default_auto_archive_duration: None,
208 default_forum_layout: None,
209 default_reaction_emoji: None,
210 default_sort_order: None,
211 default_thread_rate_limit_per_user: None,
212 flags: None,
213 guild_id: None,
214 icon: None,
215 id: Id::new(111),
216 invitable: None,
217 kind: ChannelType::GuildText,
218 last_message_id: None,
219 last_pin_timestamp: None,
220 managed: None,
221 member: None,
222 member_count: None,
223 message_count: None,
224 name: Some("guild channel with no guild id".to_owned()),
225 newly_created: None,
226 nsfw: Some(true),
227 owner_id: None,
228 parent_id: None,
229 permission_overwrites: Some(Vec::new()),
230 position: Some(1),
231 rate_limit_per_user: None,
232 recipients: None,
233 rtc_region: None,
234 thread_metadata: None,
235 topic: None,
236 user_limit: None,
237 video_quality_mode: None,
238 }]);
239
240 let threads = Vec::from([Channel {
241 application_id: None,
242 applied_tags: None,
243 available_tags: None,
244 bitrate: None,
245 default_auto_archive_duration: None,
246 default_forum_layout: None,
247 default_reaction_emoji: None,
248 default_sort_order: None,
249 default_thread_rate_limit_per_user: None,
250 flags: None,
251 guild_id: None,
252 icon: None,
253 id: Id::new(222),
254 invitable: None,
255 kind: ChannelType::PublicThread,
256 last_message_id: None,
257 last_pin_timestamp: None,
258 managed: Some(true),
259 member: Some(ThreadMember {
260 flags: 0,
261 id: Some(Id::new(1)),
262 join_timestamp: timestamp,
263 member: None,
264 presence: None,
265 user_id: Some(Id::new(2)),
266 }),
267 member_count: Some(0),
268 message_count: Some(0),
269 name: Some("guild thread with no guild id".to_owned()),
270 newly_created: None,
271 nsfw: None,
272 owner_id: None,
273 parent_id: None,
274 permission_overwrites: None,
275 position: None,
276 rate_limit_per_user: None,
277 recipients: None,
278 rtc_region: None,
279 thread_metadata: Some(ThreadMetadata {
280 archived: false,
281 auto_archive_duration: AutoArchiveDuration::Hour,
282 archive_timestamp: timestamp,
283 create_timestamp: Some(timestamp),
284 invitable: None,
285 locked: false,
286 }),
287 topic: None,
288 user_limit: None,
289 video_quality_mode: None,
290 }]);
291
292 let guild = Guild {
293 afk_channel_id: None,
294 afk_timeout: AfkTimeout::FIFTEEN_MINUTES,
295 application_id: None,
296 approximate_member_count: None,
297 approximate_presence_count: None,
298 banner: None,
299 channels,
300 default_message_notifications: DefaultMessageNotificationLevel::Mentions,
301 description: None,
302 discovery_splash: None,
303 emojis: Vec::new(),
304 explicit_content_filter: ExplicitContentFilter::AllMembers,
305 features: vec![],
306 guild_scheduled_events: Vec::new(),
307 icon: None,
308 id: Id::new(123),
309 joined_at: Some(Timestamp::from_secs(1_632_072_645).expect("non zero")),
310 large: false,
311 max_members: Some(50),
312 max_presences: Some(100),
313 max_stage_video_channel_users: Some(10),
314 max_video_channel_users: None,
315 member_count: Some(25),
316 members: Vec::new(),
317 mfa_level: MfaLevel::Elevated,
318 name: "this is a guild".to_owned(),
319 nsfw_level: NSFWLevel::AgeRestricted,
320 owner_id: Id::new(456),
321 owner: Some(false),
322 permissions: Some(Permissions::SEND_MESSAGES),
323 preferred_locale: "en-GB".to_owned(),
324 premium_progress_bar_enabled: true,
325 premium_subscription_count: Some(0),
326 premium_tier: PremiumTier::None,
327 presences: Vec::new(),
328 public_updates_channel_id: None,
329 roles: Vec::new(),
330 rules_channel_id: None,
331 safety_alerts_channel_id: Some(Id::new(789)),
332 splash: None,
333 stage_instances: Vec::new(),
334 stickers: Vec::new(),
335 system_channel_flags: SystemChannelFlags::SUPPRESS_JOIN_NOTIFICATIONS,
336 system_channel_id: None,
337 threads,
338 unavailable: Some(false),
339 vanity_url_code: None,
340 verification_level: VerificationLevel::VeryHigh,
341 voice_states: Vec::new(),
342 widget_channel_id: None,
343 widget_enabled: None,
344 };
345
346 let cache = DefaultInMemoryCache::new();
347 cache.cache_guild(guild);
348
349 let channel = cache.channel(Id::new(111)).unwrap();
350
351 let thread = cache.channel(Id::new(222)).unwrap();
352
353 assert_eq!(Some(Id::new(123)), channel.guild_id);
358 assert_eq!(Some(Id::new(123)), thread.guild_id);
359
360 Ok(())
361 }
362
363 #[test]
364 fn unavailable_available_guild() {
365 let cache = DefaultInMemoryCache::new();
366 let guild = test::guild(Id::new(1), None);
367
368 cache.update(&GuildCreate::Unavailable(
369 twilight_model::guild::UnavailableGuild {
370 id: guild.id,
371 unavailable: true,
372 },
373 ));
374 assert!(cache.unavailable_guilds.get(&guild.id).is_some());
375
376 cache.update(&GuildCreate::Available(guild.clone()));
377 assert_eq!(*cache.guilds.get(&guild.id).unwrap(), guild);
378 assert!(cache.unavailable_guilds.get(&guild.id).is_none());
379
380 cache.update(&GuildCreate::Unavailable(
381 twilight_model::guild::UnavailableGuild {
382 id: guild.id,
383 unavailable: true,
384 },
385 ));
386 assert!(cache.unavailable_guilds.get(&guild.id).is_some());
387 assert!(cache.guilds.get(&guild.id).unwrap().unavailable.unwrap());
388
389 cache.update(&GuildCreate::Available(guild.clone()));
390 assert!(
391 !cache
392 .guilds
393 .get(&guild.id)
394 .unwrap()
395 .unavailable
396 .unwrap_or(false)
397 );
398 assert!(cache.unavailable_guilds.get(&guild.id).is_none());
399 }
400
401 #[test]
402 fn guild_update() {
403 let cache = DefaultInMemoryCache::new();
404 let guild = test::guild(Id::new(1), None);
405
406 cache.update(&GuildCreate::Available(guild.clone()));
407
408 let mutation = PartialGuild {
409 id: guild.id,
410 afk_channel_id: guild.afk_channel_id,
411 afk_timeout: guild.afk_timeout,
412 application_id: guild.application_id,
413 banner: guild.banner,
414 default_message_notifications: guild.default_message_notifications,
415 description: guild.description,
416 discovery_splash: guild.discovery_splash,
417 emojis: guild.emojis,
418 explicit_content_filter: guild.explicit_content_filter,
419 features: guild.features,
420 icon: guild.icon,
421 max_members: guild.max_members,
422 max_presences: guild.max_presences,
423 member_count: guild.member_count,
424 mfa_level: guild.mfa_level,
425 name: "test2222".to_owned(),
426 nsfw_level: guild.nsfw_level,
427 owner_id: Id::new(2),
428 owner: guild.owner,
429 permissions: guild.permissions,
430 preferred_locale: guild.preferred_locale,
431 premium_progress_bar_enabled: guild.premium_progress_bar_enabled,
432 premium_subscription_count: guild.premium_subscription_count,
433 premium_tier: guild.premium_tier,
434 public_updates_channel_id: None,
435 roles: guild.roles,
436 rules_channel_id: guild.rules_channel_id,
437 splash: guild.splash,
438 system_channel_flags: guild.system_channel_flags,
439 system_channel_id: guild.system_channel_id,
440 verification_level: guild.verification_level,
441 vanity_url_code: guild.vanity_url_code,
442 widget_channel_id: guild.widget_channel_id,
443 widget_enabled: guild.widget_enabled,
444 };
445
446 cache.update(&GuildUpdate(mutation.clone()));
447
448 assert_eq!(cache.guild(guild.id).unwrap().name, mutation.name);
449 assert_eq!(cache.guild(guild.id).unwrap().owner_id, mutation.owner_id);
450 assert_eq!(cache.guild(guild.id).unwrap().id, mutation.id);
451 }
452
453 #[test]
454 fn guild_member_count() {
455 let user_id = Id::new(2);
456 let guild_id = Id::new(1);
457 let cache = DefaultInMemoryCache::new();
458 let user = test::user(user_id);
459 let member = test::member(user_id);
460 let guild = test::guild(guild_id, Some(1));
461
462 cache.update(&GuildCreate::Available(guild));
463 cache.update(&MemberAdd { guild_id, member });
464
465 assert_eq!(cache.guild(guild_id).unwrap().member_count, Some(2));
466
467 cache.update(&MemberRemove { guild_id, user });
468
469 assert_eq!(cache.guild(guild_id).unwrap().member_count, Some(1));
470 }
471
472 #[test]
473 fn guild_members_size_after_unavailable() {
474 let user_id = Id::new(2);
475 let guild_id = Id::new(1);
476 let cache = DefaultInMemoryCache::new();
477 let member = test::member(user_id);
478 let mut guild = test::guild(guild_id, Some(1));
479 guild.members.push(member);
480
481 cache.update(&GuildCreate::Available(guild.clone()));
482
483 assert_eq!(
484 1,
485 cache
486 .guild_members(guild_id)
487 .map(|members| members.len())
488 .unwrap_or_default()
489 );
490
491 cache.update(&UnavailableGuild { id: guild_id });
492
493 assert_eq!(
494 0,
495 cache
496 .guild_members(guild_id)
497 .map(|members| members.len())
498 .unwrap_or_default()
499 );
500 assert!(cache.guild(guild_id).unwrap().unavailable.unwrap());
501
502 cache.update(&GuildCreate::Available(guild));
503
504 assert_eq!(
505 1,
506 cache
507 .guild_members(guild_id)
508 .map(|members| members.len())
509 .unwrap_or_default()
510 );
511 assert!(!cache.guild(guild_id).unwrap().unavailable.unwrap_or(false));
512 }
513}