Contest caching

This commit is contained in:
Natsu Kagami 2020-02-11 19:32:48 -05:00
parent d9536a96ca
commit 75f4e403df
Signed by: nki
GPG key ID: 73376E117CD20735
3 changed files with 85 additions and 15 deletions

2
Cargo.lock generated
View file

@ -163,7 +163,7 @@ dependencies = [
[[package]]
name = "codeforces"
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 = [
"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)",

View file

@ -6,11 +6,12 @@ use serenity::{
builder::CreateEmbed, framework::standard::CommandError, model::channel::Message,
utils::MessageBuilder,
};
use std::{collections::HashMap, sync::Arc};
use youmubot_prelude::*;
lazy_static! {
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();
static ref PROBLEMSET_LINK: Regex = Regex::new(
@ -20,28 +21,92 @@ lazy_static! {
}
enum ContestOrProblem {
Contest(Contest, Vec<Problem>),
Contest(Contest, Option<Vec<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.
pub fn codeforces_info_hook(ctx: &mut Context, m: &Message) {
if m.author.bot {
return;
}
let http = ctx.data.get_cloned::<HTTPClient>();
let contest_cache = ctx.data.get_cloned::<ContestCache>();
let matches = CONTEST_LINK
.captures_iter(&m.content)
.chain(PROBLEMSET_LINK.captures_iter(&m.content))
// .collect::<Vec<_>>()
// .into_par_iter()
.filter_map(|v| match parse_capture(http.clone(), v) {
.filter_map(
|v| match parse_capture(http.clone(), contest_cache.clone(), v) {
Ok(v) => Some(v),
Err(e) => {
dbg!(e);
None
}
})
},
)
.collect::<Vec<_>>();
if !matches.is_empty() {
m.channel_id
@ -97,8 +162,11 @@ fn print_info_message<'a>(
.push_bold_safe(&contest.name)
.push(format!("]({})", link))
.push(format!(
" | **{}** problems | duration **{}**",
problems.len(),
" | {} | duration **{}**",
problems
.as_ref()
.map(|v| format!("{} | **{}** problems", contest.phase, v.len()))
.unwrap_or(format!("{}", contest.phase)),
duration
));
if let Some(p) = &contest.prepared_by {
@ -115,17 +183,18 @@ fn print_info_message<'a>(
fn parse_capture<'a>(
http: <HTTPClient as TypeMapKey>::Value,
contest_cache: ContestCache,
cap: Captures<'a>,
) -> Result<(ContestOrProblem, &'a str), CommandError> {
let contest: u64 = cap
let contest_id: u64 = cap
.name("contest")
.ok_or(CommandError::from("Contest not captured"))?
.as_str()
.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") {
Some(p) => {
for problem in problems {
for problem in problems.ok_or(CommandError::from("Contest hasn't started"))? {
if &problem.index == p.as_str() {
return Ok((
ContestOrProblem::Problem(problem),

View file

@ -24,6 +24,7 @@ pub use hook::codeforces_info_hook;
pub fn setup(path: &std::path::Path, data: &mut ShareMap, announcers: &mut AnnouncerHandler) {
CfSavedUsers::insert_into(data, path.join("cf_saved_users.yaml"))
.expect("Must be able to set up DB");
data.insert::<hook::ContestCache>(hook::ContestCache::default());
announcers.add("codeforces", announcer::updates);
}