mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-18 16:28:55 +00:00
Contest caching
This commit is contained in:
parent
d9536a96ca
commit
75f4e403df
3 changed files with 85 additions and 15 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -163,7 +163,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codeforces"
|
name = "codeforces"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/natsukagami/rust-codeforces-api#3ec1dc2a97c8225a5ba6bafee517080fc9ae88f7"
|
source = "git+https://github.com/natsukagami/rust-codeforces-api#e10f2155df238fe5edd2f0d33cb8d6a4ce252e69"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -6,11 +6,12 @@ use serenity::{
|
||||||
builder::CreateEmbed, framework::standard::CommandError, model::channel::Message,
|
builder::CreateEmbed, framework::standard::CommandError, model::channel::Message,
|
||||||
utils::MessageBuilder,
|
utils::MessageBuilder,
|
||||||
};
|
};
|
||||||
|
use std::{collections::HashMap, sync::Arc};
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CONTEST_LINK: Regex = Regex::new(
|
static ref CONTEST_LINK: Regex = Regex::new(
|
||||||
r"https?://codeforces\.com/(contest|gym)/(?P<contest>\d+)(?:/problem/(?P<problem>\w+))?"
|
r"https?://codeforces\.com/(contest|gym)s?/(?P<contest>\d+)(?:/problem/(?P<problem>\w+))?"
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
static ref PROBLEMSET_LINK: Regex = Regex::new(
|
static ref PROBLEMSET_LINK: Regex = Regex::new(
|
||||||
|
@ -20,28 +21,92 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ContestOrProblem {
|
enum ContestOrProblem {
|
||||||
Contest(Contest, Vec<Problem>),
|
Contest(Contest, Option<Vec<Problem>>),
|
||||||
Problem(Problem),
|
Problem(Problem),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Caches the contest list.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct ContestCache(Arc<RwLock<HashMap<u64, (Contest, Option<Vec<Problem>>)>>>);
|
||||||
|
|
||||||
|
impl TypeMapKey for ContestCache {
|
||||||
|
type Value = ContestCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContestCache {
|
||||||
|
fn get(
|
||||||
|
&self,
|
||||||
|
http: &<HTTPClient as TypeMapKey>::Value,
|
||||||
|
contest_id: u64,
|
||||||
|
) -> Result<(Contest, Option<Vec<Problem>>), CommandError> {
|
||||||
|
let rl = self.0.read();
|
||||||
|
match rl.get(&contest_id) {
|
||||||
|
Some(r @ (_, Some(_))) => Ok(r.clone()),
|
||||||
|
Some((c, None)) => match Contest::standings(http, contest_id, |f| f.limit(1, 1)) {
|
||||||
|
Ok((c, p, _)) => Ok({
|
||||||
|
drop(rl);
|
||||||
|
self.0
|
||||||
|
.write()
|
||||||
|
.entry(contest_id)
|
||||||
|
.or_insert((c, Some(p)))
|
||||||
|
.clone()
|
||||||
|
}),
|
||||||
|
Err(_) => Ok((c.clone(), None)),
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
drop(rl);
|
||||||
|
// Step 1: try to fetch it individually
|
||||||
|
match Contest::standings(http, contest_id, |f| f.limit(1, 1)) {
|
||||||
|
Ok((c, p, _)) => Ok(self
|
||||||
|
.0
|
||||||
|
.write()
|
||||||
|
.entry(contest_id)
|
||||||
|
.or_insert((c, Some(p)))
|
||||||
|
.clone()),
|
||||||
|
Err(codeforces::Error::Codeforces(s)) if s.ends_with("has not started") => {
|
||||||
|
// Fetch the entire list
|
||||||
|
{
|
||||||
|
let mut m = self.0.write();
|
||||||
|
let contests = Contest::list(http, contest_id > 100_000)?;
|
||||||
|
contests.into_iter().for_each(|c| {
|
||||||
|
m.entry(c.id).or_insert((c, None));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.0
|
||||||
|
.read()
|
||||||
|
.get(&contest_id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or("No contest found".into())
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}
|
||||||
|
// Step 2: try to fetch the entire list.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Prints info whenever a problem or contest (or more) is sent on a channel.
|
/// Prints info whenever a problem or contest (or more) is sent on a channel.
|
||||||
pub fn codeforces_info_hook(ctx: &mut Context, m: &Message) {
|
pub fn codeforces_info_hook(ctx: &mut Context, m: &Message) {
|
||||||
if m.author.bot {
|
if m.author.bot {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let http = ctx.data.get_cloned::<HTTPClient>();
|
let http = ctx.data.get_cloned::<HTTPClient>();
|
||||||
|
let contest_cache = ctx.data.get_cloned::<ContestCache>();
|
||||||
let matches = CONTEST_LINK
|
let matches = CONTEST_LINK
|
||||||
.captures_iter(&m.content)
|
.captures_iter(&m.content)
|
||||||
.chain(PROBLEMSET_LINK.captures_iter(&m.content))
|
.chain(PROBLEMSET_LINK.captures_iter(&m.content))
|
||||||
// .collect::<Vec<_>>()
|
// .collect::<Vec<_>>()
|
||||||
// .into_par_iter()
|
// .into_par_iter()
|
||||||
.filter_map(|v| match parse_capture(http.clone(), v) {
|
.filter_map(
|
||||||
Ok(v) => Some(v),
|
|v| match parse_capture(http.clone(), contest_cache.clone(), v) {
|
||||||
Err(e) => {
|
Ok(v) => Some(v),
|
||||||
dbg!(e);
|
Err(e) => {
|
||||||
None
|
dbg!(e);
|
||||||
}
|
None
|
||||||
})
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if !matches.is_empty() {
|
if !matches.is_empty() {
|
||||||
m.channel_id
|
m.channel_id
|
||||||
|
@ -97,8 +162,11 @@ fn print_info_message<'a>(
|
||||||
.push_bold_safe(&contest.name)
|
.push_bold_safe(&contest.name)
|
||||||
.push(format!("]({})", link))
|
.push(format!("]({})", link))
|
||||||
.push(format!(
|
.push(format!(
|
||||||
" | **{}** problems | duration **{}**",
|
" | {} | duration **{}**",
|
||||||
problems.len(),
|
problems
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| format!("{} | **{}** problems", contest.phase, v.len()))
|
||||||
|
.unwrap_or(format!("{}", contest.phase)),
|
||||||
duration
|
duration
|
||||||
));
|
));
|
||||||
if let Some(p) = &contest.prepared_by {
|
if let Some(p) = &contest.prepared_by {
|
||||||
|
@ -115,17 +183,18 @@ fn print_info_message<'a>(
|
||||||
|
|
||||||
fn parse_capture<'a>(
|
fn parse_capture<'a>(
|
||||||
http: <HTTPClient as TypeMapKey>::Value,
|
http: <HTTPClient as TypeMapKey>::Value,
|
||||||
|
contest_cache: ContestCache,
|
||||||
cap: Captures<'a>,
|
cap: Captures<'a>,
|
||||||
) -> Result<(ContestOrProblem, &'a str), CommandError> {
|
) -> Result<(ContestOrProblem, &'a str), CommandError> {
|
||||||
let contest: u64 = cap
|
let contest_id: u64 = cap
|
||||||
.name("contest")
|
.name("contest")
|
||||||
.ok_or(CommandError::from("Contest not captured"))?
|
.ok_or(CommandError::from("Contest not captured"))?
|
||||||
.as_str()
|
.as_str()
|
||||||
.parse()?;
|
.parse()?;
|
||||||
let (contest, problems, _) = Contest::standings(&http, contest, |f| f.limit(1, 1))?;
|
let (contest, problems) = contest_cache.get(&http, contest_id)?;
|
||||||
match cap.name("problem") {
|
match cap.name("problem") {
|
||||||
Some(p) => {
|
Some(p) => {
|
||||||
for problem in problems {
|
for problem in problems.ok_or(CommandError::from("Contest hasn't started"))? {
|
||||||
if &problem.index == p.as_str() {
|
if &problem.index == p.as_str() {
|
||||||
return Ok((
|
return Ok((
|
||||||
ContestOrProblem::Problem(problem),
|
ContestOrProblem::Problem(problem),
|
||||||
|
|
|
@ -24,6 +24,7 @@ pub use hook::codeforces_info_hook;
|
||||||
pub fn setup(path: &std::path::Path, data: &mut ShareMap, announcers: &mut AnnouncerHandler) {
|
pub fn setup(path: &std::path::Path, data: &mut ShareMap, announcers: &mut AnnouncerHandler) {
|
||||||
CfSavedUsers::insert_into(data, path.join("cf_saved_users.yaml"))
|
CfSavedUsers::insert_into(data, path.join("cf_saved_users.yaml"))
|
||||||
.expect("Must be able to set up DB");
|
.expect("Must be able to set up DB");
|
||||||
|
data.insert::<hook::ContestCache>(hook::ContestCache::default());
|
||||||
announcers.add("codeforces", announcer::updates);
|
announcers.add("codeforces", announcer::updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue