1use crate::{config::ResourceType, CacheableGuild, CacheableModels, InMemoryCache, UpdateCache};
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::{marker::GuildMarker, Id},
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 if let Some((_, ids)) = self.guild_members.remove(&id) {
129 for user_id in ids {
130 self.members.remove(&(id, user_id));
131 }
132 }
133 }
134
135 if self.wants(ResourceType::PRESENCE) {
136 if let Some((_, ids)) = self.guild_presences.remove(&id) {
137 for user_id in ids {
138 self.presences.remove(&(id, user_id));
139 }
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::{test, DefaultInMemoryCache};
177 use std::str::FromStr;
178 use twilight_model::{
179 channel::{
180 thread::{AutoArchiveDuration, ThreadMember, ThreadMetadata},
181 Channel, ChannelType,
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!(!cache
391 .guilds
392 .get(&guild.id)
393 .unwrap()
394 .unavailable
395 .unwrap_or(false));
396 assert!(cache.unavailable_guilds.get(&guild.id).is_none());
397 }
398
399 #[test]
400 fn guild_update() {
401 let cache = DefaultInMemoryCache::new();
402 let guild = test::guild(Id::new(1), None);
403
404 cache.update(&GuildCreate::Available(guild.clone()));
405
406 let mutation = PartialGuild {
407 id: guild.id,
408 afk_channel_id: guild.afk_channel_id,
409 afk_timeout: guild.afk_timeout,
410 application_id: guild.application_id,
411 banner: guild.banner,
412 default_message_notifications: guild.default_message_notifications,
413 description: guild.description,
414 discovery_splash: guild.discovery_splash,
415 emojis: guild.emojis,
416 explicit_content_filter: guild.explicit_content_filter,
417 features: guild.features,
418 icon: guild.icon,
419 max_members: guild.max_members,
420 max_presences: guild.max_presences,
421 member_count: guild.member_count,
422 mfa_level: guild.mfa_level,
423 name: "test2222".to_owned(),
424 nsfw_level: guild.nsfw_level,
425 owner_id: Id::new(2),
426 owner: guild.owner,
427 permissions: guild.permissions,
428 preferred_locale: guild.preferred_locale,
429 premium_progress_bar_enabled: guild.premium_progress_bar_enabled,
430 premium_subscription_count: guild.premium_subscription_count,
431 premium_tier: guild.premium_tier,
432 public_updates_channel_id: None,
433 roles: guild.roles,
434 rules_channel_id: guild.rules_channel_id,
435 splash: guild.splash,
436 system_channel_flags: guild.system_channel_flags,
437 system_channel_id: guild.system_channel_id,
438 verification_level: guild.verification_level,
439 vanity_url_code: guild.vanity_url_code,
440 widget_channel_id: guild.widget_channel_id,
441 widget_enabled: guild.widget_enabled,
442 };
443
444 cache.update(&GuildUpdate(mutation.clone()));
445
446 assert_eq!(cache.guild(guild.id).unwrap().name, mutation.name);
447 assert_eq!(cache.guild(guild.id).unwrap().owner_id, mutation.owner_id);
448 assert_eq!(cache.guild(guild.id).unwrap().id, mutation.id);
449 }
450
451 #[test]
452 fn guild_member_count() {
453 let user_id = Id::new(2);
454 let guild_id = Id::new(1);
455 let cache = DefaultInMemoryCache::new();
456 let user = test::user(user_id);
457 let member = test::member(user_id);
458 let guild = test::guild(guild_id, Some(1));
459
460 cache.update(&GuildCreate::Available(guild));
461 cache.update(&MemberAdd { guild_id, member });
462
463 assert_eq!(cache.guild(guild_id).unwrap().member_count, Some(2));
464
465 cache.update(&MemberRemove { guild_id, user });
466
467 assert_eq!(cache.guild(guild_id).unwrap().member_count, Some(1));
468 }
469
470 #[test]
471 fn guild_members_size_after_unavailable() {
472 let user_id = Id::new(2);
473 let guild_id = Id::new(1);
474 let cache = DefaultInMemoryCache::new();
475 let member = test::member(user_id);
476 let mut guild = test::guild(guild_id, Some(1));
477 guild.members.push(member);
478
479 cache.update(&GuildCreate::Available(guild.clone()));
480
481 assert_eq!(
482 1,
483 cache
484 .guild_members(guild_id)
485 .map(|members| members.len())
486 .unwrap_or_default()
487 );
488
489 cache.update(&UnavailableGuild { id: guild_id });
490
491 assert_eq!(
492 0,
493 cache
494 .guild_members(guild_id)
495 .map(|members| members.len())
496 .unwrap_or_default()
497 );
498 assert!(cache.guild(guild_id).unwrap().unavailable.unwrap());
499
500 cache.update(&GuildCreate::Available(guild));
501
502 assert_eq!(
503 1,
504 cache
505 .guild_members(guild_id)
506 .map(|members| members.len())
507 .unwrap_or_default()
508 );
509 assert!(!cache.guild(guild_id).unwrap().unavailable.unwrap_or(false));
510 }
511}