Add plugin stats increment endpoint support
This commit is contained in:
parent
cf2786242d
commit
7f1a001f63
7 changed files with 85 additions and 10 deletions
|
@ -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};
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
|
|
@ -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"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue