Add plugin stats increment endpoint support

This commit is contained in:
NGnius (Graham) 2023-12-30 22:41:51 -05:00
parent cf2786242d
commit 7f1a001f63
7 changed files with 85 additions and 10 deletions

View file

@ -1,3 +1,3 @@
mod store_plugin; mod store_plugin;
pub use store_plugin::{StorePlugin, StorePluginVersion, StorePluginList, StorePluginQuery, StorePluginQuerySortColumn, StorePluginQuerySortDirection}; pub use store_plugin::{StorePlugin, StorePluginVersion, StorePluginList, StorePluginQuery, StorePluginQuerySortColumn, StorePluginQuerySortDirection, StorePluginIncrement};

View file

@ -50,3 +50,9 @@ pub enum StorePluginQuerySortDirection {
#[serde(rename = "desc")] #[serde(rename = "desc")]
Descending, Descending,
} }
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct StorePluginIncrement {
#[serde(rename = "isUpdate")]
pub is_update: bool
}

View file

@ -81,6 +81,7 @@ async fn main() -> std::io::Result<()> {
.service(not_decky::decky_artifact) .service(not_decky::decky_artifact)
.service(not_decky::decky_image) .service(not_decky::decky_image)
.service(not_decky::decky_statistics) .service(not_decky::decky_statistics)
.service(not_decky::decky_statistics_increment)
}) })
.bind(("0.0.0.0", args.server_port.unwrap_or(22252)))? .bind(("0.0.0.0", args.server_port.unwrap_or(22252)))?
.run() .run()

View file

@ -8,4 +8,4 @@ pub use artifact::decky_artifact;
pub use image::decky_image; pub use image::decky_image;
pub use index::decky_index; pub use index::decky_index;
pub use plugins::decky_plugins; pub use plugins::decky_plugins;
pub use stats::decky_statistics; pub use stats::{decky_statistics, decky_statistics_increment};

View file

@ -1,12 +1,19 @@
use std::collections::HashMap; use std::collections::HashMap;
use actix_web::{get, web, Responder}; use actix_web::{get, post, web, Responder};
use crate::storage::IStorage; use crate::storage::IStorage;
#[get("/stats")] #[get("/stats")]
pub async fn decky_statistics(data: actix_web::web::Data<Box<dyn IStorage>>) -> impl Responder { pub async fn decky_statistics(data: actix_web::web::Data<Box<dyn IStorage>>) -> impl Responder {
println!("stats");
let plugins: HashMap<String, u64> = data.get_statistics(); let plugins: HashMap<String, u64> = data.get_statistics();
web::Json(plugins) web::Json(plugins)
} }
#[post("/plugins/{name}/versions/{version}")]
pub async fn decky_statistics_increment(data: actix_web::web::Data<Box<dyn IStorage>>, path: actix_web::web::Path<(String, String)>, query: actix_web::web::Query<decky_api::StorePluginIncrement>) -> actix_web::Result<impl Responder> {
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))
}

View file

@ -98,6 +98,10 @@ impl FileStorage {
if !lock.contains_key(&version.hash) { if !lock.contains_key(&version.hash) {
lock.insert(version.hash.clone(), AtomicU64::new(0)); 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 { 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 { fn plugin_stat_entry_name(plugin_name: &str) -> String {
@ -124,10 +138,18 @@ impl FileStorage {
format!("{}/plugins/{}.png", self.domain_root, plugin_name) format!("{}/plugins/{}.png", self.domain_root, plugin_name)
} }
fn get_single_version_stats(&self, plugin_name: &str, version_name: &str) -> Option<u64> { fn get_single_version_stats(&self, hash: &str) -> Option<u64> {
let lock = self.stats.as_ref()?.read().expect("Couldn't acquire stats read lock"); 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(hash) {
if let Some(stat_counter) = lock.get(&stat_entry) { Some(stat_counter.load(Ordering::SeqCst))
} else {
Some(0)
}
}
fn get_single_version_update_stats(&self, hash: &str) -> Option<u64> {
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)) Some(stat_counter.load(Ordering::SeqCst))
} else { } else {
Some(0) Some(0)
@ -156,14 +178,15 @@ impl FileStorage {
let version_name = entry_path.file_stem().unwrap().to_string_lossy().into_owned(); let version_name = entry_path.file_stem().unwrap().to_string_lossy().into_owned();
let hash_str = sha256::try_digest(&entry_path)?; let hash_str = sha256::try_digest(&entry_path)?;
let artifact_url = self.artifact_url(&plugin_name, &version_name, &hash_str); 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 { versions.push(StorePluginVersion {
name: version_name, name: version_name,
hash: hash_str, hash: hash_str,
artifact: Some(artifact_url), artifact: Some(artifact_url),
created: Some(entry.metadata()?.created()?.into()), created: Some(entry.metadata()?.created()?.into()),
downloads: downloads_stat, downloads: downloads_stat,
updates: None, // TODO what is this? updates: updates_stat,
}); });
} }
} }
@ -228,6 +251,11 @@ impl IStorage for FileStorage {
total += count_val; total += count_val;
map.insert(Self::version_stat_entry_name(&plugin.name, &version.name), 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); map.insert(Self::plugin_stat_entry_name(&plugin.name), total);
} }
@ -239,4 +267,33 @@ impl IStorage for FileStorage {
std::collections::HashMap::with_capacity(0) std::collections::HashMap::with_capacity(0)
} }
} }
fn increment_statistic(&self, name: &str, version: &str, query: &decky_api::StorePluginIncrement) -> Result<decky_api::StorePluginVersion, std::io::Error> {
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"))
}
} }

View file

@ -12,6 +12,10 @@ pub trait IStorage: Send + Sync {
fn get_statistics(&self) -> std::collections::HashMap<String, u64> { fn get_statistics(&self) -> std::collections::HashMap<String, u64> {
std::collections::HashMap::with_capacity(0) std::collections::HashMap::with_capacity(0)
} }
fn increment_statistic(&self, _name: &str, _version: &str, _query: &decky_api::StorePluginIncrement) -> Result<decky_api::StorePluginVersion, std::io::Error> {
Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Statistics increment not supported"))
}
} }
pub struct EmptyStorage; pub struct EmptyStorage;