Skip to content

Commit 0baeb2b

Browse files
committed
Update cache and routing
1 parent c6ef984 commit 0baeb2b

File tree

12 files changed

+373
-129
lines changed

12 files changed

+373
-129
lines changed

app/src/cache.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,77 @@
11
use std::time::Duration;
22

3+
use futures::future::join_all;
4+
use futures::FutureExt;
35
use moka::future::Cache;
46

57
use crate::models::bm::player::BattleMetricsPlayerResponse;
68
use crate::models::bm::recent::BattleMetricsRecentServers;
9+
use crate::models::rm::MapResponse;
10+
use crate::models::rm::SearchResponse;
11+
use crate::state::AppState;
712

813
pub struct AppCache {
914
pub bm_user_from_name: Cache<String, BattleMetricsPlayerResponse>,
1015
pub bm_recent_servers: Cache<String, BattleMetricsRecentServers>,
16+
pub rm_search: Cache<String, SearchResponse>,
17+
pub rm_map: Cache<String, MapResponse>,
1118
}
1219

1320
impl AppCache {
1421
pub fn new() -> Self {
1522
Self {
1623
bm_user_from_name: Cache::builder()
1724
.time_to_live(Duration::from_secs(5 * 60))
25+
.time_to_idle(Duration::from_secs(1 * 60))
1826
.max_capacity(1000)
1927
.build(),
2028
bm_recent_servers: Cache::builder()
2129
.time_to_live(Duration::from_secs(5 * 60))
30+
.time_to_idle(Duration::from_secs(1 * 60))
31+
.max_capacity(1000)
32+
.build(),
33+
rm_search: Cache::builder()
34+
.time_to_live(Duration::from_secs(5 * 60))
35+
.time_to_idle(Duration::from_secs(1 * 60))
36+
.max_capacity(1000)
37+
.build(),
38+
rm_map: Cache::builder()
39+
.time_to_live(Duration::from_secs(5 * 60))
40+
.time_to_idle(Duration::from_secs(1 * 60))
2241
.max_capacity(1000)
2342
.build(),
2443
}
2544
}
45+
46+
pub async fn get_sizes(&self) -> (u64, u64, u64, u64) {
47+
(
48+
self.bm_user_from_name.weighted_size(),
49+
self.bm_recent_servers.weighted_size(),
50+
self.rm_search.weighted_size(),
51+
self.rm_map.weighted_size(),
52+
)
53+
}
54+
55+
pub async fn collect(&self, state: &AppState) {
56+
loop {
57+
self.collect_all().await;
58+
59+
let (bm_user_from_name, bm_recent_servers, rm_search, rm_map) = self.get_sizes().await;
60+
tracing::info!("Cache sizes: bm_user_from_name: {}, bm_recent_servers: {}, rm_search: {}, rm_map: {}", bm_user_from_name, bm_recent_servers, rm_search, rm_map);
61+
async_std::task::sleep(std::time::Duration::from_secs(10)).await;
62+
}
63+
}
64+
65+
pub async fn collect_all(&self) {
66+
let tasks = vec![
67+
async { self.bm_user_from_name.run_pending_tasks().await }.boxed(),
68+
async { self.bm_recent_servers.run_pending_tasks().await }.boxed(),
69+
async { self.rm_search.run_pending_tasks().await }.boxed(),
70+
async { self.rm_map.run_pending_tasks().await }.boxed(),
71+
];
72+
73+
join_all(tasks).await;
74+
}
2675
}
2776

