Add UserHeader as cacheable alternative to fetching a full user

This commit is contained in:
Natsu Kagami 2024-06-19 21:11:57 +02:00 committed by Natsu Kagami
parent 0e72f12b6d
commit 7a98dc21a9
4 changed files with 64 additions and 69 deletions

View file

@ -1,6 +1,8 @@
use std::collections::HashMap;
use std::convert::TryInto;
use std::sync::Arc;
use futures_util::lock::Mutex;
use models::*;
use request::builders::*;
use request::*;
@ -14,6 +16,8 @@ pub mod request;
#[derive(Clone)]
pub struct Client {
rosu: Arc<rosu_v2::Osu>,
user_header_cache: Arc<Mutex<HashMap<u64, Option<UserHeader>>>>,
}
pub fn vec_try_into<U, T: std::convert::TryFrom<U>>(v: Vec<U>) -> Result<Vec<T>, T::Error> {
@ -36,6 +40,7 @@ impl Client {
.await?;
Ok(Client {
rosu: Arc::new(rosu),
user_header_cache: Arc::new(Mutex::new(HashMap::new())),
})
}
@ -54,9 +59,26 @@ impl Client {
user: UserID,
f: impl FnOnce(&mut UserRequestBuilder) -> &mut UserRequestBuilder,
) -> Result<Option<User>, Error> {
let mut r = UserRequestBuilder::new(user);
let mut r = UserRequestBuilder::new(user.clone());
f(&mut r);
r.build(self).await
let u = r.build(self).await?;
if let UserID::ID(id) = user {
self.user_header_cache
.lock()
.await
.insert(id, u.clone().map(|v| v.into()));
}
Ok(u)
}
/// Fetch the user header.
pub async fn user_header(&self, id: u64) -> Result<Option<UserHeader>, Error> {
Ok(
match self.user_header_cache.lock().await.get(&id).cloned() {
Some(v) => v,
None => self.user(UserID::ID(id), |f| f).await?.map(|v| v.into()),
},
)
}
pub async fn scores(

View file

@ -460,6 +460,13 @@ impl UserEvent {
}
}
#[derive(Clone, Debug)]
pub struct UserHeader {
pub id: u64,
pub username: String,
pub country: String,
}
#[derive(Clone, Debug)]
pub struct User {
pub id: u64,
@ -498,6 +505,36 @@ impl User {
}
}
impl UserHeader {
pub fn link(&self) -> String {
format!("https://osu.ppy.sh/users/{}", self.id)
}
pub fn avatar_url(&self) -> String {
format!("https://a.ppy.sh/{}", self.id)
}
}
impl<'a> From<&'a User> for UserHeader {
fn from(u: &'a User) -> Self {
Self {
id: u.id,
username: u.username.clone(),
country: u.country.clone(),
}
}
}
impl From<User> for UserHeader {
fn from(u: User) -> Self {
Self {
id: u.id,
username: u.username,
country: u.country,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum Rank {
SS,
@ -547,6 +584,7 @@ pub struct Score {
pub normalized_score: u32,
pub pp: Option<f64>,
pub rank: Rank,
pub mode: Mode,
pub mods: Mods, // Later
pub count_300: u64,

View file

@ -127,6 +127,7 @@ impl From<rosu::score::Score> for Score {
server_accuracy: s.accuracy as f64,
global_rank: s.rank_global,
effective_pp: s.weight.map(|w| w.pp as f64),
mode: s.mode.into(),
mods: s
.mods
.iter()

View file

@ -1,55 +1,9 @@
use crate::models::{Mode, Mods};
use crate::Client;
use chrono::{DateTime, Utc};
use rosu_v2::error::OsuError;
use youmubot_prelude::*;
trait ToQuery {
fn to_query(&self) -> Vec<(&'static str, String)>;
}
impl<T: ToQuery> ToQuery for Option<T> {
fn to_query(&self) -> Vec<(&'static str, String)> {
match self {
Some(ref v) => v.to_query(),
None => vec![],
}
}
}
impl ToQuery for Mods {
fn to_query(&self) -> Vec<(&'static str, String)> {
vec![("mods", format!("{}", self.bits()))]
}
}
impl ToQuery for Mode {
fn to_query(&self) -> Vec<(&'static str, String)> {
vec![("m", (*self as u8).to_string())]
}
}
impl ToQuery for (Mode, bool) {
fn to_query(&self) -> Vec<(&'static str, String)> {
vec![
("m", (self.0 as u8).to_string()),
("a", (self.1 as u8).to_string()),
]
}
}
impl ToQuery for (&'static str, String) {
fn to_query(&self) -> Vec<(&'static str, String)> {
vec![(self.0, self.1.clone())]
}
}
impl ToQuery for (&'static str, DateTime<Utc>) {
fn to_query(&self) -> Vec<(&'static str, String)> {
vec![(self.0, format!("{}", self.1.format("%Y-%m-%d")))]
}
}
#[derive(Clone, Debug)]
pub enum UserID {
Username(String),
ID(u64),
@ -74,32 +28,12 @@ impl UserID {
}
}
impl ToQuery for UserID {
fn to_query(&self) -> Vec<(&'static str, String)> {
use UserID::*;
match self {
Username(ref s) => vec![("u", s.clone()), ("type", "string".to_owned())],
ID(u) => vec![("u", u.to_string()), ("type", "id".to_owned())],
}
}
}
pub enum BeatmapRequestKind {
Beatmap(u64),
Beatmapset(u64),
BeatmapHash(String),
}
impl ToQuery for BeatmapRequestKind {
fn to_query(&self) -> Vec<(&'static str, String)> {
use BeatmapRequestKind::*;
match self {
Beatmap(b) => vec![("b", b.to_string())],
Beatmapset(s) => vec![("s", s.to_string())],
BeatmapHash(ref h) => vec![("h", h.clone())],
}
}
}
fn handle_not_found<T>(v: Result<T, OsuError>) -> Result<Option<T>, OsuError> {
match v {
Ok(v) => Ok(Some(v)),