fix: app cache and other issues (#5460)
* fixes * #[serde(untagged)] my BEHATED (still kinda broken) * remove unused hasContent ref * clean up code in fetch instance * ping 3 times for average latency * fix: pinging to be more accurate TCP_NODELAY — Set on the TCP stream right after connect, preventing Nagle's algorithm from buffering the small ping packet (could save up to ~40ms) Instant over Utc::now() — Switched to monotonic std::time::Instant for timing, which is more precise and designed for measuring elapsed time (still using chrono just for the ping magic value) * delete useFetch util and just use native fetch * rename worlds until functions for more clarity * fix lint * fix cache.rs logic * make backend ping use both impls * Add optional timeout to server ping * fix gallery appearing in nav with no items * remove EU countries and add EU option for server country * add uk to europe --------- Co-authored-by: aecsocket <aecsocket@tutanota.com>
This commit is contained in:
@@ -15,7 +15,7 @@ use std::path::{Path, PathBuf};
|
||||
// 1 day
|
||||
const DEFAULT_ID: &str = "0";
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CacheValueType {
|
||||
Project,
|
||||
@@ -131,37 +131,41 @@ impl CacheValueType {
|
||||
}
|
||||
}
|
||||
|
||||
// De/serialization strategy:
|
||||
// - on serialize:
|
||||
// - in the `cache` table, save the `data_type` (variant of this value) alongside
|
||||
// the data
|
||||
// - data column contains the serialized form of the INNER value (i.e. for a
|
||||
// `CacheValue::Project`, we serialize it as a `Project,` NOT as a `CacheValue`)
|
||||
// - this way, we do not tag the data using serde in any way
|
||||
// - on deserialize:
|
||||
// - use the `data_type` to figure out what type of value to deser as
|
||||
// - then wrap that in a `CacheValue`
|
||||
//
|
||||
// do NOT use `#[serde(untagged)]` here, since then a value of one variant can be
|
||||
// deser'd as a value of another variant, if it comes before it in the enum
|
||||
// definition list.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum CacheValue {
|
||||
Project(Project),
|
||||
|
||||
ProjectV3(ProjectV3),
|
||||
|
||||
Version(Version),
|
||||
|
||||
User(User),
|
||||
|
||||
Team(Vec<TeamMember>),
|
||||
|
||||
Organization(Organization),
|
||||
|
||||
File(CachedFile),
|
||||
|
||||
LoaderManifest(CachedLoaderManifest),
|
||||
MinecraftManifest(daedalus::minecraft::VersionManifest),
|
||||
|
||||
Categories(Vec<Category>),
|
||||
ReportTypes(Vec<String>),
|
||||
Loaders(Vec<Loader>),
|
||||
GameVersions(Vec<GameVersion>),
|
||||
DonationPlatforms(Vec<DonationPlatform>),
|
||||
|
||||
FileHash(CachedFileHash),
|
||||
FileUpdate(CachedFileUpdate),
|
||||
SearchResults(SearchResults),
|
||||
SearchResultsV3(SearchResultsV3),
|
||||
ProjectV3(ProjectV3),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
@@ -566,6 +570,47 @@ impl CacheValue {
|
||||
| CacheValue::SearchResultsV3(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_json_value(&self) -> crate::Result<serde_json::Value> {
|
||||
let value = match self {
|
||||
CacheValue::Project(project) => serde_json::to_value(project),
|
||||
CacheValue::ProjectV3(project) => serde_json::to_value(project),
|
||||
CacheValue::Version(version) => serde_json::to_value(version),
|
||||
CacheValue::User(user) => serde_json::to_value(user),
|
||||
CacheValue::Team(members) => serde_json::to_value(members),
|
||||
CacheValue::Organization(org) => serde_json::to_value(org),
|
||||
CacheValue::File(file) => serde_json::to_value(file),
|
||||
CacheValue::LoaderManifest(loader) => serde_json::to_value(loader),
|
||||
CacheValue::MinecraftManifest(manifest) => {
|
||||
serde_json::to_value(manifest)
|
||||
}
|
||||
CacheValue::Categories(categories) => {
|
||||
serde_json::to_value(categories)
|
||||
}
|
||||
CacheValue::ReportTypes(report_types) => {
|
||||
serde_json::to_value(report_types)
|
||||
}
|
||||
CacheValue::Loaders(loaders) => serde_json::to_value(loaders),
|
||||
CacheValue::GameVersions(versions) => {
|
||||
serde_json::to_value(versions)
|
||||
}
|
||||
CacheValue::DonationPlatforms(platforms) => {
|
||||
serde_json::to_value(platforms)
|
||||
}
|
||||
CacheValue::FileHash(hash) => serde_json::to_value(hash),
|
||||
CacheValue::FileUpdate(update) => serde_json::to_value(update),
|
||||
CacheValue::SearchResults(search) => serde_json::to_value(search),
|
||||
CacheValue::SearchResultsV3(search) => serde_json::to_value(search),
|
||||
}
|
||||
.map_err(|err| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Failed to serialize cache value: {err}"
|
||||
))
|
||||
.as_error()
|
||||
})?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
@@ -759,15 +804,11 @@ impl CachedEntry {
|
||||
.await?;
|
||||
|
||||
for row in query {
|
||||
let row_exists = row.data.is_some();
|
||||
let parsed_data = row
|
||||
.data
|
||||
.and_then(|x| serde_json::from_value::<CacheValue>(x).ok());
|
||||
|
||||
// If data is corrupted/failed to parse ignore it
|
||||
if row_exists && parsed_data.is_none() {
|
||||
continue;
|
||||
}
|
||||
let parsed_data = if let Some(data) = row.data.clone() {
|
||||
Some(Self::deserialize_cache_value(type_, data, &row.id)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if row.expires <= Utc::now().timestamp() {
|
||||
if cache_behaviour == CacheBehaviour::MustRevalidate {
|
||||
@@ -789,6 +830,16 @@ impl CachedEntry {
|
||||
});
|
||||
|
||||
if let Some(data) = parsed_data {
|
||||
if data.get_type() != type_ {
|
||||
return Err(crate::ErrorKind::OtherError(format!(
|
||||
"Cache type mismatch for id {}: expected {:?}, got {:?}",
|
||||
row.id,
|
||||
type_,
|
||||
data.get_type()
|
||||
))
|
||||
.as_error());
|
||||
}
|
||||
|
||||
return_vals.push(Self {
|
||||
id: row.id,
|
||||
alias: row.alias,
|
||||
@@ -1509,11 +1560,102 @@ impl CachedEntry {
|
||||
})
|
||||
}
|
||||
|
||||
fn deserialize_cache_value(
|
||||
type_: CacheValueType,
|
||||
data: serde_json::Value,
|
||||
id: &str,
|
||||
) -> crate::Result<CacheValue> {
|
||||
fn parse<T: DeserializeOwned>(
|
||||
data: serde_json::Value,
|
||||
id: &str,
|
||||
label: &str,
|
||||
) -> crate::Result<T> {
|
||||
serde_json::from_value::<T>(data.clone()).map_err(|err| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Failed to deserialize cache {label} for id {id}: {err}\n\ndata:\n{}",
|
||||
serde_json::to_string_pretty(&data).unwrap(),
|
||||
))
|
||||
.as_error()
|
||||
})
|
||||
}
|
||||
|
||||
let value = match type_ {
|
||||
CacheValueType::Project => {
|
||||
CacheValue::Project(parse(data, id, "project")?)
|
||||
}
|
||||
CacheValueType::ProjectV3 => {
|
||||
CacheValue::ProjectV3(parse(data, id, "project_v3")?)
|
||||
}
|
||||
CacheValueType::Version => {
|
||||
CacheValue::Version(parse(data, id, "version")?)
|
||||
}
|
||||
CacheValueType::User => CacheValue::User(parse(data, id, "user")?),
|
||||
CacheValueType::Team => CacheValue::Team(parse(data, id, "team")?),
|
||||
CacheValueType::Organization => {
|
||||
CacheValue::Organization(parse(data, id, "organization")?)
|
||||
}
|
||||
CacheValueType::File => CacheValue::File(parse(data, id, "file")?),
|
||||
CacheValueType::LoaderManifest => {
|
||||
CacheValue::LoaderManifest(parse(data, id, "loader_manifest")?)
|
||||
}
|
||||
CacheValueType::MinecraftManifest => CacheValue::MinecraftManifest(
|
||||
parse(data, id, "minecraft_manifest")?,
|
||||
),
|
||||
CacheValueType::Categories => {
|
||||
CacheValue::Categories(parse(data, id, "categories")?)
|
||||
}
|
||||
CacheValueType::ReportTypes => {
|
||||
CacheValue::ReportTypes(parse(data, id, "report_types")?)
|
||||
}
|
||||
CacheValueType::Loaders => {
|
||||
CacheValue::Loaders(parse(data, id, "loaders")?)
|
||||
}
|
||||
CacheValueType::GameVersions => {
|
||||
CacheValue::GameVersions(parse(data, id, "game_versions")?)
|
||||
}
|
||||
CacheValueType::DonationPlatforms => CacheValue::DonationPlatforms(
|
||||
parse(data, id, "donation_platforms")?,
|
||||
),
|
||||
CacheValueType::FileHash => {
|
||||
CacheValue::FileHash(parse(data, id, "file_hash")?)
|
||||
}
|
||||
CacheValueType::FileUpdate => {
|
||||
CacheValue::FileUpdate(parse(data, id, "file_update")?)
|
||||
}
|
||||
CacheValueType::SearchResults => {
|
||||
CacheValue::SearchResults(parse(data, id, "search_results")?)
|
||||
}
|
||||
CacheValueType::SearchResultsV3 => CacheValue::SearchResultsV3(
|
||||
parse(data, id, "search_results_v3")?,
|
||||
),
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub(crate) async fn upsert_many(
|
||||
items: &[Self],
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
||||
) -> crate::Result<()> {
|
||||
let items = serde_json::to_string(items)?;
|
||||
let items = items
|
||||
.iter()
|
||||
.map(|item| {
|
||||
let data = item
|
||||
.data
|
||||
.as_ref()
|
||||
.map(|value| value.to_json_value())
|
||||
.transpose()?;
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"id": item.id,
|
||||
"data_type": item.type_.as_str(),
|
||||
"alias": item.alias,
|
||||
"data": data,
|
||||
"expires": item.expires,
|
||||
}))
|
||||
})
|
||||
.collect::<crate::Result<Vec<_>>>()?;
|
||||
let items = serde_json::to_string(&items)?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
|
||||
Reference in New Issue
Block a user