Rewrite from TypeScript to Rust
This commit is contained in:
parent
a8b8b549d0
commit
25d71ba0f2
29 changed files with 221685 additions and 232325 deletions
218
src/steam.rs
Normal file
218
src/steam.rs
Normal file
|
@ -0,0 +1,218 @@
|
|||
use std::{collections::BTreeMap, process::Command};
|
||||
|
||||
use crate::{resource::ResourceFile, wiki::WikiCache, Error, State, REPO};
|
||||
|
||||
const SAVE_INTERVAL: u32 = 100;
|
||||
|
||||
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct SteamCache(pub BTreeMap<String, SteamCacheEntry>);
|
||||
|
||||
impl ResourceFile for SteamCache {
|
||||
const FILE_NAME: &'static str = "data/steam-game-cache.yaml";
|
||||
}
|
||||
|
||||
impl SteamCache {
|
||||
pub fn refresh(&mut self, outdated_only: bool, app_ids: Option<Vec<u32>>) -> Result<(), Error> {
|
||||
let mut i = 0;
|
||||
let app_ids: Vec<_> = app_ids.unwrap_or_else(|| {
|
||||
self.0
|
||||
.iter()
|
||||
.filter(|(_, v)| !outdated_only || v.state == State::Outdated)
|
||||
.filter_map(|(k, _)| k.parse::<u32>().ok())
|
||||
.collect()
|
||||
});
|
||||
|
||||
for app_id in app_ids {
|
||||
let latest = SteamCacheEntry::fetch_from_id(app_id)?;
|
||||
self.0.insert(app_id.to_string(), latest);
|
||||
|
||||
i += 1;
|
||||
if i % SAVE_INTERVAL == 0 {
|
||||
self.save();
|
||||
println!("\n:: saved\n");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn transition_states_from(&mut self, wiki_cache: &mut WikiCache) {
|
||||
for wiki in wiki_cache.0.values_mut() {
|
||||
if wiki.state == State::Updated {
|
||||
if let Some(id) = wiki.steam {
|
||||
self.0
|
||||
.entry(id.to_string())
|
||||
.and_modify(|x| {
|
||||
x.state = State::Outdated;
|
||||
})
|
||||
.or_insert(SteamCacheEntry {
|
||||
state: State::Outdated,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
wiki.state = State::Handled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct SteamCacheEntry {
|
||||
#[serde(skip_serializing_if = "State::is_handled")]
|
||||
pub state: State,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub install_dir: Option<String>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub launch: Vec<Launch>,
|
||||
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub name_localized: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct Launch {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub arguments: Option<String>,
|
||||
#[serde(skip_serializing_if = "LaunchConfig::is_empty")]
|
||||
pub config: LaunchConfig,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub executable: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub r#type: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub workingdir: Option<String>,
|
||||
}
|
||||
|
||||
impl Launch {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.arguments.is_none()
|
||||
&& self.config.is_empty()
|
||||
&& self.description.is_none()
|
||||
&& self.executable.is_none()
|
||||
&& self.r#type.is_none()
|
||||
&& self.workingdir.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct LaunchConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub betakey: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub osarch: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub oslist: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ownsdlc: Option<String>,
|
||||
}
|
||||
|
||||
impl LaunchConfig {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.betakey.is_none() && self.osarch.is_none() && self.oslist.is_none() && self.ownsdlc.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
mod product_info {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, Clone, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Response {
|
||||
pub apps: BTreeMap<String, App>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct App {
|
||||
pub common: AppCommon,
|
||||
pub config: AppConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct AppCommon {
|
||||
pub name_localized: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct AppConfig {
|
||||
pub installdir: Option<String>,
|
||||
pub launch: BTreeMap<String, AppLaunch>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct AppLaunch {
|
||||
pub executable: Option<String>,
|
||||
pub arguments: Option<String>,
|
||||
pub workingdir: Option<String>,
|
||||
pub r#type: Option<String>,
|
||||
pub config: AppLaunchConfig,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct AppLaunchConfig {
|
||||
pub betakey: Option<String>,
|
||||
pub osarch: Option<String>,
|
||||
pub oslist: Option<String>,
|
||||
pub ownsdlc: Option<String>,
|
||||
}
|
||||
}
|
||||
|
||||
impl SteamCacheEntry {
|
||||
pub fn fetch_from_id(app_id: u32) -> Result<Self, Error> {
|
||||
println!("Steam: {}", app_id);
|
||||
|
||||
let mut cmd = Command::new("python");
|
||||
cmd.arg(format!("{}/scripts/get-steam-app-info.py", REPO));
|
||||
cmd.arg(app_id.to_string());
|
||||
let output = cmd.output()?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
eprintln!("Steam product info failure: {}", &stderr);
|
||||
return Err(Error::SteamProductInfo);
|
||||
}
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
let response = serde_json::from_str::<product_info::Response>(&stdout).map_err(|_| Error::SteamProductInfo)?;
|
||||
let app = response
|
||||
.apps
|
||||
.get(&app_id.to_string())
|
||||
.ok_or(Error::SteamProductInfo)?
|
||||
.clone();
|
||||
|
||||
let launch: Vec<_> = app
|
||||
.config
|
||||
.launch
|
||||
.into_values()
|
||||
.map(|x| Launch {
|
||||
executable: x.executable,
|
||||
arguments: x.arguments,
|
||||
workingdir: x.workingdir,
|
||||
r#type: x.r#type,
|
||||
description: x.description,
|
||||
config: LaunchConfig {
|
||||
betakey: x.config.betakey,
|
||||
osarch: x.config.osarch,
|
||||
oslist: x.config.oslist,
|
||||
ownsdlc: x.config.ownsdlc,
|
||||
},
|
||||
})
|
||||
.filter(|x| !x.is_empty())
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
state: State::Handled,
|
||||
install_dir: app.config.installdir,
|
||||
name_localized: app.common.name_localized,
|
||||
launch,
|
||||
})
|
||||
}
|
||||
}
|
Reference in a new issue