From 7f1a001f63be20444f75034094848fb9dc2ba054 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Sat, 30 Dec 2023 22:41:51 -0500 Subject: [PATCH] Add plugin stats increment endpoint support --- decky_api/src/lib.rs | 2 +- decky_api/src/store_plugin.rs | 6 +++ src/main.rs | 1 + src/not_decky/mod.rs | 2 +- src/not_decky/stats.rs | 11 +++++- src/storage/filesystem.rs | 69 ++++++++++++++++++++++++++++++++--- src/storage/interface.rs | 4 ++ 7 files changed, 85 insertions(+), 10 deletions(-) diff --git a/decky_api/src/lib.rs b/decky_api/src/lib.rs index 3bf2681..5b7b49a 100644 --- a/decky_api/src/lib.rs +++ b/decky_api/src/lib.rs @@ -1,3 +1,3 @@ mod store_plugin; -pub use store_plugin::{StorePlugin, StorePluginVersion, StorePluginList, StorePluginQuery, StorePluginQuerySortColumn, StorePluginQuerySortDirection}; +pub use store_plugin::{StorePlugin, StorePluginVersion, StorePluginList, StorePluginQuery, StorePluginQuerySortColumn, StorePluginQuerySortDirection, StorePluginIncrement}; diff --git a/decky_api/src/store_plugin.rs b/decky_api/src/store_plugin.rs index e056d56..1c12eb3 100644 --- a/decky_api/src/store_plugin.rs +++ b/decky_api/src/store_plugin.rs @@ -50,3 +50,9 @@ pub enum StorePluginQuerySortDirection { #[serde(rename = "desc")] Descending, } + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct StorePluginIncrement { + #[serde(rename = "isUpdate")] + pub is_update: bool +} diff --git a/src/main.rs b/src/main.rs index 91fd150..245a851 100644 --- a/src/main.rs +++ b/src/main.rs @@ -81,6 +81,7 @@ async fn main() -> std::io::Result<()> { .service(not_decky::decky_artifact) .service(not_decky::decky_image) .service(not_decky::decky_statistics) + .service(not_decky::decky_statistics_increment) }) .bind(("0.0.0.0", args.server_port.unwrap_or(22252)))? .run() diff --git a/src/not_decky/mod.rs b/src/not_decky/mod.rs index b359f42..71fa56c 100644 --- a/src/not_decky/mod.rs +++ b/src/not_decky/mod.rs @@ -8,4 +8,4 @@ pub use artifact::decky_artifact; pub use image::decky_image; pub use index::decky_index; pub use plugins::decky_plugins; -pub use stats::decky_statistics; +pub use stats::{decky_statistics, decky_statistics_increment}; diff --git a/src/not_decky/stats.rs b/src/not_decky/stats.rs index 66aa6da..8f8ab7e 100644 --- a/src/not_decky/stats.rs +++ b/src/not_decky/stats.rs @@ -1,12 +1,19 @@ use std::collections::HashMap; -use actix_web::{get, web, Responder}; +use actix_web::{get, post, web, Responder}; use crate::storage::IStorage; #[get("/stats")] pub async fn decky_statistics(data: actix_web::web::Data>) -> impl Responder { - println!("stats"); let plugins: HashMap = data.get_statistics(); web::Json(plugins) } + + +#[post("/plugins/{name}/versions/{version}")] +pub async fn decky_statistics_increment(data: actix_web::web::Data>, path: actix_web::web::Path<(String, String)>, query: actix_web::web::Query) -> actix_web::Result { + let new_version_info = data.increment_statistic(&path.0, &path.1, &*query) + .map_err(|e| actix_web::error::ErrorNotFound(e.to_string()))?; + Ok(web::Json(new_version_info)) +} diff --git a/src/storage/filesystem.rs b/src/storage/filesystem.rs index 07a9f91..b3d190c 100644 --- a/src/storage/filesystem.rs +++ b/src/storage/filesystem.rs @@ -98,6 +98,10 @@ impl FileStorage { if !lock.contains_key(&version.hash) { lock.insert(version.hash.clone(), AtomicU64::new(0)); } + let update_key = Self::version_update_name(&version.hash); + if !lock.contains_key(&update_key) { + lock.insert(update_key, AtomicU64::new(0)); + } } } } @@ -109,7 +113,17 @@ impl FileStorage { } fn version_stat_entry_name(plugin_name: &str, version_name: &str) -> String { - format!("{} {}", plugin_name, version_name) + format!("{} {} install", plugin_name, version_name) + } + + /// External stat for updates of a specific version + fn version_stat_update_entry_name(plugin_name: &str, version_name: &str) -> String { + format!("{} {} update", plugin_name, version_name) + } + + /// Internal counter key for update stat of a specific version + fn version_update_name(hash: &str) -> String { + format!("{}-update", hash) } fn plugin_stat_entry_name(plugin_name: &str) -> String { @@ -124,10 +138,18 @@ impl FileStorage { format!("{}/plugins/{}.png", self.domain_root, plugin_name) } - fn get_single_version_stats(&self, plugin_name: &str, version_name: &str) -> Option { + fn get_single_version_stats(&self, hash: &str) -> Option { let lock = self.stats.as_ref()?.read().expect("Couldn't acquire stats read lock"); - let stat_entry = Self::version_stat_entry_name(plugin_name, version_name); - if let Some(stat_counter) = lock.get(&stat_entry) { + if let Some(stat_counter) = lock.get(hash) { + Some(stat_counter.load(Ordering::SeqCst)) + } else { + Some(0) + } + } + + fn get_single_version_update_stats(&self, hash: &str) -> Option { + let lock = self.stats.as_ref()?.read().expect("Couldn't acquire stats read lock"); + if let Some(stat_counter) = lock.get(&Self::version_update_name(hash)) { Some(stat_counter.load(Ordering::SeqCst)) } else { Some(0) @@ -156,14 +178,15 @@ impl FileStorage { let version_name = entry_path.file_stem().unwrap().to_string_lossy().into_owned(); let hash_str = sha256::try_digest(&entry_path)?; let artifact_url = self.artifact_url(&plugin_name, &version_name, &hash_str); - let downloads_stat = self.get_single_version_stats(&plugin_name, &version_name); + let downloads_stat = self.get_single_version_stats(&hash_str); + let updates_stat = self.get_single_version_update_stats(&hash_str); versions.push(StorePluginVersion { name: version_name, hash: hash_str, artifact: Some(artifact_url), created: Some(entry.metadata()?.created()?.into()), downloads: downloads_stat, - updates: None, // TODO what is this? + updates: updates_stat, }); } } @@ -228,6 +251,11 @@ impl IStorage for FileStorage { total += count_val; map.insert(Self::version_stat_entry_name(&plugin.name, &version.name), count_val); } + if let Some(count) = lock.get(&Self::version_update_name(&version.hash)) { + let count_val = count.load(Ordering::SeqCst); + total += count_val; + map.insert(Self::version_stat_update_entry_name(&plugin.name, &version.name), count_val); + } } map.insert(Self::plugin_stat_entry_name(&plugin.name), total); } @@ -239,4 +267,33 @@ impl IStorage for FileStorage { std::collections::HashMap::with_capacity(0) } } + + fn increment_statistic(&self, name: &str, version: &str, query: &decky_api::StorePluginIncrement) -> Result { + if let Some(stats) = &self.stats { + // to find the correct stats counter, match up name and version to find hash + if let Ok(plugins) = self.read_all_plugins(None) { + if let Some(plugin) = plugins.into_iter().filter(|p| p.name == name).next() { + if let Some(mut version) = plugin.versions.into_iter().filter(|v| v.name == version).next() { + let hash = &version.hash; + let lock = stats.read().expect("Failed to acquire stats read lock"); + if query.is_update { + let key = Self::version_update_name(hash); + if let Some(counter) = lock.get(&key) { + counter.fetch_add(1, Ordering::SeqCst); + version.updates = version.updates.map(|x| x + 1); + return Ok(version) + } + } else { + if let Some(counter) = lock.get(hash) { + counter.fetch_add(1, Ordering::SeqCst); + version.downloads = version.downloads.map(|x| x + 1); + return Ok(version); + } + } + } + } + } + } + Err(std::io::Error::new(std::io::ErrorKind::NotFound, "Plugin version not found")) + } } diff --git a/src/storage/interface.rs b/src/storage/interface.rs index ce4d484..d32308b 100644 --- a/src/storage/interface.rs +++ b/src/storage/interface.rs @@ -12,6 +12,10 @@ pub trait IStorage: Send + Sync { fn get_statistics(&self) -> std::collections::HashMap { std::collections::HashMap::with_capacity(0) } + + fn increment_statistic(&self, _name: &str, _version: &str, _query: &decky_api::StorePluginIncrement) -> Result { + Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Statistics increment not supported")) + } } pub struct EmptyStorage;