2877
impl Default for AppCache {

app/src/main.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
use async_std::prelude::FutureExt;
12
use state::{AppState, AppStateInner};
23
use std::sync::Arc;
34

5+
pub mod cache;
46
pub mod database;
57
pub mod models;
68
pub mod server;
7-
pub mod cache;
89
pub mod state;
910
pub mod util;
1011

@@ -21,5 +22,9 @@ async fn main() {
2122
// let http = async { server::start_http(state.clone()).await };
2223

2324
// telegram.race(http).await;
24-
server::start_http(state).await;
25+
let http = server::start_http(state.clone());
26+
27+
let cache_size_notifier = state.cache.collect(&state);
28+
29+
cache_size_notifier.race(http).await;
2530
}

app/src/models/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod user;
22
pub mod bm;
3+
pub mod rm;

app/src/models/rm/map.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use crate::models::rm::MapResponse;
2+
use crate::state::AppState;
3+
use poem::Result;
4+
use reqwest::ClientBuilder;
5+
use reqwest::Client;
6+
use tracing::{info, warn};
7+
8+
pub async fn get_map(map_id: String) -> Result<MapResponse> {
9+
info!("Getting map: {}", map_id);
10+
let url = format!("https://api.rustmaps.com/internal/v1/maps/{}", map_id);
11+
12+
let client = ClientBuilder::new()
13+
.timeout(std::time::Duration::from_secs(10))
14+
.use_rustls_tls()
15+
.build()
16+
.unwrap_or_else(|e| {
17+
warn!(
18+
"Failed to build custom HTTP client with rustls: {}, using default",
19+
e
20+
);
21+
Client::new()
22+
});
23+
let response = client.get(url).send().await.unwrap();
24+
let body = response.text().await.unwrap();
25+
tracing::info!("{}", body);
26+
let map_response: MapResponse = serde_json::from_str(&body).unwrap();
27+
28+
Ok(map_response)
29+
}
30+
31+
pub async fn get_map_cached(map_id: String, state: &AppState) -> Result<MapResponse> {
32+
let cache = state.cache.rm_map.try_get_with(map_id.clone(), get_map(map_id)).await;
33+
34+
match cache {
35+
Ok(map_response) => Ok(map_response),
36+
Err(e) => {
37+
tracing::error!("Failed to get cached map response: {}", e);
38+
let err: poem::Error = poem::Error::from_string(e.to_string(), poem::http::StatusCode::INTERNAL_SERVER_ERROR);
39+
Err(err)
40+
}
41+
}
42+
}

app/src/models/rm/mod.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use poem_openapi::Object;
2+
use serde::{Deserialize, Serialize};
3+
4+
pub mod map;
5+
pub mod search;
6+
7+
#[derive(Debug, Clone, Serialize, Deserialize, Object)]
8+
pub struct SearchResponse {
9+
pub meta: SearchMeta,
10+
pub data: Vec<Server>,
11+
}
12+
13+
#[derive(Debug, Clone, Serialize, Deserialize, Object)]
14+
pub struct SearchMeta {
15+
pub status: String,
16+
#[serde(rename = "statusCode")]
17+
pub status_code: u32,
18+
}
19+
20+
#[derive(Debug, Clone, Serialize, Deserialize, Object)]
21+
pub struct Server {
22+
pub name: String,
23+
#[serde(rename = "mapId")]
24+
pub map_id: String,
25+
pub ip: String,
26+
#[serde(rename = "gamePort")]
27+
pub game_port: u16,
28+
#[serde(rename = "lastWipeUtc")]
29+
pub last_wipe_utc: String,
30+
}
31+
32+
#[derive(Debug, Clone, Serialize, Deserialize, Object)]
33+
pub struct MapResponse {
34+
pub meta: MapMeta,
35+
pub data: MapData,
36+
}
37+
38+
#[derive(Debug, Clone, Serialize, Deserialize, Object)]
39+
pub struct MapMeta {
40+
pub status: String,
41+
#[serde(rename = "statusCode")]
42+
pub status_code: u32,
43+
}
44+
45+
#[derive(Debug, Clone, Serialize, Deserialize, Object)]
46+
pub struct MapData {
47+
pub id: String,
48+
#[serde(rename = "type")]
49+
pub _type: String,
50+
pub seed: u64,
51+
pub size: u32,
52+
#[serde(rename = "saveVersion")]
53+
pub save_version: u32,
54+
#[serde(rename = "imageUrl")]
55+
pub image_url: String,
56+
#[serde(rename = "titleBaseUrl")]
57+
pub title_base_url: Option<String>,
58+
#[serde(rename = "imageIconUrl")]
59+
pub image_icon_url: Option<String>,
60+
#[serde(rename = "thumbnailUrl")]
61+
pub thumbnail_url: Option<String>,
62+
#[serde(rename = "undergroundOverlayUrl")]
63+
pub underground_overlay_url: Option<String>,
64+
#[serde(rename = "buildingBlockAreaUrl")]
65+
pub building_block_area_url: Option<String>,
66+
#[serde(rename = "isStaging")]
67+
pub is_staging: bool,
68+
#[serde(rename = "isCustomMap")]
69+
pub is_custom_map: bool,
70+
#[serde(rename = "isForSale")]
71+
pub is_for_sale: bool,
72+
#[serde(rename = "isFeatured")]
73+
pub is_featured: bool,
74+
#[serde(rename = "hasCustomMonuments")]
75+
pub has_custom_monuments: bool,
76+
#[serde(rename = "canDownload")]
77+
pub can_download: bool,
78+
#[serde(rename = "downloadUrl")]
79+
pub download_url: Option<String>,
80+
pub slug: Option<String>,
81+
pub monuments: Vec<serde_json::Value>,
82+
83+
#[serde(flatten)]
84+
pub extra: serde_json::Value,
85+
}

app/src/models/rm/search.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use poem::Result;
2+
use reqwest::ClientBuilder;
3+
use reqwest::Client;
4+
use tracing::{error, info, warn};
5+
6+
use crate::state::AppState;
7+
8+
use super::SearchResponse;
9+
10+
pub async fn search_maps(query: String) -> Result<SearchResponse> {
11+
info!("Searching for maps: {}", query);
12+
13+
let url = format!(
14+
"https://api.rustmaps.com/internal/v1/servers/search?input={}",
15+
query
16+
);
17+
18+
let client = ClientBuilder::new()
19+
.timeout(std::time::Duration::from_secs(10))
20+
.use_rustls_tls()
21+
.build()
22+
.unwrap_or_else(|e| {
23+
warn!(
24+
"Failed to build custom HTTP client with rustls: {}, using default",
25+
e
26+
);
27+
Client::new()
28+
});
29+
let response = client.get(url).send().await.unwrap();
30+
let body = response.text().await.unwrap();
31+
tracing::info!("{}", body);
32+
let search_response: SearchResponse = serde_json::from_str(&body).unwrap();
33+
34+
Ok(search_response)
35+
}
36+
37+
pub async fn search_maps_cached(query: String, state: &AppState) -> Result<SearchResponse> {
38+
let cache = state.cache.rm_search.try_get_with(query.clone(), search_maps(query)).await;
39+
40+
match cache {
41+
Ok(search_response) => Ok(search_response),
42+
Err(e) => {
43+
error!("Failed to get cached search response: {}", e);
44+
let err: poem::Error = poem::Error::from_string(e.to_string(), poem::http::StatusCode::INTERNAL_SERVER_ERROR);
45+
Err(err)
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)