diff options
| author | Lakelezz <[email protected]> | 2018-12-18 20:55:32 +0100 |
|---|---|---|
| committer | Alex M. M <[email protected]> | 2018-12-18 20:55:32 +0100 |
| commit | 8cb1bdc6cf992cc55810f5af753666d54f2237d5 (patch) | |
| tree | 052e2f425a6117e45323886bb2b2b683b6d5a1e8 /src | |
| parent | Mutably borrow on `ChannelId`'s `edit`-method. (#447) (diff) | |
| download | serenity-8cb1bdc6cf992cc55810f5af753666d54f2237d5.tar.xz serenity-8cb1bdc6cf992cc55810f5af753666d54f2237d5.zip | |
Remove global Cache (#448)
* Update to use Rust 2018.
* Update examples and use Rust 2018.
* Pass cache via `Context` around instead of being global.
* Remove `lazy_static` from `cache`-feature.
* Update examples to use `Context`'s cache.
* Replace cache's update-timeout-setting with `update_cache_timeout`.
* Update documentation to stop using global cache.
* Move `HttpAndCache` to `lib.rs`.
* Add `__nonexhaustive`-field to `CacheAndHttp`.
* Add `__nonexhaustive` in `CacheAndHttp`-initialisers.
* Avoid `__nonexhaustive`-usage in doctest.
* Remove unnecessary comma in `cfg`-attribute.
Diffstat (limited to 'src')
32 files changed, 863 insertions, 721 deletions
diff --git a/src/builder/create_embed.rs b/src/builder/create_embed.rs index 5c82bb5..f075fcf 100644 --- a/src/builder/create_embed.rs +++ b/src/builder/create_embed.rs @@ -267,9 +267,8 @@ impl CreateEmbed { /// struct Handler; /// /// impl EventHandler for Handler { - /// fn guild_member_addition(&self, _: Context, guild_id: GuildId, member: Member) { - /// use serenity::CACHE; - /// let cache = CACHE.read(); + /// fn guild_member_addition(&self, context: Context, guild_id: GuildId, member: Member) { + /// let cache = context.cache.read(); /// /// if let Some(guild) = cache.guild(guild_id) { /// let guild = guild.read(); @@ -282,7 +281,7 @@ impl CreateEmbed { /// if let Some(channel) = channel_search { /// let user = member.user.read(); /// - /// let _ = channel.write().send_message(|m| { + /// let _ = channel.write().send_message(&context, |m| { /// m.embed(|e| { /// e.author(|a| { /// a.icon_url(&user.face()).name(&user.name) diff --git a/src/builder/create_invite.rs b/src/builder/create_invite.rs index 99c2bb5..4858a8e 100644 --- a/src/builder/create_invite.rs +++ b/src/builder/create_invite.rs @@ -19,10 +19,9 @@ use crate::utils::VecMap; /// struct Handler; /// /// impl EventHandler for Handler { -/// fn message(&self, _: Context, msg: Message) { -/// use serenity::CACHE; +/// fn message(&self, context: Context, msg: Message) { /// if msg.content == "!createinvite" { -/// let channel = match CACHE.read().guild_channel(msg.channel_id) { +/// let channel = match context.cache.read().guild_channel(msg.channel_id) { /// Some(channel) => channel, /// None => { /// let _ = msg.channel_id.say("Error creating invite"); @@ -33,7 +32,7 @@ use crate::utils::VecMap; /// /// let reader = channel.read(); /// -/// let creation = reader.create_invite(|i| { +/// let creation = reader.create_invite(&context, |i| { /// i.max_age(3600).max_uses(10) /// }); /// @@ -80,23 +79,17 @@ impl CreateInvite { /// Create an invite with a max age of `3600` seconds, or 1 hour: /// /// ```rust,no_run - /// # use serenity::CACHE; - /// # use serenity::model::id::ChannelId; - /// # use std::error::Error; + /// # use serenity::{command, model::id::ChannelId}; + /// # use std::{error::Error, sync::Arc}; /// # - /// # fn try_main() -> Result<(), Box<Error>> { - /// # let channel = CACHE.read().guild_channel(81384788765712384).unwrap(); + /// # command!(example(context) { + /// # let channel = context.cache.read().guild_channel(81384788765712384).unwrap(); /// # let channel = channel.read(); /// # - /// let invite = channel.create_invite(|i| { + /// let invite = channel.create_invite(&context, |i| { /// i.max_age(3600) /// })?; - /// # Ok(()) - /// # } - /// # - /// # fn main() { - /// # try_main().unwrap(); - /// # } + /// # }); /// ``` pub fn max_age(&mut self, max_age: u64) -> &mut Self { self.0.insert("max_age", Value::Number(Number::from(max_age))); @@ -114,23 +107,16 @@ impl CreateInvite { /// Create an invite with a max use limit of `5`: /// /// ```rust,no_run - /// # use serenity::CACHE; - /// # use serenity::model::id::ChannelId; - /// # use std::error::Error; + /// # use serenity::{command, model::id::ChannelId}; /// # - /// # fn try_main() -> Result<(), Box<Error>> { - /// # let channel = CACHE.read().guild_channel(81384788765712384).unwrap(); + /// # command!(example(context) { + /// # let channel = context.cache.read().guild_channel(81384788765712384).unwrap(); /// # let channel = channel.read(); /// # - /// let invite = channel.create_invite(|i| { + /// let invite = channel.create_invite(&context, |i| { /// i.max_uses(5) /// })?; - /// # Ok(()) - /// # } - /// # - /// # fn main() { - /// # try_main().unwrap(); - /// # } + /// # }); /// ``` pub fn max_uses(&mut self, max_uses: u64) -> &mut Self { self.0.insert("max_uses", Value::Number(Number::from(max_uses))); @@ -146,23 +132,17 @@ impl CreateInvite { /// Create an invite which is temporary: /// /// ```rust,no_run - /// # use serenity::CACHE; - /// # use serenity::model::id::ChannelId; + /// # use serenity::{command, model::id::ChannelId}; /// # use std::error::Error; /// # - /// # fn try_main() -> Result<(), Box<Error>> { - /// # let channel = CACHE.read().guild_channel(81384788765712384).unwrap(); + /// # command!(example(context) { + /// # let channel = context.cache.read().guild_channel(81384788765712384).unwrap(); /// # let channel = channel.read(); /// # - /// let invite = channel.create_invite(|i| { + /// let invite = channel.create_invite(&context, |i| { /// i.temporary(true) /// })?; - /// # Ok(()) - /// # } - /// # - /// # fn main() { - /// # try_main().unwrap(); - /// # } + /// # }); /// ``` pub fn temporary(&mut self, temporary: bool) -> &mut Self { self.0.insert("temporary", Value::Bool(temporary)); @@ -178,23 +158,16 @@ impl CreateInvite { /// Create an invite which is unique: /// /// ```rust,no_run - /// # use serenity::CACHE; - /// # use serenity::model::id::ChannelId; - /// # use std::error::Error; + /// # use serenity::{command, Error, model::id::ChannelId}; /// # - /// # fn try_main() -> Result<(), Box<Error>> { - /// # let channel = CACHE.read().guild_channel(81384788765712384).unwrap(); + /// # command!(example(context) { + /// # let channel = context.cache.read().guild_channel(81384788765712384).unwrap(); /// # let channel = channel.read(); /// # - /// let invite = channel.create_invite(|i| { + /// let invite = channel.create_invite(&context, |i| { /// i.unique(true) /// })?; - /// # Ok(()) - /// # } - /// # - /// # fn main() { - /// # try_main().unwrap(); - /// # } + /// # }); /// ``` pub fn unique(&mut self, unique: bool) -> &mut Self { self.0.insert("unique", Value::Bool(unique)); diff --git a/src/builder/edit_message.rs b/src/builder/edit_message.rs index 2410a13..8c1cd8f 100644 --- a/src/builder/edit_message.rs +++ b/src/builder/edit_message.rs @@ -10,13 +10,14 @@ use crate::utils::{self, VecMap}; /// Editing the content of a [`Message`] to `"hello"`: /// /// ```rust,no_run -/// # use serenity::model::id::{ChannelId, MessageId}; +/// # use serenity::{command, model::id::{ChannelId, MessageId}}; /// # +/// # command!(example(context) { /// # let mut message = ChannelId(7).message(MessageId(8)).unwrap(); -/// # -/// let _ = message.edit(|m| { +/// let _ = message.edit(&context, |m| { /// m.content("hello") /// }); +/// # }); /// ``` /// /// [`Message`]: ../model/channel/struct.Message.html diff --git a/src/cache/mod.rs b/src/cache/mod.rs index a2c133f..6dbf097 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -42,6 +42,7 @@ //! [`CACHE`]: ../struct.CACHE.html //! [`http`]: ../http/index.html +use std::str::FromStr; use crate::model::prelude::*; use parking_lot::RwLock; use std::collections::{ @@ -53,7 +54,6 @@ use std::collections::{ use std::{ default::Default, sync::Arc, - time::Duration, }; mod cache_update; @@ -64,6 +64,30 @@ pub use self::settings::Settings; type MessageCache = HashMap<ChannelId, HashMap<MessageId, Message>>; +pub trait FromStrAndCache: Sized { + type Err; + + fn from_str(cache: &Arc<RwLock<Cache>>, s: &str) -> Result<Self, Self::Err>; +} + +pub trait StrExt: Sized { + fn parse_cached<F: FromStrAndCache>(&self, cache: &Arc<RwLock<Cache>>) -> Result<F, F::Err>; +} + +impl<'a> StrExt for &'a str { + fn parse_cached<F: FromStrAndCache>(&self, cache: &Arc<RwLock<Cache>>) -> Result<F, F::Err> { + F::from_str(&cache, &self) + } +} + +impl<F: FromStr> FromStrAndCache for F { + type Err = F::Err; + + fn from_str(_cache: &Arc<RwLock<Cache>>, s: &str) -> Result<Self, Self::Err> { + s.parse::<F>() + } +} + /// A cache of all events received over a [`Shard`], where storing at least /// some data from the event is possible. /// @@ -222,7 +246,6 @@ impl Cache { /// # /// # #[cfg(feature = "client")] /// # fn main() { - /// use serenity::CACHE; /// use std::thread; /// use std::time::Duration; /// @@ -241,7 +264,7 @@ impl Cache { /// // seconds. /// thread::sleep(Duration::from_secs(5)); /// - /// println!("{} unknown members", CACHE.read().unknown_members()); + /// println!("{} unknown members", ctx.cache.read().unknown_members()); /// } /// } /// @@ -284,9 +307,15 @@ impl Cache { /// Printing the count of all private channels and groups: /// /// ```rust,no_run - /// use serenity::CACHE; - /// - /// let amount = CACHE.read().all_private_channels().len(); + /// # extern crate parking_lot; + /// # extern crate serenity; + /// # + /// # use serenity::{cache::Cache}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; + /// # + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// let amount = cache.read().all_private_channels().len(); /// /// println!("There are {} private channels", amount); /// ``` @@ -316,13 +345,11 @@ impl Cache { /// # use serenity::model::prelude::*; /// # use serenity::prelude::*; /// # - /// use serenity::CACHE; - /// /// struct Handler; /// /// impl EventHandler for Handler { - /// fn ready(&self, _: Context, _: Ready) { - /// let guilds = CACHE.read().guilds.len(); + /// fn ready(&self, context: Context, _: Ready) { + /// let guilds = context.cache.read().guilds.len(); /// /// println!("Guilds in the Cache: {}", guilds); /// } @@ -397,20 +424,21 @@ impl Cache { /// Retrieve a guild from the cache and print its name: /// /// ```rust,no_run - /// # use std::error::Error; + /// # extern crate parking_lot; + /// # extern crate serenity; /// # - /// # fn try_main() -> Result<(), Box<Error>> { - /// use serenity::CACHE; - /// - /// if let Some(guild) = CACHE.read().guild(7) { + /// # use serenity::{cache::Cache}; + /// # use parking_lot::RwLock; + /// # use std::{error::Error, sync::Arc}; + /// # + /// # fn main() -> Result<(), Box<Error>> { + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// // assuming the cache is in scope, e.g. via `Context` + /// if let Some(guild) = cache.read().guild(7) { /// println!("Guild name: {}", guild.read().name); /// } /// # Ok(()) /// # } - /// # - /// # fn main() { - /// # try_main().unwrap(); - /// # } /// ``` #[inline] pub fn guild<G: Into<GuildId>>(&self, id: G) -> Option<Arc<RwLock<Guild>>> { @@ -438,13 +466,11 @@ impl Cache { /// # use serenity::model::prelude::*; /// # use serenity::prelude::*; /// # - /// use serenity::CACHE; - /// /// struct Handler; /// /// impl EventHandler for Handler { - /// fn message(&self, ctx: Context, message: Message) { - /// let cache = CACHE.read(); + /// fn message(&self, context: Context, message: Message) { + /// let cache = context.cache.read(); /// /// let channel = match cache.guild_channel(message.channel_id) { /// Some(channel) => channel, @@ -496,20 +522,20 @@ impl Cache { /// Retrieve a group from the cache and print its owner's id: /// /// ```rust,no_run - /// # use std::error::Error; + /// # extern crate parking_lot; + /// # extern crate serenity; /// # - /// # fn try_main() -> Result<(), Box<Error>> { - /// use serenity::CACHE; - /// - /// if let Some(group) = CACHE.read().group(7) { + /// # use serenity::cache::Cache; + /// # use parking_lot::RwLock; + /// # use std::{error::Error, sync::Arc}; + /// # + /// # fn main() -> Result<(), Box<Error>> { + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// if let Some(group) = cache.read().group(7) { /// println!("Owner Id: {}", group.read().owner_id); /// } /// # Ok(()) /// # } - /// # - /// # fn main() { - /// # try_main().unwrap(); - /// # } /// ``` #[inline] pub fn group<C: Into<ChannelId>>(&self, id: C) -> Option<Arc<RwLock<Group>>> { @@ -532,9 +558,16 @@ impl Cache { /// [`Client::on_message`] context: /// /// ```rust,ignore - /// use serenity::CACHE; + /// # extern crate parking_lot; + /// # extern crate serenity; + /// # + /// # use serenity::{cache::Cache, model::prelude::*, prelude::*}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; + /// # + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// let cache = cache.read(); /// - /// let cache = CACHE.read(); /// let member = { /// let channel = match cache.guild_channel(message.channel_id) { /// Some(channel) => channel, @@ -589,12 +622,19 @@ impl Cache { /// name: /// /// ```rust,no_run + /// # extern crate parking_lot; + /// # extern crate serenity; + /// # /// # use std::error::Error; /// # + /// # use serenity::{cache::Cache, model::prelude::*, prelude::*}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; + /// # /// # fn try_main() -> Result<(), Box<Error>> { - /// use serenity::CACHE; - /// - /// let cache = CACHE.read(); + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// # let cache = cache.read(); + /// // assuming the cache has been unlocked /// /// if let Some(channel) = cache.private_channel(7) { /// let channel_reader = channel.read(); @@ -635,20 +675,21 @@ impl Cache { /// Retrieve a role from the cache and print its name: /// /// ```rust,no_run - /// # use std::error::Error; + /// # extern crate parking_lot; + /// # extern crate serenity; /// # - /// # fn try_main() -> Result<(), Box<Error>> { - /// use serenity::CACHE; - /// - /// if let Some(role) = CACHE.read().role(7, 77) { + /// # use serenity::cache::Cache; + /// # use parking_lot::RwLock; + /// # use std::{error::Error, sync::Arc}; + /// # + /// # fn main() -> Result<(), Box<Error>> { + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// // assuming the cache is in scope, e.g. via `Context` + /// if let Some(role) = cache.read().role(7, 77) { /// println!("Role with Id 77 is called {}", role.name); /// } /// # Ok(()) /// # } - /// # - /// # fn main() { - /// # try_main().unwrap(); - /// # } /// ``` #[inline] pub fn role<G, R>(&self, guild_id: G, role_id: R) -> Option<Role> @@ -707,20 +748,14 @@ impl Cache { /// Retrieve a user from the cache and print their name: /// /// ```rust,no_run + /// # use serenity::command; /// # use std::error::Error; /// # - /// # fn try_main() -> Result<(), Box<Error>> { - /// use serenity::CACHE; - /// - /// if let Some(user) = CACHE.read().user(7) { + /// # command!(test(context) { + /// if let Some(user) = context.cache.read().user(7) { /// println!("User with Id 7 is currently named {}", user.read().name); /// } - /// # Ok(()) - /// # } - /// # - /// # fn main() { - /// # try_main().unwrap(); - /// # } + /// # }); /// ``` #[inline] pub fn user<U: Into<UserId>>(&self, user_id: U) -> Option<Arc<RwLock<User>>> { @@ -757,15 +792,6 @@ impl Cache { e.update(self) } - /// Gets the duration it will try for when acquiring a write lock. - /// - /// Refer to the documentation for [`cache_lock_time`] for more information. - /// - /// [`cache_lock_time`]: struct.Settings.html#method.cache_lock_time - pub fn get_try_write_duration(&self) -> Option<Duration> { - self.settings.cache_lock_time - } - pub(crate) fn update_user_entry(&mut self, user: &User) { match self.users.entry(user.id) { Entry::Vacant(e) => { diff --git a/src/cache/settings.rs b/src/cache/settings.rs index 975ddc6..3fabb5a 100644 --- a/src/cache/settings.rs +++ b/src/cache/settings.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - /// Settings for the cache. /// /// # Examples @@ -18,15 +16,6 @@ pub struct Settings { /// /// Defaults to 0. pub max_messages: usize, - - /// The Duration cache updates will try to acquire write-locks for. - /// - /// Defaults to 10 milliseconds. - /// - /// **Note**: - /// If set to `None`, cache updates will acquire write-lock until available, - /// potentially deadlocking. - pub cache_lock_time: Option<Duration>, __nonexhaustive: (), } @@ -34,7 +23,6 @@ impl Default for Settings { fn default() -> Self { Settings { max_messages: usize::default(), - cache_lock_time: Some(Duration::from_millis(10)), __nonexhaustive: (), } } @@ -68,46 +56,4 @@ impl Settings { self } - - /// Sets the duration that the cache will try to aquire a write lock. - /// - /// Refer to [`cache_lock_time`] for more information. - /// - /// **Note**: - /// Should be set before the client gets started, as it can not be - /// changed after the first read of the duration. - /// - /// # Examples - /// - /// Set the time that it will try to aquire a lock. - /// - /// ```rust,no_run - /// use std::time::Duration; - /// use std::env; - /// use serenity::prelude::*; - /// - /// struct Handler; - /// - /// impl EventHandler for Handler {} - /// - /// fn main() { - /// let token = env::var("DISCORD_TOKEN") - /// .expect("Expected a token in the environment"); - /// serenity::CACHE - /// .write().settings_mut() - /// .cache_lock_time(Some(Duration::from_secs(1))); - /// let mut client = Client::new(&token, Handler).unwrap(); - /// - /// if let Err(why) = client.start() { - /// println!("Client error: {:?}", why); - /// } - /// } - /// ``` - /// - /// [`cache_lock_time`]: #structfield.cache_lock_time - pub fn cache_lock_time(&mut self, duration: Option<Duration>) -> &mut Self { - self.cache_lock_time = duration; - - self - } } diff --git a/src/client/bridge/gateway/shard_manager.rs b/src/client/bridge/gateway/shard_manager.rs index 45926ef..579b003 100644 --- a/src/client/bridge/gateway/shard_manager.rs +++ b/src/client/bridge/gateway/shard_manager.rs @@ -1,5 +1,6 @@ use crate::gateway::InterMessage; use crate::internal::prelude::*; +use crate::CacheAndHttp; use parking_lot::Mutex; use std::{ collections::{HashMap, VecDeque}, @@ -50,14 +51,17 @@ use crate::client::bridge::voice::ClientVoiceManager; /// # use serenity::client::bridge::voice::ClientVoiceManager; /// # #[cfg(feature = "voice")] /// # use serenity::model::id::UserId; +/// # #[cfg(feature = "cache")] +/// # use serenity::cache::Cache; /// # /// # #[cfg(feature = "framework")] /// # fn try_main() -> Result<(), Box<Error>> { /// # -/// use parking_lot::Mutex; +/// use parking_lot::{Mutex, RwLock}; /// use serenity::client::bridge::gateway::{ShardManager, ShardManagerOptions}; /// use serenity::client::EventHandler; /// use serenity::http; +/// use serenity::CacheAndHttp; /// use std::sync::Arc; /// use std::env; /// use threadpool::ThreadPool; @@ -76,6 +80,7 @@ use crate::client::bridge::voice::ClientVoiceManager; /// let event_handler = Arc::new(Handler); /// let framework = Arc::new(Mutex::new(None)); /// let threadpool = ThreadPool::with_name("my threadpool".to_owned(), 5); +/// let cache_and_http = Arc::new(CacheAndHttp::default()); /// /// ShardManager::new(ShardManagerOptions { /// data: &data, @@ -92,6 +97,7 @@ use crate::client::bridge::voice::ClientVoiceManager; /// # #[cfg(feature = "voice")] /// # voice_manager: &Arc::new(Mutex::new(ClientVoiceManager::new(0, UserId(0)))), /// ws_url: &gateway_url, +/// cache_and_http: &cache_and_http, /// }); /// # Ok(()) /// # } @@ -151,6 +157,7 @@ impl ShardManager { #[cfg(feature = "voice")] voice_manager: Arc::clone(opt.voice_manager), ws_url: Arc::clone(opt.ws_url), + cache_and_http: Arc::clone(&opt.cache_and_http), }; thread::spawn(move || { @@ -362,4 +369,5 @@ pub struct ShardManagerOptions<'a, H: EventHandler + Send + Sync + 'static> { #[cfg(feature = "voice")] pub voice_manager: &'a Arc<Mutex<ClientVoiceManager>>, pub ws_url: &'a Arc<Mutex<String>>, + pub cache_and_http: &'a Arc<CacheAndHttp>, } diff --git a/src/client/bridge/gateway/shard_queuer.rs b/src/client/bridge/gateway/shard_queuer.rs index 5152249..12df4c8 100644 --- a/src/client/bridge/gateway/shard_queuer.rs +++ b/src/client/bridge/gateway/shard_queuer.rs @@ -1,5 +1,6 @@ use crate::gateway::Shard; use crate::internal::prelude::*; +use crate::CacheAndHttp; use parking_lot::Mutex; use std::{ collections::{HashMap, VecDeque}, @@ -85,6 +86,7 @@ pub struct ShardQueuer<H: EventHandler + Send + Sync + 'static> { pub voice_manager: Arc<Mutex<ClientVoiceManager>>, /// A copy of the URI to use to connect to the gateway. pub ws_url: Arc<Mutex<String>>, + pub cache_and_http: Arc<CacheAndHttp>, } impl<H: EventHandler + Send + Sync + 'static> ShardQueuer<H> { @@ -188,6 +190,7 @@ impl<H: EventHandler + Send + Sync + 'static> ShardQueuer<H> { #[cfg(feature = "voice")] voice_manager: Arc::clone(&self.voice_manager), shard, + cache_and_http: Arc::clone(&self.cache_and_http), }); let runner_info = ShardRunnerInfo { diff --git a/src/client/bridge/gateway/shard_runner.rs b/src/client/bridge/gateway/shard_runner.rs index 5e2a1ff..fe71462 100644 --- a/src/client/bridge/gateway/shard_runner.rs +++ b/src/client/bridge/gateway/shard_runner.rs @@ -2,6 +2,7 @@ use crate::gateway::{InterMessage, ReconnectType, Shard, ShardAction}; use crate::internal::prelude::*; use crate::internal::ws_impl::{ReceiverExt, SenderExt}; use crate::model::event::{Event, GatewayEvent}; +use crate::CacheAndHttp; use parking_lot::Mutex; use serde::Deserialize; use std::{ @@ -32,6 +33,7 @@ use crate::framework::Framework; #[cfg(feature = "voice")] use super::super::voice::ClientVoiceManager; + /// A runner for managing a [`Shard`] and its respective WebSocket client. /// /// [`Shard`]: ../../../gateway/struct.Shard.html @@ -49,6 +51,7 @@ pub struct ShardRunner<H: EventHandler + Send + Sync + 'static> { threadpool: ThreadPool, #[cfg(feature = "voice")] voice_manager: Arc<Mutex<ClientVoiceManager>>, + cache_and_http: Arc<CacheAndHttp>, } impl<H: EventHandler + Send + Sync + 'static> ShardRunner<H> { @@ -68,6 +71,7 @@ impl<H: EventHandler + Send + Sync + 'static> ShardRunner<H> { threadpool: opt.threadpool, #[cfg(feature = "voice")] voice_manager: opt.voice_manager, + cache_and_http: opt.cache_and_http, } } @@ -212,6 +216,7 @@ impl<H: EventHandler + Send + Sync + 'static> ShardRunner<H> { &self.runner_tx, &self.threadpool, self.shard.shard_info()[0], + Arc::clone(&self.cache_and_http), ); } @@ -493,4 +498,6 @@ pub struct ShardRunnerOptions<H: EventHandler + Send + Sync + 'static> { pub threadpool: ThreadPool, #[cfg(feature = "voice")] pub voice_manager: Arc<Mutex<ClientVoiceManager>>, + #[cfg(feature = "cache")] + pub cache_and_http: Arc<CacheAndHttp>, } diff --git a/src/client/context.rs b/src/client/context.rs index 8a08213..ff92f5e 100644 --- a/src/client/context.rs +++ b/src/client/context.rs @@ -1,5 +1,4 @@ use crate::builder::EditProfile; -use crate::CACHE; use crate::client::bridge::gateway::ShardMessenger; use crate::error::Result; use crate::gateway::InterMessage; @@ -14,6 +13,10 @@ use std::sync::{ use typemap::ShareMap; use crate::utils::VecMap; use crate::utils::vecmap_to_json_map; +#[cfg(feature = "cache")] +pub use crate::cache::Cache; +#[cfg(feature = "cache")] +use parking_lot::RwLock; /// The context is a general utility struct provided on event dispatches, which /// helps with dealing with the current "context" of the event dispatch. @@ -41,6 +44,8 @@ pub struct Context { pub shard: ShardMessenger, /// The ID of the shard this context is related to. pub shard_id: u64, + #[cfg(feature = "cache")] + pub cache: Arc<RwLock<Cache>>, } impl Context { @@ -49,11 +54,14 @@ impl Context { data: Arc<Mutex<ShareMap>>, runner_tx: Sender<InterMessage>, shard_id: u64, + cache: Arc<RwLock<Cache>>, ) -> Context { Context { shard: ShardMessenger::new(runner_tx), shard_id, data, + #[cfg(feature = "cache")] + cache, } } @@ -94,7 +102,7 @@ impl Context { feature_cache! { { - let cache = CACHE.read(); + let cache = self.cache.read(); map.insert("username", Value::String(cache.user.name.clone())); diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index 5ed5f3e..0425f29 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -4,14 +4,13 @@ use crate::model::{ event::Event, guild::Member, }; -use std::sync::Arc; +use std::{sync::{Arc, mpsc::Sender}}; use parking_lot::Mutex; use super::{ bridge::gateway::event::ClientEvent, event_handler::EventHandler, Context }; -use std::sync::mpsc::Sender; use threadpool::ThreadPool; use typemap::ShareMap; @@ -20,42 +19,45 @@ use crate::framework::Framework; #[cfg(feature = "cache")] use crate::model::id::GuildId; #[cfg(feature = "cache")] -use std::time::Duration; - +use crate::cache::Cache; #[cfg(feature = "cache")] -use super::CACHE; - +use crate::CacheAndHttp; #[cfg(feature = "cache")] -lazy_static! { - pub static ref CACHE_TRY_WRITE_DURATION: Option<Duration> = - CACHE.read().get_try_write_duration(); -} +use parking_lot::RwLock; macro_rules! update { - ($event:expr) => { + ($cache_and_http:ident, $event:expr) => { { #[cfg(feature = "cache")] { - match *CACHE_TRY_WRITE_DURATION { - Some(duration) => { - if let Some(mut lock) = CACHE.try_write_for(duration) { - lock.update(&mut $event) - } else { - warn!( - "[dispatch] Possible deadlock: couldn't unlock cache to update with event: {:?}", - $event, - ); - None - }}, - None => { - CACHE.write().update(&mut $event) - }, + if let Some(millis_timeout) = $cache_and_http.update_cache_timeout { + + if let Some(mut lock) = $cache_and_http.cache.try_write_for(millis_timeout) { + lock.update(&mut $event) + } else { + warn!("[dispatch] Possible deadlock: Couldn't unlock cache to update with event: {:?}", $event); + + None + } + } else { + $cache_and_http.cache.write().update(&mut $event) } } } - }; + } } +#[cfg(feature = "cache")] +fn context( + data: &Arc<Mutex<ShareMap>>, + runner_tx: &Sender<InterMessage>, + shard_id: u64, + cache: &Arc<RwLock<Cache>>, +) -> Context { + Context::new(Arc::clone(data), runner_tx.clone(), shard_id, cache.clone()) +} + +#[cfg(not(feature = "cache"))] fn context( data: &Arc<Mutex<ShareMap>>, runner_tx: &Sender<InterMessage>, @@ -79,12 +81,17 @@ pub(crate) fn dispatch<H: EventHandler + Send + Sync + 'static>( runner_tx: &Sender<InterMessage>, threadpool: &ThreadPool, shard_id: u64, + cache_and_http: Arc<CacheAndHttp>, ) { match event { DispatchEvent::Model(Event::MessageCreate(mut event)) => { - update!(event); + update!(cache_and_http, event); + #[cfg(feature = "cache")] + let context = context(data, runner_tx, shard_id, &cache_and_http.cache); + #[cfg(not(feature = "cache"))] let context = context(data, runner_tx, shard_id); + dispatch_message( context.clone(), event.message.clone(), @@ -103,6 +110,7 @@ pub(crate) fn dispatch<H: EventHandler + Send + Sync + 'static>( runner_tx, threadpool, shard_id, + cache_and_http, ), } } @@ -116,12 +124,12 @@ pub(crate) fn dispatch<H: EventHandler + Send + Sync + 'static>( runner_tx: &Sender<InterMessage>, threadpool: &ThreadPool, shard_id: u64, + cache_and_http: Arc<CacheAndHttp>, ) { match event { DispatchEvent::Model(Event::MessageCreate(mut event)) => { - update!(event); + update!(cache_and_http, event); - let context = context(data, runner_tx, shard_id); dispatch_message(context, event.message, event_handler, threadpool); }, other => handle_event( @@ -131,6 +139,7 @@ pub(crate) fn dispatch<H: EventHandler + Send + Sync + 'static>( runner_tx, threadpool, shard_id, + cache_and_http, ), } } @@ -162,10 +171,15 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( runner_tx: &Sender<InterMessage>, threadpool: &ThreadPool, shard_id: u64, + cache_and_http: Arc<CacheAndHttp>, ) { + #[cfg(feature = "cache")] + let context = context(data, runner_tx, shard_id, &cache_and_http.cache); + #[cfg(not(feature = "cache"))] + let context = context(data, runner_tx, shard_id); + match event { DispatchEvent::Client(ClientEvent::ShardStageUpdate(event)) => { - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -173,10 +187,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); } DispatchEvent::Model(Event::ChannelCreate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); - + update!(cache_and_http, event); // Discord sends both a MessageCreate and a ChannelCreate upon a new message in a private channel. // This could potentially be annoying to handle when otherwise wanting to normally take care of a new channel. // So therefore, private channels are dispatched to their own handler code. @@ -206,9 +217,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( } }, DispatchEvent::Model(Event::ChannelDelete(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); + update!(cache_and_http, event); match event.channel { Channel::Private(_) | Channel::Group(_) => {}, @@ -229,7 +238,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( } }, DispatchEvent::Model(Event::ChannelPinsUpdate(mut event)) => { - let context = context(data, runner_tx, shard_id); + let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -237,9 +246,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::ChannelRecipientAdd(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); + update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); @@ -252,9 +259,8 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::ChannelRecipientRemove(mut event)) => { - update!(event); + update!(cache_and_http, event); - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -266,14 +272,13 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::ChannelUpdate(mut event)) => { - update!(event); + update!(cache_and_http, event); - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { feature_cache! {{ - let before = CACHE.read().channel(event.channel.id()); + let before = cache_and_http.cache.read().channel(event.channel.id()); event_handler.channel_update(context, before, event.channel); } else { @@ -282,7 +287,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildBanAdd(mut event)) => { - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -290,7 +294,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildBanRemove(mut event)) => { - let context = context(data, runner_tx, shard_id); + let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -300,21 +304,20 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( DispatchEvent::Model(Event::GuildCreate(mut event)) => { #[cfg(feature = "cache")] let _is_new = { - let cache = CACHE.read(); + let cache = cache_and_http.cache.read(); !cache.unavailable_guilds.contains(&event.guild.id) }; - update!(event); + update!(cache_and_http, event); #[cfg(feature = "cache")] { - let cache = CACHE.read(); - - if cache.unavailable_guilds.is_empty() { - let context = context(data, runner_tx, shard_id); + let locked_cache = cache_and_http.cache.read(); + let context = context.clone(); - let guild_amount = cache + if locked_cache.unavailable_guilds.is_empty() { + let guild_amount = locked_cache .guilds .iter() .map(|(&id, _)| id) @@ -327,7 +330,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( } } - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -339,8 +341,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildDelete(mut event)) => { - let _full = update!(event); - let context = context(data, runner_tx, shard_id); + let _full = update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -352,9 +353,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildEmojisUpdate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); + update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -362,7 +361,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildIntegrationsUpdate(mut event)) => { - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -370,9 +368,8 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildMemberAdd(mut event)) => { - update!(event); + update!(cache_and_http, event); - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -380,8 +377,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildMemberRemove(mut event)) => { - let _member = update!(event); - let context = context(data, runner_tx, shard_id); + let _member = update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -393,15 +389,13 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildMemberUpdate(mut event)) => { - let _before = update!(event); - + let _before = update!(cache_and_http, event); let _after: Option<Member> = feature_cache! {{ - CACHE.read().member(event.guild_id, event.user.id) + cache_and_http.cache.read().member(event.guild_id, event.user.id) } else { None }}; - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -415,9 +409,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildMembersChunk(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); + update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -425,9 +417,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildRoleCreate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); + update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -435,8 +425,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildRoleDelete(mut event)) => { - let _role = update!(event); - let context = context(data, runner_tx, shard_id); + let _role = update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -448,8 +437,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildRoleUpdate(mut event)) => { - let _before = update!(event); - let context = context(data, runner_tx, shard_id); + let _before = update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -461,9 +449,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildUnavailable(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); + update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -471,14 +457,12 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::GuildUpdate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); + update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { feature_cache! {{ - let before = CACHE.read() + let before = cache_and_http.cache.read() .guilds .get(&event.guild.id) .cloned(); @@ -492,7 +476,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( // Already handled by the framework check macro DispatchEvent::Model(Event::MessageCreate(_)) => {}, DispatchEvent::Model(Event::MessageDeleteBulk(mut event)) => { - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -500,7 +483,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::MessageDelete(mut event)) => { - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -508,9 +490,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::MessageUpdate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); + update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -518,9 +498,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::PresencesReplace(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); + update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -528,9 +506,8 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::PresenceUpdate(mut event)) => { - update!(event); + update!(cache_and_http, event); - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -538,7 +515,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::ReactionAdd(mut event)) => { - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -546,7 +522,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::ReactionRemove(mut event)) => { - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -554,7 +529,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::ReactionRemoveAll(mut event)) => { - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -562,9 +536,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::Ready(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); + update!(cache_and_http, event); let event_handler = Arc::clone(&event_handler); threadpool.execute(move || { @@ -572,12 +544,9 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::Resumed(mut event)) => { - let context = context(data, runner_tx, shard_id); - event_handler.resume(context, event); }, DispatchEvent::Model(Event::TypingStart(mut event)) => { - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -585,7 +554,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::Unknown(mut event)) => { - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -593,8 +561,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::UserUpdate(mut event)) => { - let _before = update!(event); - let context = context(data, runner_tx, shard_id); + let _before = update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -606,7 +573,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::VoiceServerUpdate(mut event)) => { - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -614,9 +580,7 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::VoiceStateUpdate(mut event)) => { - update!(event); - - let context = context(data, runner_tx, shard_id); + update!(cache_and_http, event); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { @@ -624,7 +588,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( }); }, DispatchEvent::Model(Event::WebhookUpdate(mut event)) => { - let context = context(data, runner_tx, shard_id); let event_handler = Arc::clone(event_handler); threadpool.execute(move || { diff --git a/src/client/mod.rs b/src/client/mod.rs index 4bd9d11..bbedf89 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -32,18 +32,22 @@ pub use self::{ event_handler::EventHandler }; +pub use crate::CacheAndHttp; + // Note: the following re-exports are here for backwards compatibility pub use crate::gateway; pub use crate::http as rest; #[cfg(feature = "cache")] -pub use crate::CACHE; +pub use crate::cache::Cache; +#[cfg(feature = "cache")] +use parking_lot::RwLock; use crate::http; use crate::internal::prelude::*; use parking_lot::Mutex; use self::bridge::gateway::{ShardManager, ShardManagerMonitor, ShardManagerOptions}; -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; use threadpool::ThreadPool; use typemap::ShareMap; @@ -295,6 +299,7 @@ pub struct Client { /// This is wrapped in an `Arc<Mutex<T>>` so all shards will have an updated /// value available. pub ws_uri: Arc<Mutex<String>>, + pub cache_and_http: Arc<CacheAndHttp>, } impl Client { @@ -354,6 +359,115 @@ impl Client { UserId(0), ))); + let cache_and_http = Arc::new(CacheAndHttp { + #[cfg(feature = "cache")] + cache: Arc::new(RwLock::new(Cache::default())), + #[cfg(feature = "cache")] + update_cache_timeout: None, + __nonexhaustive: (), + }); + + let (shard_manager, shard_manager_worker) = { + ShardManager::new(ShardManagerOptions { + data: &data, + event_handler: &event_handler, + #[cfg(feature = "framework")] + framework: &framework, + shard_index: 0, + shard_init: 0, + shard_total: 0, + threadpool: threadpool.clone(), + token: &locked, + #[cfg(feature = "voice")] + voice_manager: &voice_manager, + ws_url: &url, + cache_and_http: &cache_and_http, + }) + }; + + Ok(Client { + token: locked, + ws_uri: url, + #[cfg(feature = "framework")] + framework, + data, + shard_manager, + shard_manager_worker, + threadpool, + #[cfg(feature = "voice")] + voice_manager, + cache_and_http, + }) + } + + /// Creates a Client for a bot user and sets a cache update timeout. + /// If set to some duration, updating the cache will try to claim a + /// write-lock for given duration and skip received event but also + /// issue a deadlock-warning upon failure. + /// If `duration` is set to `None`, updating the cache will try to claim + /// a write-lock until success and potentially deadlock. + /// + /// Discord has a requirement of prefixing bot tokens with `"Bot "`, which + /// this function will automatically do for you if not already included. + /// + /// # Examples + /// + /// Create a Client, using a token from an environment variable: + /// + /// ```rust,no_run + /// # use serenity::prelude::EventHandler; + /// struct Handler; + /// + /// impl EventHandler for Handler {} + /// # use std::error::Error; + /// # + /// # fn try_main() -> Result<(), Box<Error>> { + /// use serenity::Client; + /// use std::env; + /// + /// let token = env::var("DISCORD_TOKEN")?; + /// let client = Client::new_with_cache_update_timeout(&token, Handler, None)?; + /// # Ok(()) + /// # } + /// # + /// # fn main() { + /// # try_main().unwrap(); + /// # } + /// ``` + #[cfg(feature = "cache")] + pub fn new_with_cache_update_timeout<H>(token: &str, handler: H, duration: Option<Duration>) -> Result<Self> + where H: EventHandler + Send + Sync + 'static { + let token = token.trim(); + + let token = if token.starts_with("Bot ") { + token.to_string() + } else { + format!("Bot {}", token) + }; + + http::set_token(&token); + let locked = Arc::new(Mutex::new(token)); + + let name = "serenity client".to_owned(); + let threadpool = ThreadPool::with_name(name, 5); + let url = Arc::new(Mutex::new(http::get_gateway()?.url)); + let data = Arc::new(Mutex::new(ShareMap::custom())); + let event_handler = Arc::new(handler); + + #[cfg(feature = "framework")] + let framework = Arc::new(Mutex::new(None)); + #[cfg(feature = "voice")] + let voice_manager = Arc::new(Mutex::new(ClientVoiceManager::new( + 0, + UserId(0), + ))); + + let cache_and_http = Arc::new(CacheAndHttp { + cache: Arc::new(RwLock::new(Cache::default())), + update_cache_timeout: duration, + __nonexhaustive: (), + }); + let (shard_manager, shard_manager_worker) = { ShardManager::new(ShardManagerOptions { data: &data, @@ -368,6 +482,7 @@ impl Client { #[cfg(feature = "voice")] voice_manager: &voice_manager, ws_url: &url, + cache_and_http: &cache_and_http, }) }; @@ -382,6 +497,7 @@ impl Client { threadpool, #[cfg(feature = "voice")] voice_manager, + cache_and_http, }) } diff --git a/src/framework/standard/command.rs b/src/framework/standard/command.rs index 964f788..d4cbcae 100644 --- a/src/framework/standard/command.rs +++ b/src/framework/standard/command.rs @@ -373,7 +373,7 @@ pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Opti #[cfg(feature = "cache")] { - let private = match msg.channel() { + let private = match msg.channel(&ctx.cache) { Some(Channel::Private(_)) => true, _ => false, }; diff --git a/src/framework/standard/help_commands.rs b/src/framework/standard/help_commands.rs index bde965b..a5aa9b1 100644 --- a/src/framework/standard/help_commands.rs +++ b/src/framework/standard/help_commands.rs @@ -26,6 +26,10 @@ use crate::client::Context; #[cfg(feature = "cache")] use crate::framework::standard::{has_correct_roles, has_correct_permissions}; +#[cfg(feature = "cache")] +use crate::cache::Cache; +#[cfg(feature = "cache")] +use parking_lot::RwLock; use crate::model::{ channel::Message, id::ChannelId, @@ -237,18 +241,18 @@ fn remove_aliases(cmds: &HashMap<String, CommandOrAlias>) -> HashMap<&String, &I /// Checks whether a user is member of required roles /// and given the required permissions. #[cfg(feature = "cache")] -pub fn has_all_requirements(cmd: &Arc<CommandOptions>, msg: &Message) -> bool { - if let Some(guild) = msg.guild() { +pub fn has_all_requirements(cache: &Arc<RwLock<Cache>>, cmd: &Arc<CommandOptions>, msg: &Message) -> bool { + if let Some(guild) = msg.guild(&cache) { let guild = guild.read(); if let Some(member) = guild.members.get(&msg.author.id) { - if let Ok(permissions) = member.permissions() { + if let Ok(permissions) = member.permissions(&cache) { return if cmd.allowed_roles.is_empty() { - permissions.administrator() || has_correct_permissions(cmd, msg) + permissions.administrator() || has_correct_permissions(&cache, cmd, msg) } else { - permissions.administrator() || (has_correct_roles(cmd, &guild, member) && has_correct_permissions(cmd, msg)) + permissions.administrator() || (has_correct_roles(cmd, &guild, member) && has_correct_permissions(&cache, cmd, msg)) } } } @@ -262,20 +266,22 @@ pub fn has_all_requirements(cmd: &Arc<CommandOptions>, msg: &Message) -> bool { /// **Note**: A command is visible when it is either normally displayed or /// strikethrough upon requested help by a user. #[cfg(feature = "cache")] -pub fn is_command_visible(command_options: &Arc<CommandOptions>, msg: &Message, help_options: &HelpOptions) -> bool { +pub fn is_command_visible(cache: &Arc<RwLock<Cache>>, command_options: &Arc<CommandOptions>, msg: &Message, + help_options: &HelpOptions) -> bool { + if !command_options.dm_only && !command_options.guild_only || command_options.dm_only && msg.is_private() || command_options.guild_only && !msg.is_private() { - if let Some(guild) = msg.guild() { + if let Some(guild) = msg.guild(&cache) { let guild = guild.read(); if let Some(member) = guild.members.get(&msg.author.id) { if command_options.help_available { - return if has_correct_permissions(command_options, msg) { + return if has_correct_permissions(&cache, command_options, msg) { if has_correct_roles(command_options, &guild, &member) { true @@ -288,7 +294,7 @@ pub fn is_command_visible(command_options: &Arc<CommandOptions>, msg: &Message, } } } else if command_options.help_available { - return if has_correct_permissions(command_options, msg) { + return if has_correct_permissions(&cache, command_options, msg) { true } else { help_options.lacking_permissions != HelpBehaviour::Hide @@ -305,6 +311,7 @@ pub fn is_command_visible(command_options: &Arc<CommandOptions>, msg: &Message, /// returns similar commands. #[cfg(feature = "cache")] fn fetch_single_command<'a, H: BuildHasher>( + cache: &Arc<RwLock<Cache>>, groups: &'a HashMap<String, Arc<CommandGroup>, H>, name: &str, help_options: &'a HelpOptions, @@ -329,7 +336,7 @@ fn fetch_single_command<'a, H: BuildHasher>( match *command { CommandOrAlias::Command(ref cmd) => { - if is_command_visible(&cmd.options(), msg, help_options) { + if is_command_visible(&cache, &cmd.options(), msg, help_options) { found = Some((command_name, cmd)); } else { break; @@ -340,7 +347,7 @@ fn fetch_single_command<'a, H: BuildHasher>( match *actual_command { CommandOrAlias::Command(ref cmd) => { - if is_command_visible(&cmd.options(), msg, help_options) { + if is_command_visible(&cache, &cmd.options(), msg, help_options) { found = Some((name, cmd)); } else { break; @@ -374,7 +381,7 @@ fn fetch_single_command<'a, H: BuildHasher>( let levenshtein_distance = levenshtein_distance(&command_name, &name); if levenshtein_distance <= help_options.max_levenshtein_distance - && is_command_visible(&cmd.options(), &msg, &help_options) { + && is_command_visible(&cache, &cmd.options(), &msg, &help_options) { similar_commands.push(SuggestedCommandName { name: command_name, @@ -426,6 +433,7 @@ fn fetch_single_command<'a, H: BuildHasher>( /// Tries to extract a single command matching searched command name. #[cfg(feature = "cache")] fn fetch_all_eligible_commands_in_group<'a>( + cache: &Arc<RwLock<Cache>>, commands: &HashMap<&String, &InternalCommand>, command_names: &[&&String], help_options: &'a HelpOptions, @@ -442,9 +450,9 @@ fn fetch_all_eligible_commands_in_group<'a>( || cmd.dm_only && msg.is_private() || cmd.guild_only && !msg.is_private() { - if cmd.help_available && has_correct_permissions(&cmd, msg) { + if cmd.help_available && has_correct_permissions(&cache, &cmd, msg) { - if let Some(guild) = msg.guild() { + if let Some(guild) = msg.guild(&cache) { let guild = guild.read(); if let Some(member) = guild.members.get(&msg.author.id) { @@ -475,6 +483,7 @@ fn fetch_all_eligible_commands_in_group<'a>( /// Fetch groups with their commands. #[cfg(feature = "cache")] fn create_command_group_commands_pair_from_groups<'a, H: BuildHasher>( + cache: &Arc<RwLock<Cache>>, groups: &'a HashMap<String, Arc<CommandGroup>, H>, group_names: &[&'a String], msg: &Message, @@ -486,6 +495,7 @@ fn create_command_group_commands_pair_from_groups<'a, H: BuildHasher>( let group = &groups[&**group_name]; let group_with_cmds = create_single_group( + &cache, group, group_name, &msg, @@ -503,6 +513,7 @@ fn create_command_group_commands_pair_from_groups<'a, H: BuildHasher>( /// Fetches a single group with its commands. #[cfg(feature = "cache")] fn create_single_group<'a>( + cache: &Arc<RwLock<Cache>>, group: &CommandGroup, group_name: &'a str, msg: &Message, @@ -513,6 +524,7 @@ let commands = remove_aliases(&group.commands); command_names.sort(); let mut group_with_cmds = fetch_all_eligible_commands_in_group( + &cache, &commands, &command_names, &help_options, @@ -533,6 +545,7 @@ let commands = remove_aliases(&group.commands); /// shall be picked and in what textual format. #[cfg(feature = "cache")] pub fn create_customised_help_data<'a, H: BuildHasher>( + cache: &Arc<RwLock<Cache>>, groups: &'a HashMap<String, Arc<CommandGroup>, H>, args: &'a Args, help_options: &'a HelpOptions, @@ -541,7 +554,7 @@ pub fn create_customised_help_data<'a, H: BuildHasher>( if !args.is_empty() { let name = args.full(); - return match fetch_single_command(&groups, &name, &help_options, &msg) { + return match fetch_single_command(&cache, &groups, &name, &help_options, &msg) { Ok(single_command) => single_command, Err(suggestions) => { let searched_named_lowercase = name.to_lowercase(); @@ -554,6 +567,7 @@ pub fn create_customised_help_data<'a, H: BuildHasher>( *prefix == searched_named_lowercase)) { let mut single_group = create_single_group( + &cache, &group, &key, &msg, @@ -602,7 +616,7 @@ pub fn create_customised_help_data<'a, H: BuildHasher>( group_names.sort(); let listed_groups = - create_command_group_commands_pair_from_groups(&groups, &group_names, &msg, &help_options); + create_command_group_commands_pair_from_groups(&cache, &groups, &group_names, &msg, &help_options); return if listed_groups.is_empty() { CustomisedHelpData::NoCommandFound { @@ -745,13 +759,13 @@ fn send_error_embed(channel_id: ChannelId, input: &str, colour: Colour) -> Resul /// ``` #[cfg(feature = "cache")] pub fn with_embeds<H: BuildHasher>( - _: &mut Context, + context: &mut Context, msg: &Message, help_options: &HelpOptions, groups: HashMap<String, Arc<CommandGroup>, H>, args: &Args ) -> Result<(), CommandError> { - let formatted_help = create_customised_help_data(&groups, args, help_options, msg); + let formatted_help = create_customised_help_data(&context.cache, &groups, args, help_options, msg); if let Err(why) = match formatted_help { CustomisedHelpData::SuggestedCommands { ref help_description, ref suggestions } => @@ -858,13 +872,13 @@ fn single_command_to_plain_string(help_options: &HelpOptions, command: &Command) /// ``` #[cfg(feature = "cache")] pub fn plain<H: BuildHasher>( - _: &mut Context, + context: &mut Context, msg: &Message, help_options: &HelpOptions, groups: HashMap<String, Arc<CommandGroup>, H>, args: &Args ) -> Result<(), CommandError> { - let formatted_help = create_customised_help_data(&groups, args, help_options, msg); + let formatted_help = create_customised_help_data(&context.cache, &groups, args, help_options, msg); let result = match formatted_help { CustomisedHelpData::SuggestedCommands { ref help_description, ref suggestions } => diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index 7231612..c9cf600 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -49,9 +49,11 @@ use super::Framework; use threadpool::ThreadPool; #[cfg(feature = "cache")] -use crate::client::CACHE; +use crate::cache::Cache; #[cfg(feature = "cache")] use crate::model::channel::Channel; +#[cfg(feature = "cache")] +use parking_lot::RwLock; /// A convenience macro for generating a struct fulfilling the [`Command`][command trait] trait. /// @@ -472,14 +474,14 @@ impl StandardFramework { } #[cfg(feature = "cache")] - fn is_blocked_guild(&self, message: &Message) -> bool { - if let Some(Channel::Guild(channel)) = CACHE.read().channel(message.channel_id) { + fn is_blocked_guild(&self, cache: &Arc<RwLock<Cache>>, message: &Message) -> bool { + if let Some(Channel::Guild(channel)) = cache.read().channel(message.channel_id) { let guild_id = channel.with(|g| g.guild_id); if self.configuration.blocked_guilds.contains(&guild_id) { return true; } - if let Some(guild) = guild_id.to_guild_cached() { + if let Some(guild) = guild_id.to_guild_cached(&cache) { return self.configuration .blocked_users .contains(&guild.with(|g| g.owner_id)); @@ -560,7 +562,7 @@ impl StandardFramework { #[cfg(feature = "cache")] { - if self.is_blocked_guild(message) { + if self.is_blocked_guild(&context.cache, message) { return Some(DispatchError::BlockedGuild); } @@ -568,7 +570,7 @@ impl StandardFramework { return Some(DispatchError::BlockedChannel); } - if !has_correct_permissions(command, message) { + if !has_correct_permissions(&context.cache, command, message) { return Some(DispatchError::LackOfPermissions( command.required_permissions, )); @@ -598,11 +600,11 @@ impl StandardFramework { #[cfg(feature = "cache")] { if !command.allowed_roles.is_empty() { - if let Some(guild) = message.guild() { + if let Some(guild) = message.guild(&context.cache) { let guild = guild.read(); if let Some(member) = guild.members.get(&message.author.id) { - if let Ok(permissions) = member.permissions() { + if let Ok(permissions) = member.permissions(&context.cache) { if !permissions.administrator() && !has_correct_roles(command, &guild, member) { @@ -880,8 +882,8 @@ impl StandardFramework { /// use serenity::framework::StandardFramework; /// /// client.with_framework(StandardFramework::new() - /// .before(|_, msg, cmd_name| { - /// if let Ok(channel) = msg.channel_id.to_channel() { + /// .before(|ctx, msg, cmd_name| { + /// if let Ok(channel) = msg.channel_id.to_channel(&ctx) { /// // Don't run unless in nsfw channel /// if !channel.is_nsfw() { /// return false; @@ -1318,11 +1320,11 @@ impl Framework for StandardFramework { } #[cfg(feature = "cache")] -pub fn has_correct_permissions(command: &Arc<CommandOptions>, message: &Message) -> bool { +pub fn has_correct_permissions(cache: &Arc<RwLock<Cache>>, command: &Arc<CommandOptions>, message: &Message) -> bool { if command.required_permissions.is_empty() { true } else { - if let Some(guild) = message.guild() { + if let Some(guild) = message.guild(&cache) { let perms = guild .with(|g| g.permissions_in(message.channel_id, message.author.id)); @@ -58,8 +58,8 @@ //! } //! } //! -//! command!(ping(_context, message) { -//! let _ = message.reply("Pong!"); +//! command!(ping(context, message) { +//! let _ = message.reply(&context, "Pong!"); //! }); //! # //! # } @@ -192,44 +192,14 @@ pub use crate::client::Client; use crate::cache::Cache; #[cfg(feature = "cache")] use parking_lot::RwLock; - #[cfg(feature = "cache")] -lazy_static! { - /// A mutable and lazily-initialized static binding. It can be accessed - /// across any function and in any context. - /// - /// This [`Cache`] instance is updated for every event received, so you do - /// not need to maintain your own cache. - /// - /// See the [cache module documentation] for more details. - /// - /// The Cache itself is wrapped within an `RwLock`, which allows for - /// multiple readers or at most one writer at a time across threads. This - /// means that you may have multiple commands reading from the Cache - /// concurrently. - /// - /// # Examples - /// - /// Retrieve the [current user][`CurrentUser`]'s Id, by opening a Read - /// guard: - /// - /// ```rust,ignore - /// use serenity::CACHE; - /// - /// println!("{}", CACHE.read().user.id); - /// ``` - /// - /// Update the cache's settings to enable caching of channels' messages: - /// - /// ```rust - /// use serenity::CACHE; - /// - /// // Cache up to the 10 most recent messages per channel. - /// CACHE.write().settings_mut().max_messages(10); - /// ``` - /// - /// [`CurrentUser`]: model/user/struct.CurrentUser.html - /// [`Cache`]: cache/struct.Cache.html - /// [cache module documentation]: cache/index.html - pub static ref CACHE: RwLock<Cache> = RwLock::new(Cache::default()); +use std::{time::Duration, sync::Arc}; + +#[derive(Default)] +pub struct CacheAndHttp { + #[cfg(feature = "cache")] + pub cache: Arc<RwLock<Cache>>, + #[cfg(feature = "cache")] + pub update_cache_timeout: Option<Duration>, + __nonexhaustive: (), } diff --git a/src/model/channel/channel_category.rs b/src/model/channel/channel_category.rs index f38b0f6..50720f9 100644 --- a/src/model/channel/channel_category.rs +++ b/src/model/channel/channel_category.rs @@ -1,5 +1,7 @@ -use crate::model::prelude::*; +use crate::{model::prelude::*}; +#[cfg(feature = "client")] +use crate::client::Context; #[cfg(all(feature = "builder", feature = "model"))] use crate::builder::EditChannel; #[cfg(all(feature = "builder", feature = "model"))] @@ -55,14 +57,18 @@ impl ChannelCategory { self.id.delete_permission(permission_type) } - /// Deletes this category. + + /// Deletes this category if required permissions are met. + /// + /// **Note**: If the `cache`-feature is enabled permissions will be checked and upon + /// owning the required permissions the HTTP-request will be issued. #[inline] - pub fn delete(&self) -> Result<()> { + pub fn delete(&self, context: &Context) -> Result<()> { #[cfg(feature = "cache")] { let req = Permissions::MANAGE_CHANNELS; - if !utils::user_has_perms(self.id, req)? { + if !utils::user_has_perms(&context.cache, self.id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -82,13 +88,13 @@ impl ChannelCategory { /// category.edit(|c| c.name("test").bitrate(86400)); /// ``` #[cfg(all(feature = "builder", feature = "model", feature = "utils"))] - pub fn edit<F>(&mut self, f: F) -> Result<()> + pub fn edit<F>(&mut self, context: &Context, f: F) -> Result<()> where F: FnOnce(EditChannel) -> EditChannel { #[cfg(feature = "cache")] { let req = Permissions::MANAGE_CHANNELS; - if !utils::user_has_perms(self.id, req)? { + if !utils::user_has_perms(&context.cache, self.id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } diff --git a/src/model/channel/channel_id.rs b/src/model/channel/channel_id.rs index 782ea5b..3f452fa 100644 --- a/src/model/channel/channel_id.rs +++ b/src/model/channel/channel_id.rs @@ -1,6 +1,7 @@ -use crate::internal::RwLockExt; -use crate::model::prelude::*; +use crate::{internal::RwLockExt, model::prelude::*}; +#[cfg(feature = "client")] +use crate::client::Context; #[cfg(feature = "model")] use std::borrow::Cow; #[cfg(feature = "model")] @@ -13,9 +14,9 @@ use crate::builder::{ GetMessages }; #[cfg(all(feature = "cache", feature = "model"))] -use crate::CACHE; +use crate::cache::Cache; #[cfg(all(feature = "cache", feature = "model"))] -use crate::Cache; +use parking_lot::RwLock; #[cfg(feature = "model")] use crate::http::{self, AttachmentType}; #[cfg(feature = "model")] @@ -293,8 +294,8 @@ impl ChannelId { /// [`Channel`]: ../channel/enum.Channel.html #[cfg(feature = "cache")] #[inline] - pub fn to_channel_cached(self) -> Option<Channel> { - self._to_channel_cached(&CACHE) + pub fn to_channel_cached(self, cache: &Arc<RwLock<Cache>>) -> Option<Channel> { + self._to_channel_cached(&cache) } /// To allow testing pass their own cache instead of using the globale one. @@ -307,15 +308,15 @@ impl ChannelId { /// First attempts to find a [`Channel`] by its Id in the cache, /// upon failure requests it via the REST API. /// - /// **Note**: If the cache is not enabled, - /// REST API will be used only. + /// **Note**: If the `cache`-feature is enabled permissions will be checked and upon + /// owning the required permissions the HTTP-request will be issued. /// /// [`Channel`]: ../channel/enum.Channel.html #[inline] - pub fn to_channel(self) -> Result<Channel> { + pub fn to_channel(self, context: &Context) -> Result<Channel> { #[cfg(feature = "cache")] { - if let Some(channel) = CACHE.read().channel(self) { + if let Some(channel) = context.cache.read().channel(self) { return Ok(channel); } } @@ -385,11 +386,11 @@ impl ChannelId { /// Returns the name of whatever channel this id holds. #[cfg(feature = "model")] - pub fn name(&self) -> Option<String> { + pub fn name(&self, context: &Context) -> Option<String> { use self::Channel::*; let finding = feature_cache! {{ - Some(self.to_channel_cached()) + Some(self.to_channel_cached(&context.cache)) } else { None }}; diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs index b976b0b..0c476e6 100644 --- a/src/model/channel/guild_channel.rs +++ b/src/model/channel/guild_channel.rs @@ -1,8 +1,14 @@ use chrono::{DateTime, FixedOffset}; -use crate::model::prelude::*; +use crate::{model::prelude::*}; +#[cfg(feature = "client")] +use crate::client::Context; #[cfg(all(feature = "cache", feature = "model"))] -use crate::CACHE; +use crate::cache::Cache; +#[cfg(feature = "cache")] +use parking_lot::RwLock; +#[cfg(feature = "cache")] +use std::sync::Arc; #[cfg(feature = "model")] use crate::builder::{ CreateInvite, @@ -119,16 +125,17 @@ impl GuildChannel { /// let invite = channel.create_invite(|i| i.max_uses(5)); /// ``` #[cfg(feature = "utils")] - pub fn create_invite<F>(&self, f: F) -> Result<RichInvite> + pub fn create_invite<F>(&self, context: &Context, f: F) -> Result<RichInvite> where F: FnOnce(&mut CreateInvite) -> &mut CreateInvite { #[cfg(feature = "cache")] { let req = Permissions::CREATE_INVITE; - if !utils::user_has_perms(self.id, req)? { + if !utils::user_has_perms(&context.cache, self.id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } + let mut invite = CreateInvite::default(); f(&mut invite); @@ -153,10 +160,15 @@ impl GuildChannel { /// permissions: /// /// ```rust,no_run - /// # use serenity::model::id::{ChannelId, UserId}; - /// # use std::error::Error; + /// # extern crate parking_lot; + /// # extern crate serenity; /// # - /// # fn try_main() -> Result<(), Box<Error>> { + /// # use serenity::{cache::Cache, model::id::{ChannelId, UserId}}; + /// # use parking_lot::RwLock; + /// # use std::{error::Error, sync::Arc}; + /// # + /// # fn main() -> Result<(), Box<Error>> { + /// # let cache = Arc::new(RwLock::new(Cache::default())); /// # let (channel_id, user_id) = (ChannelId(0), UserId(0)); /// # /// use serenity::model::channel::{ @@ -164,8 +176,6 @@ impl GuildChannel { /// PermissionOverwriteType, /// }; /// use serenity::model::{ModelError, Permissions}; - /// use serenity::CACHE; - /// /// let allow = Permissions::SEND_MESSAGES; /// let deny = Permissions::SEND_TTS_MESSAGES | Permissions::ATTACH_FILES; /// let overwrite = PermissionOverwrite { @@ -173,18 +183,14 @@ impl GuildChannel { /// deny: deny, /// kind: PermissionOverwriteType::Member(user_id), /// }; - /// - /// let cache = CACHE.read(); + /// # let cache = cache.read(); + /// // assuming the cache has been unlocked /// let channel = cache /// .guild_channel(channel_id) /// .ok_or(ModelError::ItemMissing)?; /// /// channel.read().create_permission(&overwrite)?; - /// # Ok(()) - /// # } - /// # - /// # fn main() { - /// # try_main().unwrap(); + /// # Ok(()) /// # } /// ``` /// @@ -194,18 +200,22 @@ impl GuildChannel { /// permissions: /// /// ```rust,no_run - /// # use serenity::model::id::{ChannelId, UserId}; - /// # use std::error::Error; + /// # extern crate parking_lot; + /// # extern crate serenity; + /// + /// # use serenity::{cache::Cache, model::id::{ChannelId, UserId}}; + /// # use parking_lot::RwLock; + /// # use std::{error::Error, sync::Arc}; /// # /// # fn try_main() -> Result<(), Box<Error>> { - /// # let (channel_id, user_id) = (ChannelId(0), UserId(0)); + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// # let (channel_id, user_id) = (ChannelId(0), UserId(0)); /// # /// use serenity::model::channel::{ /// PermissionOverwrite, /// PermissionOverwriteType, /// }; /// use serenity::model::{ModelError, Permissions}; - /// use serenity::CACHE; /// /// let allow = Permissions::SEND_MESSAGES; /// let deny = Permissions::SEND_TTS_MESSAGES | Permissions::ATTACH_FILES; @@ -215,7 +225,7 @@ impl GuildChannel { /// kind: PermissionOverwriteType::Member(user_id), /// }; /// - /// let cache = CACHE.read(); + /// let cache = cache.read(); /// let channel = cache /// .guild_channel(channel_id) /// .ok_or(ModelError::ItemMissing)?; @@ -247,12 +257,15 @@ impl GuildChannel { } /// Deletes this channel, returning the channel on a successful deletion. - pub fn delete(&self) -> Result<Channel> { + /// + /// **Note**: If the `cache`-feature is enabled permissions will be checked and upon + /// owning the required permissions the HTTP-request will be issued. + pub fn delete(&self, context: &Context) -> Result<Channel> { #[cfg(feature = "cache")] { let req = Permissions::MANAGE_CHANNELS; - if !utils::user_has_perms(self.id, req)? { + if !utils::user_has_perms(&context.cache, self.id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -322,13 +335,13 @@ impl GuildChannel { /// channel.edit(|c| c.name("test").bitrate(86400)); /// ``` #[cfg(feature = "utils")] - pub fn edit<F>(&mut self, f: F) -> Result<()> + pub fn edit<F>(&mut self, context: &Context, f: F) -> Result<()> where F: FnOnce(EditChannel) -> EditChannel { #[cfg(feature = "cache")] { let req = Permissions::MANAGE_CHANNELS; - if !utils::user_has_perms(self.id, req)? { + if !utils::user_has_perms(&context.cache, self.id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -380,7 +393,7 @@ impl GuildChannel { /// **Note**: Right now this performs a clone of the guild. This will be /// optimized in the future. #[cfg(feature = "cache")] - pub fn guild(&self) -> Option<Arc<RwLock<Guild>>> { CACHE.read().guild(self.guild_id) } + pub fn guild(&self, cache: &Arc<RwLock<Cache>>) -> Option<Arc<RwLock<Guild>>> { cache.read().guild(self.guild_id) } /// Gets all of the channel's invites. /// @@ -443,16 +456,14 @@ impl GuildChannel { /// use serenity::model::prelude::*; /// struct Handler; /// - /// use serenity::CACHE; - /// /// impl EventHandler for Handler { - /// fn message(&self, _: Context, msg: Message) { - /// let channel = match CACHE.read().guild_channel(msg.channel_id) { + /// fn message(&self, context: Context, msg: Message) { + /// let channel = match context.cache.read().guild_channel(msg.channel_id) { /// Some(channel) => channel, /// None => return, /// }; /// - /// let permissions = channel.read().permissions_for(&msg.author).unwrap(); + /// let permissions = channel.read().permissions_for(&context.cache, &msg.author).unwrap(); /// /// println!("The user's permissions: {:?}", permissions); /// } @@ -467,7 +478,6 @@ impl GuildChannel { /// for demonstrative purposes): /// /// ```rust,no_run - /// use serenity::CACHE; /// use serenity::prelude::*; /// use serenity::model::prelude::*; /// use std::fs::File; @@ -475,15 +485,15 @@ impl GuildChannel { /// struct Handler; /// /// impl EventHandler for Handler { - /// fn message(&self, _: Context, mut msg: Message) { - /// let channel = match CACHE.read().guild_channel(msg.channel_id) { + /// fn message(&self, context: Context, mut msg: Message) { + /// let channel = match context.cache.read().guild_channel(msg.channel_id) { /// Some(channel) => channel, /// None => return, /// }; /// - /// let current_user_id = CACHE.read().user.id; + /// let current_user_id = context.cache.read().user.id; /// let permissions = - /// channel.read().permissions_for(current_user_id).unwrap(); + /// channel.read().permissions_for(&context.cache, current_user_id).unwrap(); /// /// if !permissions.contains(Permissions::ATTACH_FILES | Permissions::SEND_MESSAGES) { /// return; @@ -526,13 +536,13 @@ impl GuildChannel { /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES #[cfg(feature = "cache")] #[inline] - pub fn permissions_for<U: Into<UserId>>(&self, user_id: U) -> Result<Permissions> { - self._permissions_for(user_id.into()) + pub fn permissions_for<U: Into<UserId>>(&self, cache: &Arc<RwLock<Cache>>, user_id: U) -> Result<Permissions> { + self._permissions_for(&cache, user_id.into()) } #[cfg(feature = "cache")] - fn _permissions_for(&self, user_id: UserId) -> Result<Permissions> { - self.guild() + fn _permissions_for(&self, cache: &Arc<RwLock<Cache>>, user_id: UserId) -> Result<Permissions> { + self.guild(&cache) .ok_or_else(|| Error::Model(ModelError::GuildNotFound)) .map(|g| g.read().permissions_in(self.id, user_id)) } @@ -628,13 +638,13 @@ impl GuildChannel { /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong /// [`Message`]: struct.Message.html /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES - pub fn send_message<F>(&self, f: F) -> Result<Message> + pub fn send_message<F>(&self, context: &Context, f: F) -> Result<Message> where for <'b> F: FnOnce(&'b mut CreateMessage<'b>) -> &'b mut CreateMessage<'b> { #[cfg(feature = "cache")] { let req = Permissions::SEND_MESSAGES; - if !utils::user_has_perms(self.id, req)? { + if !utils::user_has_perms(&context.cache, self.id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 28c00fe..60ee3df 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -1,13 +1,19 @@ //! Models relating to Discord channels. use chrono::{DateTime, FixedOffset}; -use crate::model::prelude::*; +use crate::{model::prelude::*}; use serde_json::Value; +#[cfg(feature = "client")] +use crate::client::Context; #[cfg(feature = "model")] use crate::builder::{CreateEmbed, EditMessage}; #[cfg(all(feature = "cache", feature = "model"))] -use crate::CACHE; +use crate::cache::Cache; +#[cfg(all(feature = "cache", feature = "model"))] +use parking_lot::RwLock; +#[cfg(all(feature = "cache", feature = "model"))] +use std::sync::Arc; #[cfg(all(feature = "cache", feature = "model"))] use std::fmt::Write; #[cfg(feature = "model")] @@ -102,30 +108,30 @@ impl Message { /// .configure(|c| c.prefix("~")) /// .cmd("channelname", channel_name)); /// - /// command!(channel_name(_ctx, msg) { - /// let _ = match msg.channel() { - /// Some(Channel::Category(c)) => msg.reply(&c.read().name), - /// Some(Channel::Group(c)) => msg.reply(&c.read().name()), - /// Some(Channel::Guild(c)) => msg.reply(&c.read().name), + /// command!(channel_name(ctx, msg) { + /// let _ = match msg.channel(&ctx.cache) { + /// Some(Channel::Category(c)) => msg.reply(&ctx, &c.read().name), + /// Some(Channel::Group(c)) => msg.reply(&ctx, &c.read().name()), + /// Some(Channel::Guild(c)) => msg.reply(&ctx, &c.read().name), /// Some(Channel::Private(c)) => { /// let channel = c.read(); /// let user = channel.recipient.read(); /// - /// msg.reply(&format!("DM with {}", user.name.clone())) + /// msg.reply(&ctx, &format!("DM with {}", user.name.clone())) /// }, - /// None => msg.reply("Unknown"), + /// None => msg.reply(&ctx, "Unknown"), /// }; /// }); /// # } /// ``` #[cfg(feature = "cache")] #[inline] - pub fn channel(&self) -> Option<Channel> { CACHE.read().channel(self.channel_id) } + pub fn channel(&self, cache: &Arc<RwLock<Cache>>) -> Option<Channel> { cache.read().channel(self.channel_id) } /// A util function for determining whether this message was sent by someone else, or the /// bot. #[cfg(all(feature = "cache", feature = "utils"))] - pub fn is_own(&self) -> bool { self.author.id == CACHE.read().user.id } + pub fn is_own(&self, cache: &Arc<RwLock<Cache>>) -> bool { self.author.id == cache.read().user.id } /// Deletes the message. /// @@ -141,12 +147,12 @@ impl Message { /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [`ModelError::InvalidUser`]: ../error/enum.Error.html#variant.InvalidUser /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES - pub fn delete(&self) -> Result<()> { + pub fn delete(&self, context: &Context) -> Result<()> { #[cfg(feature = "cache")] { let req = Permissions::MANAGE_MESSAGES; - let is_author = self.author.id == CACHE.read().user.id; - let has_perms = utils::user_has_perms(self.channel_id, req)?; + let is_author = self.author.id == context.cache.read().user.id; + let has_perms = utils::user_has_perms(&context.cache, self.channel_id, req)?; if !is_author && !has_perms { return Err(Error::Model(ModelError::InvalidPermissions(req))); @@ -169,12 +175,12 @@ impl Message { /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [`Reaction`]: struct.Reaction.html /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES - pub fn delete_reactions(&self) -> Result<()> { + pub fn delete_reactions(&self, context: &Context) -> Result<()> { #[cfg(feature = "cache")] { let req = Permissions::MANAGE_MESSAGES; - if !utils::user_has_perms(self.channel_id, req)? { + if !utils::user_has_perms(&context.cache, self.channel_id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -214,11 +220,11 @@ impl Message { /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong /// [`EditMessage`]: ../../builder/struct.EditMessage.html /// [`the limit`]: ../../builder/struct.EditMessage.html#method.content - pub fn edit<F>(&mut self, f: F) -> Result<()> + pub fn edit<F>(&mut self, context: &Context, f: F) -> Result<()> where F: FnOnce(&mut EditMessage) -> &mut EditMessage { #[cfg(feature = "cache")] { - if self.author.id != CACHE.read().user.id { + if self.author.id != context.cache.read().user.id { return Err(Error::Model(ModelError::InvalidUser)); } } @@ -276,7 +282,7 @@ impl Message { /// Returns message content, but with user and role mentions replaced with /// names and everyone/here mentions cancelled. #[cfg(feature = "cache")] - pub fn content_safe(&self) -> String { + pub fn content_safe(&self, cache: &Arc<RwLock<Cache>>) -> String { let mut result = self.content.clone(); // First replace all user mentions. @@ -293,7 +299,7 @@ impl Message { for id in &self.mention_roles { let mention = id.mention(); - if let Some(role) = id.to_role_cached() { + if let Some(role) = id.to_role_cached(&cache) { result = result.replace(&mention, &format!("@{}", role.name)); } else { result = result.replace(&mention, "@deleted-role"); @@ -342,8 +348,8 @@ impl Message { /// /// [`guild_id`]: #method.guild_id #[cfg(feature = "cache")] - pub fn guild(&self) -> Option<Arc<RwLock<Guild>>> { - CACHE.read().guild(self.guild_id?) + pub fn guild(&self, cache: &Arc<RwLock<Cache>>) -> Option<Arc<RwLock<Guild>>> { + cache.read().guild(self.guild_id?) } /// True if message was sent using direct messages. @@ -360,8 +366,8 @@ impl Message { /// /// [`Guild::members`]: ../guild/struct.Guild.html#structfield.members #[cfg(feature = "cache")] - pub fn member(&self) -> Option<Member> { - self.guild().and_then(|g| g.read().members.get(&self.author.id).cloned()) + pub fn member(&self, cache: &Arc<RwLock<Cache>>) -> Option<Member> { + self.guild(&cache).and_then(|g| g.read().members.get(&self.author.id).cloned()) } /// Checks the length of a string to ensure that it is within Discord's @@ -395,13 +401,13 @@ impl Message { /// /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES.html - pub fn pin(&self) -> Result<()> { + pub fn pin(&self, context: &Context) -> Result<()> { #[cfg(feature = "cache")] { if self.guild_id.is_some() { let req = Permissions::MANAGE_MESSAGES; - if !utils::user_has_perms(self.channel_id, req)? { + if !utils::user_has_perms(&context.cache, self.channel_id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -426,17 +432,17 @@ impl Message { /// ../permissions/struct.Permissions.html#associatedconstant.ADD_REACTIONS /// [permissions]: ../permissions/index.html #[inline] - pub fn react<R: Into<ReactionType>>(&self, reaction_type: R) -> Result<()> { - self._react(&reaction_type.into()) + pub fn react<R: Into<ReactionType>>(&self, context: &Context, reaction_type: R) -> Result<()> { + self._react(&context, &reaction_type.into()) } - fn _react(&self, reaction_type: &ReactionType) -> Result<()> { + fn _react(&self, context: &Context, reaction_type: &ReactionType) -> Result<()> { #[cfg(feature = "cache")] { if self.guild_id.is_some() { let req = Permissions::ADD_REACTIONS; - if !utils::user_has_perms(self.channel_id, req)? { + if !utils::user_has_perms(&context.cache, self.channel_id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -467,7 +473,7 @@ impl Message { /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES - pub fn reply(&self, content: &str) -> Result<Message> { + pub fn reply(&self, context: &Context, content: &str) -> Result<Message> { if let Some(length_over) = Message::overflow_length(content) { return Err(Error::Model(ModelError::MessageTooLong(length_over))); } @@ -477,7 +483,7 @@ impl Message { if self.guild_id.is_some() { let req = Permissions::SEND_MESSAGES; - if !utils::user_has_perms(self.channel_id, req)? { + if !utils::user_has_perms(&context.cache, self.channel_id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -526,13 +532,13 @@ impl Message { /// /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES - pub fn unpin(&self) -> Result<()> { + pub fn unpin(&self, context: &Context) -> Result<()> { #[cfg(feature = "cache")] { if self.guild_id.is_some() { let req = Permissions::MANAGE_MESSAGES; - if !utils::user_has_perms(self.channel_id, req)? { + if !utils::user_has_perms(&context.cache, self.channel_id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -546,8 +552,8 @@ impl Message { /// **Note**: /// If message was sent in a private channel, then the function will return /// `None`. - pub fn author_nick(&self) -> Option<String> { - self.guild_id.as_ref().and_then(|guild_id| self.author.nick_in(*guild_id)) + pub fn author_nick(&self, context: &Context) -> Option<String> { + self.guild_id.as_ref().and_then(|guild_id| self.author.nick_in(&context, *guild_id)) } pub(crate) fn check_content_length(map: &JsonMap) -> Result<()> { diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs index 9c913cb..d21acb2 100644 --- a/src/model/channel/mod.rs +++ b/src/model/channel/mod.rs @@ -20,22 +20,29 @@ pub use self::private_channel::*; pub use self::reaction::*; pub use self::channel_category::*; -use crate::internal::RwLockExt; -use crate::model::prelude::*; +use crate::{internal::RwLockExt, model::prelude::*}; use serde::de::Error as DeError; use serde::ser::{SerializeStruct, Serialize, Serializer}; use serde_json; use super::utils::deserialize_u64; +#[cfg(feature = "client")] +use crate::client::Context; #[cfg(feature = "model")] use std::fmt::{Display, Formatter, Result as FmtResult}; #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] -use std::str::FromStr; +use crate::cache::FromStrAndCache; #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] use crate::model::misc::ChannelParseError; #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] use crate::utils::parse_channel; +#[cfg(feature = "cache")] +use crate::cache::Cache; +#[cfg(feature = "cache")] +use std::sync::Arc; +#[cfg(feature = "cache")] +use parking_lot::RwLock; /// A container for any channel. #[derive(Clone, Debug)] @@ -71,13 +78,17 @@ impl Channel { /// Basic usage: /// /// ```rust,no_run + /// # extern crate parking_lot; /// # extern crate serenity; /// # - /// # use self::serenity::model::id::ChannelId; + /// # use serenity::{cache::Cache, model::id::ChannelId}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; /// # /// # #[cfg(feature = "model")] /// # fn main() { - /// # let channel = ChannelId(0).to_channel().unwrap(); + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// # let channel = ChannelId(0).to_channel_cached(&cache).unwrap(); /// # /// match channel.group() { /// Some(group_lock) => { @@ -113,13 +124,17 @@ impl Channel { /// Basic usage: /// /// ```rust,no_run + /// # extern crate parking_lot; /// # extern crate serenity; /// # - /// # use self::serenity::model::id::ChannelId; + /// # use serenity::{cache::Cache, model::id::ChannelId}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; /// # /// # #[cfg(feature = "model")] /// # fn main() { - /// # let channel = ChannelId(0).to_channel().unwrap(); + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// # let channel = ChannelId(0).to_channel_cached(&cache).unwrap(); /// # /// match channel.guild() { /// Some(guild_lock) => { @@ -151,13 +166,17 @@ impl Channel { /// Basic usage: /// /// ```rust,no_run + /// # extern crate parking_lot; /// # extern crate serenity; /// # - /// # use self::serenity::model::id::ChannelId; + /// # use serenity::{cache::Cache, model::id::ChannelId}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; /// # /// # #[cfg(feature = "model")] /// # fn main() { - /// # let channel = ChannelId(0).to_channel().unwrap(); + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// # let channel = ChannelId(0).to_channel_cached(&cache).unwrap(); /// # /// match channel.private() { /// Some(private_lock) => { @@ -192,13 +211,17 @@ impl Channel { /// Basic usage: /// /// ```rust,no_run + /// # extern crate parking_lot; /// # extern crate serenity; /// # - /// # use self::serenity::model::id::ChannelId; + /// # use serenity::{cache::Cache, model::id::ChannelId}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; /// # /// # #[cfg(feature = "model")] /// # fn main() { - /// # let channel = ChannelId(0).to_channel().unwrap(); + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// # let channel = ChannelId(0).to_channel_cached(&cache).unwrap(); /// # /// match channel.category() { /// Some(category_lock) => { @@ -221,24 +244,27 @@ impl Channel { /// Deletes the inner channel. /// + /// **Note**: If the `cache`-feature is enabled permissions will be checked and upon + /// owning the required permissions the HTTP-request will be issued. + /// /// **Note**: There is no real function as _deleting_ a [`Group`]. The /// closest functionality is leaving it. /// /// [`Group`]: struct.Group.html #[cfg(feature = "model")] - pub fn delete(&self) -> Result<()> { + pub fn delete(&self, context: &Context) -> Result<()> { match *self { Channel::Group(ref group) => { let _ = group.read().leave()?; }, Channel::Guild(ref public_channel) => { - let _ = public_channel.read().delete()?; + let _ = public_channel.read().delete(&context)?; }, Channel::Private(ref private_channel) => { let _ = private_channel.read().delete()?; }, Channel::Category(ref category) => { - category.read().delete()?; + category.read().delete(&context)?; }, } @@ -564,12 +590,12 @@ mod test { } #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] -impl FromStr for Channel { +impl FromStrAndCache for Channel { type Err = ChannelParseError; - fn from_str(s: &str) -> StdResult<Self, Self::Err> { + fn from_str(cache: &Arc<RwLock<Cache>>, s: &str) -> StdResult<Self, Self::Err> { match parse_channel(s) { - Some(x) => match ChannelId(x).to_channel_cached() { + Some(x) => match ChannelId(x).to_channel_cached(&cache) { Some(channel) => Ok(channel), _ => Err(ChannelParseError::NotPresentInCache), }, diff --git a/src/model/channel/reaction.rs b/src/model/channel/reaction.rs index a1df12a..6c5bdeb 100644 --- a/src/model/channel/reaction.rs +++ b/src/model/channel/reaction.rs @@ -1,4 +1,4 @@ -use crate::model::prelude::*; +use crate::{model::prelude::*}; use serde::de::{Deserialize, Error as DeError, MapAccess, Visitor}; use serde::ser::{SerializeMap, Serialize, Serializer}; use std::{ @@ -11,10 +11,11 @@ use std::{ }, str::FromStr }; + use crate::internal::prelude::*; -#[cfg(all(feature = "cache", feature = "model"))] -use crate::CACHE; +#[cfg(feature = "client")] +use crate::client::Context; #[cfg(feature = "model")] use crate::http; @@ -50,8 +51,8 @@ impl Reaction { /// /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[inline] - pub fn channel(&self) -> Result<Channel> { - self.channel_id.to_channel() + pub fn channel(&self, context: &Context) -> Result<Channel> { + self.channel_id.to_channel(&context) } /// Deletes the reaction, but only if the current user is the user who made @@ -69,10 +70,10 @@ impl Reaction { /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES /// [permissions]: ../permissions/index.html - pub fn delete(&self) -> Result<()> { + pub fn delete(&self, context: &Context) -> Result<()> { let user_id = feature_cache! { { - let user = if self.user_id == CACHE.read().user.id { + let user = if self.user_id == context.cache.read().user.id { None } else { Some(self.user_id.0) @@ -87,7 +88,7 @@ impl Reaction { if user.is_some() { let req = Permissions::MANAGE_MESSAGES; - if !utils::user_has_perms(self.channel_id, req).unwrap_or(true) { + if !utils::user_has_perms(&context.cache, self.channel_id, req).unwrap_or(true) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -122,8 +123,8 @@ impl Reaction { /// If not - or the user was not found - this will perform a request over /// the REST API for the user. #[inline] - pub fn user(&self) -> Result<User> { - self.user_id.to_user() + pub fn user(&self, context: &Context) -> Result<User> { + self.user_id.to_user(&context) } /// Retrieves the list of [`User`]s who have reacted to a [`Message`] with a @@ -326,19 +327,13 @@ impl From<char> for ReactionType { /// Reacting to a message with an apple: /// /// ```rust,no_run - /// # use serenity::model::id::ChannelId; - /// # use std::error::Error; - /// # - /// # fn try_main() -> Result<(), Box<Error>> { - /// # let message = ChannelId(0).message(0)?; + /// # use serenity::{command, model::id::ChannelId}; /// # - /// message.react('🍎')?; - /// # Ok(()) - /// # } + /// # command!(example(context) { + /// # let message = ChannelId(0).message(0)?; /// # - /// # fn main() { - /// # try_main().unwrap(); - /// # } + /// message.react(&context, '🍎')?; + /// # }); /// ``` fn from(ch: char) -> ReactionType { ReactionType::Unicode(ch.to_string()) } } diff --git a/src/model/guild/emoji.rs b/src/model/guild/emoji.rs index 03c7a7e..dde379e 100644 --- a/src/model/guild/emoji.rs +++ b/src/model/guild/emoji.rs @@ -15,7 +15,11 @@ use super::super::ModelError; #[cfg(all(feature = "cache", feature = "model"))] use super::super::id::GuildId; #[cfg(all(feature = "cache", feature = "model"))] -use crate::{CACHE, http}; +use crate::{cache::Cache, http}; +#[cfg(all(feature = "cache", feature = "model"))] +use parking_lot::RwLock; +#[cfg(all(feature = "cache", feature = "model"))] +use std::sync::Arc; /// Represents a custom guild emoji, which can either be created using the API, /// or via an integration. Emojis created using the API only work within the @@ -52,7 +56,7 @@ impl Emoji { /// /// **Note**: Only user accounts may use this method. /// - /// [Manage Emojis]: + /// [Manage Emojis]: /// ../permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS /// /// # Examples @@ -60,8 +64,14 @@ impl Emoji { /// Delete a given emoji: /// /// ```rust,no_run - /// # use serenity::model::guild::Emoji; - /// # use serenity::model::id::EmojiId; + /// # extern crate parking_lot; + /// # extern crate serenity; + /// # + /// # use serenity::{cache::Cache, model::{guild::Emoji, id::EmojiId}}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; + /// # + /// # let cache = Arc::new(RwLock::new(Cache::default())); /// # /// # let mut emoji = Emoji { /// # animated: false, @@ -73,14 +83,14 @@ impl Emoji { /// # }; /// # /// // assuming emoji has been set already - /// match emoji.delete() { + /// match emoji.delete(&cache) { /// Ok(()) => println!("Emoji deleted."), /// Err(_) => println!("Could not delete emoji.") /// } /// ``` #[cfg(feature = "cache")] - pub fn delete(&self) -> Result<()> { - match self.find_guild_id() { + pub fn delete(&self, cache: &Arc<RwLock<Cache>>) -> Result<()> { + match self.find_guild_id(&cache) { Some(guild_id) => http::delete_emoji(guild_id.0, self.id.0), None => Err(Error::Model(ModelError::ItemMissing)), } @@ -99,9 +109,9 @@ impl Emoji { /// Change the name of an emoji: /// /// ```rust,no_run - /// # use serenity::model::guild::Emoji; - /// # use serenity::model::id::EmojiId; + /// # use serenity::{command, model::{guild::Emoji, id::EmojiId}}; /// # + /// # command!(example(context) { /// # let mut emoji = Emoji { /// # animated: false, /// # id: EmojiId(7), @@ -110,14 +120,14 @@ impl Emoji { /// # require_colons: false, /// # roles: vec![], /// # }; - /// # /// // assuming emoji has been set already - /// let _ = emoji.edit("blobuwu"); + /// let _ = emoji.edit(&context.cache, "blobuwu"); /// assert_eq!(emoji.name, "blobuwu"); + /// # }); /// ``` #[cfg(feature = "cache")] - pub fn edit(&mut self, name: &str) -> Result<()> { - match self.find_guild_id() { + pub fn edit(&mut self, cache: &Arc<RwLock<Cache>>, name: &str) -> Result<()> { + match self.find_guild_id(&cache) { Some(guild_id) => { let map = json!({ "name": name, @@ -145,8 +155,14 @@ impl Emoji { /// Print the guild id that owns this emoji: /// /// ```rust,no_run - /// # use serenity::model::guild::Emoji; - /// # use serenity::model::id::EmojiId; + /// # extern crate parking_lot; + /// # extern crate serenity; + /// # + /// # use serenity::{cache::Cache, model::{guild::Emoji, id::EmojiId}}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; + /// # + /// # let cache = Arc::new(RwLock::new(Cache::default())); /// # /// # let mut emoji = Emoji { /// # animated: false, @@ -158,13 +174,13 @@ impl Emoji { /// # }; /// # /// // assuming emoji has been set already - /// if let Some(guild_id) = emoji.find_guild_id() { + /// if let Some(guild_id) = emoji.find_guild_id(&cache) { /// println!("{} is owned by {}", emoji.name, guild_id); /// } /// ``` #[cfg(feature = "cache")] - pub fn find_guild_id(&self) -> Option<GuildId> { - for guild in CACHE.read().guilds.values() { + pub fn find_guild_id(&self, cache: &Arc<RwLock<Cache>>) -> Option<GuildId> { + for guild in cache.read().guilds.values() { let guild = guild.read(); if guild.emojis.contains_key(&self.id) { @@ -182,8 +198,7 @@ impl Emoji { /// Print the direct link to the given emoji: /// /// ```rust,no_run - /// # use serenity::model::guild::Emoji; - /// # use serenity::model::id::EmojiId; + /// # use serenity::model::{guild::Emoji, id::EmojiId}; /// # /// # let mut emoji = Emoji { /// # animated: false, diff --git a/src/model/guild/guild_id.rs b/src/model/guild/guild_id.rs index c488b52..3146c78 100644 --- a/src/model/guild/guild_id.rs +++ b/src/model/guild/guild_id.rs @@ -1,7 +1,9 @@ -use crate::model::prelude::*; +use crate::{model::prelude::*}; +#[cfg(feature = "client")] +use crate::client::Context; #[cfg(all(feature = "cache", feature = "model"))] -use crate::CACHE; +use crate::cache::Cache; #[cfg(feature = "model")] use crate::builder::{EditGuild, EditMember, EditRole}; #[cfg(feature = "model")] @@ -407,7 +409,7 @@ impl GuildId { /// [`Guild`]: ../guild/struct.Guild.html #[cfg(feature = "cache")] #[inline] - pub fn to_guild_cached(self) -> Option<Arc<RwLock<Guild>>> { CACHE.read().guild(self) } + pub fn to_guild_cached(self, cache: &Arc<RwLock<Cache>>) -> Option<Arc<RwLock<Guild>>> { cache.read().guild(self) } /// Requests [`PartialGuild`] over REST API. /// @@ -456,14 +458,14 @@ impl GuildId { /// [`Guild`]: ../guild/struct.Guild.html /// [`Member`]: ../guild/struct.Member.html #[inline] - pub fn member<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { - self._member(user_id.into()) + pub fn member<U: Into<UserId>>(&self, context: &Context, user_id: U) -> Result<Member> { + self._member(&context, user_id.into()) } - fn _member(&self, user_id: UserId) -> Result<Member> { + fn _member(&self, context: &Context, user_id: UserId) -> Result<Member> { #[cfg(feature = "cache")] { - if let Some(member) = CACHE.read().member(self.0, user_id) { + if let Some(member) = context.cache.read().member(self.0, user_id) { return Ok(member); } } @@ -563,7 +565,9 @@ impl GuildId { /// [`utils::shard_id`]: ../../utils/fn.shard_id.html #[cfg(all(feature = "cache", feature = "utils"))] #[inline] - pub fn shard_id(&self) -> u64 { crate::utils::shard_id(self.0, CACHE.read().shard_count) } + pub fn shard_id(&self, cache: &Arc<RwLock<Cache>>) -> u64 { + crate::utils::shard_id(self.0, cache.read().shard_count) + } /// Returns the Id of the shard associated with the guild. /// diff --git a/src/model/guild/member.rs b/src/model/guild/member.rs index cbda4c3..d969bd0 100644 --- a/src/model/guild/member.rs +++ b/src/model/guild/member.rs @@ -1,4 +1,4 @@ -use crate::model::prelude::*; +use crate::{model::prelude::*}; use chrono::{DateTime, FixedOffset}; use std::fmt::{ Display, @@ -7,6 +7,8 @@ use std::fmt::{ }; use super::deserialize_sync_user; +#[cfg(feature = "client")] +use crate::client::Context; #[cfg(all(feature = "builder", feature = "cache", feature = "model"))] use crate::builder::EditMember; #[cfg(all(feature = "cache", feature = "model"))] @@ -16,7 +18,7 @@ use std::borrow::Cow; #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] use crate::utils::Colour; #[cfg(all(feature = "cache", feature = "model"))] -use crate::{CACHE, http, utils}; +use crate::{cache::Cache, http, utils}; /// A trait for allowing both u8 or &str or (u8, &str) to be passed into the `ban` methods in `Guild` and `Member`. pub trait BanOptions { @@ -169,8 +171,8 @@ impl Member { /// Determines the member's colour. #[cfg(all(feature = "cache", feature = "utils"))] - pub fn colour(&self) -> Option<Colour> { - let cache = CACHE.read(); + pub fn colour(&self, cache: &Arc<RwLock<Cache>>) -> Option<Colour> { + let cache = cache.read(); let guild = cache.guilds.get(&self.guild_id)?.read(); let mut roles = self.roles @@ -191,8 +193,8 @@ impl Member { /// (This returns the first channel that can be read by the member, if there isn't /// one returns `None`) #[cfg(feature = "cache")] - pub fn default_channel(&self) -> Option<Arc<RwLock<GuildChannel>>> { - let guild = match self.guild_id.to_guild_cached() { + pub fn default_channel(&self, cache: &Arc<RwLock<Cache>>) -> Option<Arc<RwLock<GuildChannel>>> { + let guild = match self.guild_id.to_guild_cached(&cache) { Some(guild) => guild, None => return None, }; @@ -257,8 +259,8 @@ impl Member { /// position. If two or more roles have the same highest position, then the /// role with the lowest ID is the highest. #[cfg(feature = "cache")] - pub fn highest_role_info(&self) -> Option<(RoleId, i64)> { - let guild = self.guild_id.to_guild_cached()?; + pub fn highest_role_info(&self, cache: &Arc<RwLock<Cache>>) -> Option<(RoleId, i64)> { + let guild = self.guild_id.to_guild_cached(&cache)?; let reader = guild.try_read()?; let mut highest = None; @@ -315,20 +317,20 @@ impl Member { /// [`ModelError::GuildNotFound`]: ../error/enum.Error.html#variant.GuildNotFound /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [Kick Members]: ../permissions/struct.Permissions.html#associatedconstant.KICK_MEMBERS - pub fn kick(&self) -> Result<()> { + pub fn kick(&self, context: &Context) -> Result<()> { #[cfg(feature = "cache")] { - let cache = CACHE.read(); + let locked_cache = context.cache.read(); - if let Some(guild) = cache.guilds.get(&self.guild_id) { + if let Some(guild) = locked_cache.guilds.get(&self.guild_id) { let req = Permissions::KICK_MEMBERS; let reader = guild.read(); - if !reader.has_perms(req) { + if !reader.has_perms(&context.cache, req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } - reader.check_hierarchy(self.user.read().id)?; + reader.check_hierarchy(&context.cache, self.user.read().id)?; } } @@ -356,8 +358,8 @@ impl Member { /// [`ModelError::GuildNotFound`]: ../error/enum.Error.html#variant.GuildNotFound /// [`ModelError::ItemMissing`]: ../error/enum.Error.html#variant.ItemMissing #[cfg(feature = "cache")] - pub fn permissions(&self) -> Result<Permissions> { - let guild = match self.guild_id.to_guild_cached() { + pub fn permissions(&self, cache: &Arc<RwLock<Cache>>) -> Result<Permissions> { + let guild = match self.guild_id.to_guild_cached(&cache) { Some(guild) => guild, None => return Err(From::from(ModelError::GuildNotFound)), }; @@ -426,10 +428,10 @@ impl Member { /// /// If role data can not be found for the member, then `None` is returned. #[cfg(feature = "cache")] - pub fn roles(&self) -> Option<Vec<Role>> { + pub fn roles(&self, cache: &Arc<RwLock<Cache>>) -> Option<Vec<Role>> { self .guild_id - .to_guild_cached() + .to_guild_cached(&cache) .map(|g| g .read() .roles diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index 86cba3d..3ea8d06 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -17,13 +17,19 @@ pub use self::role::*; pub use self::audit_log::*; use chrono::{DateTime, FixedOffset}; -use crate::model::prelude::*; +use crate::{model::prelude::*}; use serde::de::Error as DeError; use serde_json; use super::utils::*; +#[cfg(feature = "client")] +use crate::client::Context; #[cfg(all(feature = "cache", feature = "model"))] -use crate::CACHE; +use crate::cache::Cache; +#[cfg(all(feature = "cache", feature = "model"))] +use parking_lot::RwLock; +#[cfg(all(feature = "cache", feature = "model"))] +use std::sync::Arc; #[cfg(feature = "model")] use crate::http; #[cfg(feature = "model")] @@ -146,10 +152,10 @@ pub struct Guild { #[cfg(feature = "model")] impl Guild { #[cfg(feature = "cache")] - fn check_hierarchy(&self, other_user: UserId) -> Result<()> { - let current_id = CACHE.read().user.id; + fn check_hierarchy(&self, cache: &Arc<RwLock<Cache>>, other_user: UserId) -> Result<()> { + let current_id = cache.read().user.id; - if let Some(higher) = self.greater_member_hierarchy(other_user, current_id) { + if let Some(higher) = self.greater_member_hierarchy(&cache, other_user, current_id) { if higher != current_id { return Err(Error::Model(ModelError::Hierarchy)); } @@ -189,8 +195,8 @@ impl Guild { } #[cfg(feature = "cache")] - fn has_perms(&self, mut permissions: Permissions) -> bool { - let user_id = CACHE.read().user.id; + fn has_perms(&self, cache: &Arc<RwLock<Cache>>, mut permissions: Permissions) -> bool { + let user_id = cache.read().user.id; let perms = self.member_permissions(user_id); permissions.remove(perms); @@ -228,20 +234,20 @@ impl Guild { /// [`User`]: ../user/struct.User.html /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS #[inline] - pub fn ban<U: Into<UserId>, BO: BanOptions>(&self, user: U, options: &BO) -> Result<()> { - self._ban(user.into(), options) + pub fn ban<U: Into<UserId>, BO: BanOptions>(&self, context: &Context, user: U, options: &BO) -> Result<()> { + self._ban(&context, user.into(), options) } - fn _ban<BO: BanOptions>(&self, user: UserId, options: &BO) -> Result<()> { + fn _ban<BO: BanOptions>(&self, context: &Context, user: UserId, options: &BO) -> Result<()> { #[cfg(feature = "cache")] { let req = Permissions::BAN_MEMBERS; - if !self.has_perms(req) { + if !self.has_perms(&context.cache, req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } - self.check_hierarchy(user)?; + self.check_hierarchy(&context.cache, user)?; } self.id.ban(user, options) @@ -259,12 +265,12 @@ impl Guild { /// [`Ban`]: struct.Ban.html /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS - pub fn bans(&self) -> Result<Vec<Ban>> { + pub fn bans(&self, context: &Context) -> Result<Vec<Ban>> { #[cfg(feature = "cache")] { let req = Permissions::BAN_MEMBERS; - if !self.has_perms(req) { + if !self.has_perms(&context.cache, req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -346,13 +352,13 @@ impl Guild { /// [`Channel`]: ../channel/enum.Channel.html /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [Manage Channels]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS - pub fn create_channel<C>(&self, name: &str, kind: ChannelType, category: C) -> Result<GuildChannel> + pub fn create_channel<C>(&self, context: &Context, name: &str, kind: ChannelType, category: C) -> Result<GuildChannel> where C: Into<Option<ChannelId>> { #[cfg(feature = "cache")] { let req = Permissions::MANAGE_CHANNELS; - if !self.has_perms(req) { + if !self.has_perms(&context.cache, req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -417,13 +423,13 @@ impl Guild { /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [`Role`]: struct.Role.html /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES - pub fn create_role<F>(&self, f: F) -> Result<Role> + pub fn create_role<F>(&self, context: &Context, f: F) -> Result<Role> where F: FnOnce(&mut EditRole) -> &mut EditRole { #[cfg(feature = "cache")] { let req = Permissions::MANAGE_ROLES; - if !self.has_perms(req) { + if !self.has_perms(&context.cache, req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -442,10 +448,10 @@ impl Guild { /// if the current user is not the guild owner. /// /// [`ModelError::InvalidUser`]: ../error/enum.Error.html#variant.InvalidUser - pub fn delete(&self) -> Result<PartialGuild> { + pub fn delete(&self, context: &Context) -> Result<PartialGuild> { #[cfg(feature = "cache")] { - if self.owner_id != CACHE.read().user.id { + if self.owner_id != context.cache.read().user.id { let req = Permissions::MANAGE_GUILD; return Err(Error::Model(ModelError::InvalidPermissions(req))); @@ -519,13 +525,13 @@ impl Guild { /// /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD - pub fn edit<F>(&mut self, f: F) -> Result<()> + pub fn edit<F>(&mut self, context: &Context, f: F) -> Result<()> where F: FnOnce(&mut EditGuild) -> &mut EditGuild { #[cfg(feature = "cache")] { let req = Permissions::MANAGE_GUILD; - if !self.has_perms(req) { + if !self.has_perms(&context.cache, req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -600,12 +606,12 @@ impl Guild { /// /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [Change Nickname]: ../permissions/struct.Permissions.html#associatedconstant.CHANGE_NICKNAME - pub fn edit_nickname(&self, new_nickname: Option<&str>) -> Result<()> { + pub fn edit_nickname(&self, context: &Context, new_nickname: Option<&str>) -> Result<()> { #[cfg(feature = "cache")] { let req = Permissions::CHANGE_NICKNAME; - if !self.has_perms(req) { + if !self.has_perms(&context.cache, req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -673,14 +679,15 @@ impl Guild { /// [`position`]: struct.Role.html#structfield.position #[cfg(feature = "cache")] #[inline] - pub fn greater_member_hierarchy<T, U>(&self, lhs_id: T, rhs_id: U) + pub fn greater_member_hierarchy<T, U>(&self, cache: &Arc<RwLock<Cache>>, lhs_id: T, rhs_id: U) -> Option<UserId> where T: Into<UserId>, U: Into<UserId> { - self._greater_member_hierarchy(lhs_id.into(), rhs_id.into()) + self._greater_member_hierarchy(&cache, lhs_id.into(), rhs_id.into()) } #[cfg(feature = "cache")] fn _greater_member_hierarchy( &self, + cache: &Arc<RwLock<Cache>>, lhs_id: UserId, rhs_id: UserId, ) -> Option<UserId> { @@ -697,10 +704,10 @@ impl Guild { } let lhs = self.members.get(&lhs_id)? - .highest_role_info() + .highest_role_info(&cache) .unwrap_or((RoleId(0), 0)); let rhs = self.members.get(&rhs_id)? - .highest_role_info() + .highest_role_info(&cache) .unwrap_or((RoleId(0), 0)); // If LHS and RHS both have no top position or have the same role ID, @@ -754,12 +761,12 @@ impl Guild { /// /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD - pub fn invites(&self) -> Result<Vec<RichInvite>> { + pub fn invites(&self, context: &Context) -> Result<Vec<RichInvite>> { #[cfg(feature = "cache")] { let req = Permissions::MANAGE_GUILD; - if !self.has_perms(req) { + if !self.has_perms(&context.cache, req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -790,7 +797,9 @@ impl Guild { /// [`Guild`]: ../guild/struct.Guild.html /// [`Member`]: struct.Member.html #[inline] - pub fn member<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { self.id.member(user_id) } + pub fn member<U: Into<UserId>>(&self, context: &Context, user_id: U) -> Result<Member> { + self.id.member(&context, user_id) + } /// Gets a list of the guild's members. /// @@ -1339,12 +1348,12 @@ impl Guild { /// [`GuildPrune`]: struct.GuildPrune.html /// [`Member`]: struct.Member.html /// [Kick Members]: ../permissions/struct.Permissions.html#associatedconstant.KICK_MEMBERS - pub fn prune_count(&self, days: u16) -> Result<GuildPrune> { + pub fn prune_count(&self, context: &Context, days: u16) -> Result<GuildPrune> { #[cfg(feature = "cache")] { let req = Permissions::KICK_MEMBERS; - if !self.has_perms(req) { + if !self.has_perms(&context.cache, req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -1374,7 +1383,7 @@ impl Guild { /// [`utils::shard_id`]: ../../utils/fn.shard_id.html #[cfg(all(feature = "cache", feature = "utils"))] #[inline] - pub fn shard_id(&self) -> u64 { self.id.shard_id() } + pub fn shard_id(&self, cache: &Arc<RwLock<Cache>>) -> u64 { self.id.shard_id(&cache) } /// Returns the Id of the shard associated with the guild. /// @@ -1432,12 +1441,12 @@ impl Guild { /// [`GuildPrune`]: struct.GuildPrune.html /// [`Member`]: struct.Member.html /// [Kick Members]: ../permissions/struct.Permissions.html#associatedconstant.KICK_MEMBERS - pub fn start_prune(&self, days: u16) -> Result<GuildPrune> { + pub fn start_prune(&self, context: &Context, days: u16) -> Result<GuildPrune> { #[cfg(feature = "cache")] { let req = Permissions::KICK_MEMBERS; - if !self.has_perms(req) { + if !self.has_perms(&context.cache, req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -1457,12 +1466,12 @@ impl Guild { /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [`User`]: ../user/struct.User.html /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS - pub fn unban<U: Into<UserId>>(&self, user_id: U) -> Result<()> { + pub fn unban<U: Into<UserId>>(&self, context: &Context, user_id: U) -> Result<()> { #[cfg(feature = "cache")] { let req = Permissions::BAN_MEMBERS; - if !self.has_perms(req) { + if !self.has_perms(&context.cache, req) { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -1503,11 +1512,9 @@ impl Guild { /// /// struct Handler; /// - /// use serenity::CACHE; - /// /// impl EventHandler for Handler { - /// fn message(&self, _: Context, msg: Message) { - /// if let Some(arc) = msg.guild_id.unwrap().to_guild_cached() { + /// fn message(&self, ctx: Context, msg: Message) { + /// if let Some(arc) = msg.guild_id.unwrap().to_guild_cached(&ctx.cache) { /// if let Some(role) = arc.read().role_by_name("role_name") { /// println!("{:?}", role); /// } diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs index 6276db2..8b1be58 100644 --- a/src/model/guild/partial_guild.rs +++ b/src/model/guild/partial_guild.rs @@ -1,6 +1,8 @@ -use crate::model::prelude::*; +use crate::{model::prelude::*}; use super::super::utils::{deserialize_emojis, deserialize_roles}; +#[cfg(feature = "client")] +use crate::client::Context; #[cfg(feature = "model")] use crate::builder::{EditGuild, EditMember, EditRole}; @@ -333,7 +335,9 @@ impl PartialGuild { /// /// [`Guild`]: struct.Guild.html /// [`Member`]: struct.Member.html - pub fn member<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { self.id.member(user_id) } + pub fn member<U: Into<UserId>>(&self, context: &Context, user_id: U) -> Result<Member> { + self.id.member(&context, user_id) + } /// Gets a list of the guild's members. /// @@ -380,7 +384,7 @@ impl PartialGuild { /// [`utils::shard_id`]: ../../utils/fn.shard_id.html #[cfg(all(feature = "cache", feature = "utils"))] #[inline] - pub fn shard_id(&self) -> u64 { self.id.shard_id() } + pub fn shard_id(&self, context: &Context) -> u64 { self.id.shard_id(&context.cache) } /// Returns the Id of the shard associated with the guild. /// @@ -465,8 +469,6 @@ impl PartialGuild { /// /// struct Handler; /// - /// use serenity::CACHE; - /// /// impl EventHandler for Handler { /// fn message(&self, _: Context, msg: Message) { /// let guild = msg.guild_id.unwrap().to_partial_guild().unwrap(); diff --git a/src/model/guild/role.rs b/src/model/guild/role.rs index b68803b..404cfcf 100644 --- a/src/model/guild/role.rs +++ b/src/model/guild/role.rs @@ -6,10 +6,10 @@ use crate::builder::EditRole; #[cfg(all(feature = "cache", feature = "model"))] use crate::internal::prelude::*; #[cfg(all(feature = "cache", feature = "model"))] -use crate::{CACHE, Cache, http}; +use crate::{cache::Cache, http}; #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] -use std::str::FromStr; +use crate::cache::FromStrAndCache; #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] use crate::model::misc::RoleParseError; #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] @@ -75,7 +75,9 @@ impl Role { /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[cfg(feature = "cache")] #[inline] - pub fn delete(&self) -> Result<()> { http::delete_role(self.find_guild()?.0, self.id.0) } + pub fn delete(&self, cache: &Arc<RwLock<Cache>>) -> Result<()> { + http::delete_role(self.find_guild(&cache)?.0, self.id.0) + } /// Edits a [`Role`], optionally setting its new fields. /// @@ -85,9 +87,9 @@ impl Role { /// /// Make a role hoisted: /// - /// ```rust,no_run + /// ```rust,ignore /// # use serenity::model::id::RoleId; - /// # let role = RoleId(7).to_role_cached().unwrap(); + /// # let role = RoleId(7).to_role_cached(&cache).unwrap(); /// // assuming a `role` has already been bound // /// role.edit(|mut r| { @@ -100,8 +102,8 @@ impl Role { /// [`Role`]: struct.Role.html /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[cfg(all(feature = "builder", feature = "cache"))] - pub fn edit<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> { - self.find_guild() + pub fn edit<F: FnOnce(EditRole) -> EditRole>(&self, cache: &Arc<RwLock<Cache>>, f: F) -> Result<Role> { + self.find_guild(&cache) .and_then(|guild_id| guild_id.edit_role(self.id, f)) } @@ -114,8 +116,8 @@ impl Role { /// /// [`ModelError::GuildNotFound`]: ../error/enum.Error.html#variant.GuildNotFound #[cfg(feature = "cache")] - pub fn find_guild(&self) -> Result<GuildId> { - for guild in CACHE.read().guilds.values() { + pub fn find_guild(&self, cache: &Arc<RwLock<Cache>>) -> Result<GuildId> { + for guild in cache.read().guilds.values() { let guild = guild.read(); if guild.roles.contains_key(&RoleId(self.id.0)) { @@ -178,12 +180,12 @@ impl RoleId { /// /// [`Role`]: ../guild/struct.Role.html #[cfg(feature = "cache")] - pub fn to_role_cached(self) -> Option<Role> { - self._to_role_cached(&CACHE) + pub fn to_role_cached(self, cache: &Arc<RwLock<Cache>>) -> Option<Role> { + self._to_role_cached(&cache) } #[cfg(feature = "cache")] - pub(crate) fn _to_role_cached(self, cache: &RwLock<Cache>) -> Option<Role> { + pub(crate) fn _to_role_cached(self, cache: &Arc<RwLock<Cache>>) -> Option<Role> { for guild in cache.read().guilds.values() { let guild = guild.read(); @@ -211,12 +213,12 @@ impl<'a> From<&'a Role> for RoleId { } #[cfg(all(feature = "cache", feature = "model", feature = "utils"))] -impl FromStr for Role { +impl FromStrAndCache for Role { type Err = RoleParseError; - fn from_str(s: &str) -> StdResult<Self, Self::Err> { + fn from_str(cache: &Arc<RwLock<Cache>>, s: &str) -> StdResult<Self, Self::Err> { match parse_role(s) { - Some(x) => match RoleId(x).to_role_cached() { + Some(x) => match RoleId(x).to_role_cached(&cache) { Some(role) => Ok(role), _ => Err(RoleParseError::NotPresentInCache), }, diff --git a/src/model/invite.rs b/src/model/invite.rs index 74a46dc..a6f0479 100644 --- a/src/model/invite.rs +++ b/src/model/invite.rs @@ -3,6 +3,8 @@ use chrono::{DateTime, FixedOffset}; use super::prelude::*; +#[cfg(feature = "client")] +use crate::client::Context; #[cfg(feature = "model")] use crate::builder::CreateInvite; #[cfg(feature = "model")] @@ -11,6 +13,12 @@ use crate::internal::prelude::*; use super::{Permissions, utils as model_utils}; #[cfg(feature = "model")] use crate::{http, utils}; +#[cfg(feature = "cache")] +use crate::cache::Cache; +#[cfg(feature = "cache")] +use parking_lot::RwLock; +#[cfg(feature = "cache")] +use std::sync::Arc; /// Information about an invite code. /// @@ -70,18 +78,18 @@ impl Invite { /// [`GuildChannel`]: ../channel/struct.GuildChannel.html /// [Create Invite]: ../permissions/struct.Permissions.html#associatedconstant.CREATE_INVITE /// [permission]: ../permissions/index.html - pub fn create<C, F>(channel_id: C, f: F) -> Result<RichInvite> + pub fn create<C, F>(context: &Context, channel_id: C, f: F) -> Result<RichInvite> where C: Into<ChannelId>, F: FnOnce(CreateInvite) -> CreateInvite { - Self::_create(channel_id.into(), f) + Self::_create(&context, channel_id.into(), f) } - fn _create<F>(channel_id: ChannelId, f: F) -> Result<RichInvite> + fn _create<F>(context: &Context, channel_id: ChannelId, f: F) -> Result<RichInvite> where F: FnOnce(CreateInvite) -> CreateInvite { #[cfg(feature = "cache")] { let req = Permissions::CREATE_INVITE; - if !model_utils::user_has_perms(channel_id, req)? { + if !model_utils::user_has_perms(&context.cache, channel_id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -103,12 +111,12 @@ impl Invite { /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD /// [permission]: ../permissions/index.html - pub fn delete(&self) -> Result<Invite> { + pub fn delete(&self, context: &Context) -> Result<Invite> { #[cfg(feature = "cache")] { let req = Permissions::MANAGE_GUILD; - if !model_utils::user_has_perms(self.channel.id, req)? { + if !model_utils::user_has_perms(&context.cache, self.channel.id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } @@ -195,7 +203,7 @@ impl InviteGuild { /// [`utils::shard_id`]: ../../utils/fn.shard_id.html #[cfg(all(feature = "cache", feature = "utils"))] #[inline] - pub fn shard_id(&self) -> u64 { self.id.shard_id() } + pub fn shard_id(&self, cache: &Arc<RwLock<Cache>>) -> u64 { self.id.shard_id(&cache) } /// Returns the Id of the shard associated with the guild. /// @@ -288,12 +296,12 @@ impl RichInvite { /// [`http::delete_invite`]: ../../http/fn.delete_invite.html /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD.html /// [permission]: ../permissions/index.html - pub fn delete(&self) -> Result<Invite> { + pub fn delete(&self, context: &Context) -> Result<Invite> { #[cfg(feature = "cache")] { let req = Permissions::MANAGE_GUILD; - if !model_utils::user_has_perms(self.channel.id, req)? { + if !model_utils::user_has_perms(&context.cache, self.channel.id, req)? { return Err(Error::Model(ModelError::InvalidPermissions(req))); } } diff --git a/src/model/misc.rs b/src/model/misc.rs index e2adbcc..5726af6 100644 --- a/src/model/misc.rs +++ b/src/model/misc.rs @@ -112,20 +112,6 @@ impl StdError for UserParseError { } } -#[cfg(all(feature = "model", feature = "utils"))] -impl FromStr for User { - type Err = UserParseError; - - fn from_str(s: &str) -> StdResult<Self, Self::Err> { - match utils::parse_username(s) { - Some(x) => UserId(x as u64) - .to_user() - .map_err(|e| UserParseError::Rest(Box::new(e))), - _ => Err(UserParseError::InvalidUsername), - } - } -} - macro_rules! impl_from_str { (id: $($id:tt, $err:ident;)*) => { $( diff --git a/src/model/user.rs b/src/model/user.rs index 80bf40e..923d4d1 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -4,11 +4,10 @@ use serde_json; use std::fmt; use super::utils::deserialize_u16; use super::prelude::*; -use crate::internal::prelude::*; -use crate::model::misc::Mentionable; +use crate::{internal::prelude::*, model::misc::Mentionable}; -#[cfg(all(feature = "cache", feature = "model"))] -use crate::CACHE; +#[cfg(feature = "client")] +use crate::client::Context; #[cfg(feature = "model")] use crate::builder::{CreateMessage, EditProfile}; #[cfg(feature = "model")] @@ -22,6 +21,8 @@ use std::fmt::Write; #[cfg(feature = "model")] use std::mem; #[cfg(all(feature = "cache", feature = "model"))] +use crate::cache::Cache; +#[cfg(all(feature = "cache", feature = "model"))] use std::sync::Arc; #[cfg(feature = "model")] use crate::utils::{self, VecMap}; @@ -50,10 +51,15 @@ impl CurrentUser { /// Print out the current user's avatar url if one is set: /// /// ```rust,no_run - /// # use serenity::CACHE; + /// # extern crate parking_lot; + /// # extern crate serenity; /// # - /// # let cache = CACHE.read(); + /// # use serenity::{cache::Cache, model::prelude::*, prelude::*}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; /// # + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// # let cache = cache.read(); /// // assuming the cache has been unlocked /// let user = &cache.user; /// @@ -82,11 +88,9 @@ impl CurrentUser { /// Change the avatar: /// /// ```rust,ignore - /// use serenity::CACHE; - /// /// let avatar = serenity::utils::read_image("./avatar.png").unwrap(); /// - /// CACHE.write().user.edit(|p| p.avatar(Some(&avatar))); + /// context.cache.write().user.edit(|p| p.avatar(Some(&avatar))); /// ``` /// /// [`EditProfile`]: ../../builder/struct.EditProfile.html @@ -131,10 +135,15 @@ impl CurrentUser { /// Print out the names of all guilds the current user is in: /// /// ```rust,no_run - /// # use serenity::CACHE; + /// # extern crate parking_lot; + /// # extern crate serenity; /// # - /// # let cache = CACHE.read(); + /// # use serenity::{cache::Cache, model::prelude::*, prelude::*}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; /// # + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// # let cache = cache.read(); /// // assuming the cache has been unlocked /// let user = &cache.user; /// @@ -159,9 +168,15 @@ impl CurrentUser { /// Get the invite url with no permissions set: /// /// ```rust,no_run - /// # use serenity::CACHE; + /// # extern crate parking_lot; + /// # extern crate serenity; + /// # + /// # use serenity::{cache::Cache, model::prelude::*, prelude::*}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; /// # - /// # let mut cache = CACHE.write(); + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// # let mut cache = cache.write(); /// /// use serenity::model::Permissions; /// @@ -182,10 +197,15 @@ impl CurrentUser { /// Get the invite url with some basic permissions set: /// /// ```rust,no_run - /// # use serenity::CACHE; + /// # extern crate parking_lot; + /// # extern crate serenity; /// # - /// # let mut cache = CACHE.write(); - /// + /// # use serenity::{cache::Cache, model::prelude::*, prelude::*}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; + /// # + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// # let mut cache = cache.write(); /// use serenity::model::Permissions; /// /// // assuming the cache has been unlocked @@ -238,10 +258,15 @@ impl CurrentUser { /// Print out the current user's static avatar url if one is set: /// /// ```rust,no_run - /// # use serenity::CACHE; + /// # extern crate parking_lot; + /// # extern crate serenity; /// # - /// # let cache = CACHE.read(); + /// # use serenity::{cache::Cache, model::prelude::*, prelude::*}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; /// # + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// # let cache = cache.read(); /// // assuming the cache has been unlocked /// let user = &cache.user; /// @@ -262,10 +287,15 @@ impl CurrentUser { /// Print out the current user's distinct identifier (e.g., Username#1234): /// /// ```rust,no_run - /// # use serenity::CACHE; + /// # extern crate parking_lot; + /// # extern crate serenity; /// # - /// # let cache = CACHE.read(); + /// # use serenity::{cache::Cache, model::prelude::*, prelude::*}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; /// # + /// # let cache = Arc::new(RwLock::new(Cache::default())); + /// # let cache = cache.read(); /// // assuming the cache has been unlocked /// println!("The current user's distinct identifier is {}", cache.user.tag()); /// ``` @@ -415,14 +445,13 @@ impl User { /// # use serenity::model::prelude::*; /// # /// use serenity::model::Permissions; - /// use serenity::CACHE; /// /// struct Handler; /// /// impl EventHandler for Handler { - /// fn message(&self, _: Context, msg: Message) { + /// fn message(&self, ctx: Context, msg: Message) { /// if msg.content == "~help" { - /// let cache = CACHE.read(); + /// let cache = ctx.cache.read(); /// /// let url = match cache.user.invite_url(Permissions::empty()) { /// Ok(v) => v, @@ -438,18 +467,18 @@ impl User { /// url, /// ); /// - /// let dm = msg.author.direct_message(|m| { + /// let dm = msg.author.direct_message(&ctx, |m| { /// m.content(&help) /// }); /// /// match dm { /// Ok(_) => { - /// let _ = msg.react('👌'); + /// let _ = msg.react(&ctx, '👌'); /// }, /// Err(why) => { /// println!("Err sending help: {:?}", why); /// - /// let _ = msg.reply("There was an error DMing you help."); + /// let _ = msg.reply(&ctx, "There was an error DMing you help."); /// }, /// }; /// } @@ -483,7 +512,7 @@ impl User { // (AKA: Clippy is wrong and so we have to mark as allowing this lint.) #[allow(clippy::let_and_return)] #[cfg(feature = "builder")] - pub fn direct_message<F>(&self, f: F) -> Result<Message> + pub fn direct_message<F>(&self, context: &Context, f: F) -> Result<Message> where for <'b> F: FnOnce(&'b mut CreateMessage<'b>) -> &'b mut CreateMessage<'b> { if self.bot { return Err(Error::Model(ModelError::MessagingBot)); @@ -492,7 +521,7 @@ impl User { let private_channel_id = feature_cache! { { let finding = { - let cache = CACHE.read(); + let cache = context.cache.read(); let finding = cache.private_channels .values() @@ -545,9 +574,9 @@ impl User { /// [direct_message]: #method.direct_message #[cfg(feature = "builder")] #[inline] - pub fn dm<F>(&self, f: F) -> Result<Message> + pub fn dm<F>(&self, context: &Context, f: F) -> Result<Message> where for <'b> F: FnOnce(&'b mut CreateMessage<'b>) -> &'b mut CreateMessage<'b> { - self.direct_message(f) + self.direct_message(&context, f) } /// Retrieves the URL to the user's avatar, falling back to the default @@ -585,17 +614,17 @@ impl User { /// [`Role`]: ../guild/struct.Role.html /// [`Cache`]: ../../cache/struct.Cache.html // no-cache would warn on guild_id. - pub fn has_role<G, R>(&self, guild: G, role: R) -> bool + pub fn has_role<G, R>(&self, context: &Context, guild: G, role: R) -> bool where G: Into<GuildContainer>, R: Into<RoleId> { - self._has_role(guild.into(), role.into()) + self._has_role(&context, guild.into(), role.into()) } - fn _has_role(&self, guild: GuildContainer, role: RoleId) -> bool { + fn _has_role(&self, context: &Context, guild: GuildContainer, role: RoleId) -> bool { match guild { GuildContainer::Guild(guild) => guild.roles.contains_key(&role), GuildContainer::Id(_guild_id) => { feature_cache! {{ - CACHE.read() + context.cache.read() .guilds .get(&_guild_id) .map(|g| { @@ -622,9 +651,12 @@ impl User { /// out-of-sync: /// /// ```rust,no_run - /// # use serenity::prelude::*; - /// # use serenity::model::prelude::*; + /// # extern crate parking_lot; + /// # extern crate serenity; /// # + /// # use serenity::{cache::Cache, model::prelude::*, prelude::*}; + /// # use parking_lot::RwLock; + /// # use std::sync::Arc; /// struct Handler; /// /// impl EventHandler for Handler { @@ -635,38 +667,42 @@ impl User { /// /// let mut client = Client::new("token", Handler).unwrap(); /// # - /// use serenity::model::id::UserId; - /// use serenity::CACHE; + /// use serenity::{command, model::id::UserId}; /// use std::thread; /// use std::time::Duration; /// - /// let special_users = vec![UserId(114941315417899012), UserId(87600987040120832)]; - /// /// // start a new thread to periodically refresh the special users' data /// // every 12 hours + /// # command!(example(context) { + /// # let context = context.clone(); + /// /// let handle = thread::spawn(move || { + /// let special_users = vec![UserId(114941315417899012), UserId(87600987040120832)]; + /// # let cache = Arc::new(RwLock::new(Cache::default())); /// // 12 hours in seconds /// let duration = Duration::from_secs(43200); /// /// loop { /// thread::sleep(duration); /// - /// let cache = CACHE.read(); + /// let cache = cache.read(); /// /// for id in &special_users { + /// /// if let Some(user) = cache.user(*id) { - /// if let Err(why) = user.write().refresh() { + /// + /// if let Err(why) = user.write().refresh(&context) { /// println!("Error refreshing {}: {:?}", id, why); /// } /// } /// } /// } /// }); - /// + /// # }); /// println!("{:?}", client.start()); /// ``` - pub fn refresh(&mut self) -> Result<()> { - self.id.to_user().map(|replacement| { + pub fn refresh(&mut self, context: &Context) -> Result<()> { + self.id.to_user(&context).map(|replacement| { mem::replace(self, replacement); () @@ -722,15 +758,15 @@ impl User { /// /// If none is used, it returns `None`. #[inline] - pub fn nick_in<G>(&self, guild_id: G) -> Option<String> + pub fn nick_in<G>(&self, context: &Context, guild_id: G) -> Option<String> where G: Into<GuildId> { - self._nick_in(guild_id.into()) + self._nick_in(&context, guild_id.into()) } - fn _nick_in(&self, guild_id: GuildId) -> Option<String> { + fn _nick_in(&self, context: &Context, guild_id: GuildId) -> Option<String> { #[cfg(feature = "cache")] { - guild_id.to_guild_cached().and_then(|guild| { + guild_id.to_guild_cached(&context.cache).and_then(|guild| { guild.read().members.get(&self.id).and_then(|member| member.nick.clone()) }) } @@ -769,7 +805,7 @@ impl UserId { /// [`User`]: ../user/struct.User.html #[cfg(feature = "cache")] #[inline] - pub fn to_user_cached(self) -> Option<Arc<RwLock<User>>> { CACHE.read().user(self) } + pub fn to_user_cached(self, cache: &Arc<RwLock<Cache>>) -> Option<Arc<RwLock<User>>> { cache.read().user(self) } /// First attempts to find a [`User`] by its Id in the cache, /// upon failure requests it via the REST API. @@ -779,10 +815,10 @@ impl UserId { /// /// [`User`]: ../user/struct.User.html #[inline] - pub fn to_user(self) -> Result<User> { + pub fn to_user(self, context: &Context) -> Result<User> { #[cfg(feature = "cache")] { - if let Some(user) = CACHE.read().user(self) { + if let Some(user) = context.cache.read().user(self) { return Ok(user.read().clone()); } } diff --git a/src/model/utils.rs b/src/model/utils.rs index 2399db3..9cb9571 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -14,7 +14,7 @@ use crate::internal::prelude::*; #[cfg(all(feature = "cache", feature = "model"))] use super::permissions::Permissions; #[cfg(all(feature = "cache", feature = "model"))] -use crate::CACHE; +use crate::cache::Cache; pub fn default_true() -> bool { true @@ -206,8 +206,8 @@ pub fn serialize_gen_locked_map<K: Eq + Hash, S: Serializer, V: Serialize>( } #[cfg(all(feature = "cache", feature = "model"))] -pub fn user_has_perms(channel_id: ChannelId, mut permissions: Permissions) -> Result<bool> { - let cache = CACHE.read(); +pub fn user_has_perms(cache: &Arc<RwLock<Cache>>, channel_id: ChannelId, mut permissions: Permissions) -> Result<bool> { + let cache = cache.read(); let current_user = &cache.user; let channel = match cache.channel(channel_id) { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 0162010..3b8a24c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -38,7 +38,7 @@ use std::{ #[cfg(feature = "cache")] use crate::cache::Cache; #[cfg(feature = "cache")] -use crate::CACHE; +use std::sync::Arc; /// Converts a HashMap into a final `serde_json::Map` representation. pub fn hashmap_to_json_map<H, T>(map: HashMap<T, Value, H>) @@ -467,9 +467,9 @@ pub fn shard_id(guild_id: u64, shard_count: u64) -> u64 { (guild_id >> 22) % sha /// assert_eq!(1234, utils::with_cache(|cache| cache.user.id)); /// ``` #[cfg(feature = "cache")] -pub fn with_cache<T, F>(f: F) -> T +pub fn with_cache<T, F>(cache: &Arc<RwLock<Cache>>, f: F) -> T where F: Fn(&Cache) -> T { - let cache = CACHE.read(); + let cache = cache.read(); f(&cache) } @@ -488,9 +488,9 @@ pub fn with_cache<T, F>(f: F) -> T /// /// [`with_cache`]: #fn.with_cache #[cfg(feature = "cache")] -pub fn with_cache_mut<T, F>(mut f: F) -> T +pub fn with_cache_mut<T, F>(cache: &Arc<RwLock<Cache>>, mut f: F) -> T where F: FnMut(&mut Cache) -> T { - let mut cache = CACHE.write(); + let mut cache = cache.write(); f(&mut cache) } @@ -609,7 +609,7 @@ impl Default for ContentSafeOptions { #[cfg(feature = "cache")] #[inline] -fn clean_roles(cache: &RwLock<Cache>, s: &mut String) { +fn clean_roles(cache: &Arc<RwLock<Cache>>, s: &mut String) { let mut progress = 0; while let Some(mut mention_start) = s[progress..].find("<@&") { @@ -769,28 +769,28 @@ fn clean_users(cache: &RwLock<Cache>, s: &mut String, show_discriminator: bool, /// Sanitise an `@everyone` mention. /// /// ```rust +/// # extern crate serenity; +/// # extern crate parking_lot; +/// # +/// # use std::sync::Arc; +/// # use serenity::client::Cache; +/// # use parking_lot::RwLock; +/// # +/// # let cache = Arc::new(RwLock::new(Cache::default())); /// use serenity::utils::{ /// content_safe, /// ContentSafeOptions, /// }; /// /// let with_mention = "@everyone"; -/// let without_mention = content_safe(&with_mention, &ContentSafeOptions::default()); +/// let without_mention = content_safe(&cache, &with_mention, &ContentSafeOptions::default()); /// /// assert_eq!("@\u{200B}everyone".to_string(), without_mention); /// ``` /// [`ContentSafeOptions`]: struct.ContentSafeOptions.html /// [`Cache`]: ../cache/struct.Cache.html #[cfg(feature = "cache")] -pub fn content_safe(s: &str, options: &ContentSafeOptions) -> String { - let cache = &CACHE; - - _content_safe(&cache, s, options) -} - - -#[cfg(feature = "cache")] -fn _content_safe(cache: &RwLock<Cache>, s: &str, options: &ContentSafeOptions) -> String { +pub fn content_safe(cache: &Arc<RwLock<Cache>>, s: &str, options: &ContentSafeOptions) -> String { let mut s = s.to_string(); if options.clean_role { @@ -944,7 +944,7 @@ mod test { slow_mode_rate: 0, }; - let cache = RwLock::new(Cache::default()); + let cache = Arc::new(RwLock::new(Cache::default())); { let mut cache = cache.try_write().unwrap(); @@ -969,31 +969,31 @@ mod test { // User mentions let options = ContentSafeOptions::default(); - assert_eq!(without_user_mentions, _content_safe(&cache, with_user_metions, &options)); + assert_eq!(without_user_mentions, content_safe(&cache, with_user_metions, &options)); let options = ContentSafeOptions::default(); assert_eq!(format!("@{}#{:04}", user.name, user.discriminator), - _content_safe(&cache, "<@!100000000000000000>", &options)); + content_safe(&cache, "<@!100000000000000000>", &options)); let options = ContentSafeOptions::default(); assert_eq!(format!("@{}#{:04}", user.name, user.discriminator), - _content_safe(&cache, "<@100000000000000000>", &options)); + content_safe(&cache, "<@100000000000000000>", &options)); let options = options.show_discriminator(false); assert_eq!(format!("@{}", user.name), - _content_safe(&cache, "<@!100000000000000000>", &options)); + content_safe(&cache, "<@!100000000000000000>", &options)); let options = options.show_discriminator(false); assert_eq!(format!("@{}", user.name), - _content_safe(&cache, "<@100000000000000000>", &options)); + content_safe(&cache, "<@100000000000000000>", &options)); let options = options.display_as_member_from(guild.id); assert_eq!(format!("@{}", member.nick.unwrap()), - _content_safe(&cache, "<@!100000000000000000>", &options)); + content_safe(&cache, "<@!100000000000000000>", &options)); let options = options.clean_user(false); assert_eq!(with_user_metions, - _content_safe(&cache, with_user_metions, &options)); + content_safe(&cache, with_user_metions, &options)); // Channel mentions let with_channel_mentions = "<#> <#deleted-channel> #deleted-channel <#0> \ @@ -1005,11 +1005,11 @@ mod test { #deleted-channel"; assert_eq!(without_channel_mentions, - _content_safe(&cache, with_channel_mentions, &options)); + content_safe(&cache, with_channel_mentions, &options)); let options = options.clean_channel(false); assert_eq!(with_channel_mentions, - _content_safe(&cache, with_channel_mentions, &options)); + content_safe(&cache, with_channel_mentions, &options)); // Role mentions let with_role_mentions = "<@&> @deleted-role <@&9829> \ @@ -1019,11 +1019,11 @@ mod test { @ferris-club-member @deleted-role"; assert_eq!(without_role_mentions, - _content_safe(&cache, with_role_mentions, &options)); + content_safe(&cache, with_role_mentions, &options)); let options = options.clean_role(false); assert_eq!(with_role_mentions, - _content_safe(&cache, with_role_mentions, &options)); + content_safe(&cache, with_role_mentions, &options)); // Everyone mentions let with_everyone_mention = "@everyone"; @@ -1031,11 +1031,11 @@ mod test { let without_everyone_mention = "@\u{200B}everyone"; assert_eq!(without_everyone_mention, - _content_safe(&cache, with_everyone_mention, &options)); + content_safe(&cache, with_everyone_mention, &options)); let options = options.clean_everyone(false); assert_eq!(with_everyone_mention, - _content_safe(&cache, with_everyone_mention, &options)); + content_safe(&cache, with_everyone_mention, &options)); // Here mentions let with_here_mention = "@here"; @@ -1043,10 +1043,10 @@ mod test { let without_here_mention = "@\u{200B}here"; assert_eq!(without_here_mention, - _content_safe(&cache, with_here_mention, &options)); + content_safe(&cache, with_here_mention, &options)); let options = options.clean_here(false); assert_eq!(with_here_mention, - _content_safe(&cache, with_here_mention, &options)); + content_safe(&cache, with_here_mention, &options)); } } |