From cf2786242da333c192a6b6cc75eabdd9b663ad70 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Sat, 30 Dec 2023 21:44:10 -0500 Subject: [PATCH] Add decky sorting query support --- .gitignore | 2 + Cargo.lock | 7 +- Cargo.toml | 5 +- decky_api/Cargo.lock | 365 ++++++++++++++++++++++++++++++++++ decky_api/Cargo.toml | 3 +- decky_api/src/lib.rs | 2 +- decky_api/src/store_plugin.rs | 31 +++ src/main.rs | 6 +- src/storage/filesystem.rs | 65 +++++- src/storage/interface.rs | 9 + src/storage/mod.rs | 4 +- src/storage/standard_sort.rs | 57 ++++++ 12 files changed, 537 insertions(+), 19 deletions(-) create mode 100644 decky_api/Cargo.lock create mode 100644 src/storage/standard_sort.rs diff --git a/.gitignore b/.gitignore index dc531fd..dda8ffb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /target +**/target /store not-decky-* +/*.sh diff --git a/Cargo.lock b/Cargo.lock index f09e47a..8631ac4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -435,6 +435,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.48.5", ] @@ -537,8 +538,9 @@ dependencies = [ [[package]] name = "decky_api" -version = "0.1.0" +version = "0.2.0" dependencies = [ + "chrono", "serde", ] @@ -899,7 +901,7 @@ dependencies = [ [[package]] name = "not-decky-store" -version = "0.1.0" +version = "0.2.0" dependencies = [ "actix-cors", "actix-web", @@ -911,6 +913,7 @@ dependencies = [ "native-tls", "serde", "serde_json", + "serde_urlencoded", "sha256", "simplelog", "ureq", diff --git a/Cargo.toml b/Cargo.toml index 43b5c56..ef4c84d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "not-decky-store" -version = "0.1.0" +version = "0.2.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -decky_api = { version = "0.1.0", path = "./decky_api" } +decky_api = { version = "0.2.0", path = "./decky_api" } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } +serde_urlencoded = "0.7" bytes = "1.3" sha256 = "1.1" diff --git a/decky_api/Cargo.lock b/decky_api/Cargo.lock new file mode 100644 index 0000000..fdd1328 --- /dev/null +++ b/decky_api/Cargo.lock @@ -0,0 +1,365 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.48.5", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "decky_api" +version = "0.2.0" +dependencies = [ + "chrono", + "serde", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro2" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a293318316cf6478ec1ad2a21c49390a8d5b5eae9fab736467d93fbc0edc29c5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/decky_api/Cargo.toml b/decky_api/Cargo.toml index f2457bb..9db0fca 100644 --- a/decky_api/Cargo.toml +++ b/decky_api/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "decky_api" -version = "0.1.0" +version = "0.2.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] serde = { version = "1.0", features = ["derive"] } +chrono = { version = "0.4", features = ["serde"] } diff --git a/decky_api/src/lib.rs b/decky_api/src/lib.rs index fceaac2..3bf2681 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}; +pub use store_plugin::{StorePlugin, StorePluginVersion, StorePluginList, StorePluginQuery, StorePluginQuerySortColumn, StorePluginQuerySortDirection}; diff --git a/decky_api/src/store_plugin.rs b/decky_api/src/store_plugin.rs index 6298068..e056d56 100644 --- a/decky_api/src/store_plugin.rs +++ b/decky_api/src/store_plugin.rs @@ -11,6 +11,10 @@ pub struct StorePlugin { pub description: String, pub tags: Vec, pub image_url: String, + pub downloads: Option, + pub updates: Option, + pub created: Option>, + pub updated: Option>, } #[derive(Serialize, Deserialize, Clone)] @@ -18,4 +22,31 @@ pub struct StorePluginVersion { pub name: String, pub hash: String, pub artifact: Option, + pub created: Option>, + pub downloads: Option, + pub updates: Option, +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct StorePluginQuery { + pub sort_by: Option, + pub sort_direction: Option, +} + +#[derive(Serialize, Deserialize, Clone)] +pub enum StorePluginQuerySortColumn { + #[serde(rename = "name")] + Name, + #[serde(rename = "date")] + Date, + #[serde(rename = "downloads")] + Downloads +} + +#[derive(Serialize, Deserialize, Clone)] +pub enum StorePluginQuerySortDirection { + #[serde(rename = "asc")] + Ascending, + #[serde(rename = "desc")] + Descending, } diff --git a/src/main.rs b/src/main.rs index c1c0922..91fd150 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,8 +42,12 @@ fn build_storage_box(storage: &cli::StorageArgs) -> Box { async fn main() -> std::io::Result<()> { let args = cli::CliArgs::get(); let log_filepath = std::path::Path::new("/tmp").join(format!("{}.log", consts::PACKAGE_NAME)); + #[cfg(debug_assertions)] + let log_level = LevelFilter::Debug; + #[cfg(not(debug_assertions))] + let log_level = LevelFilter::Info; WriteLogger::init( - LevelFilter::Debug, + log_level, Default::default(), std::fs::File::create(&log_filepath).unwrap(), ) diff --git a/src/storage/filesystem.rs b/src/storage/filesystem.rs index 8e281d2..07a9f91 100644 --- a/src/storage/filesystem.rs +++ b/src/storage/filesystem.rs @@ -9,7 +9,7 @@ use decky_api::{StorePlugin, StorePluginList, StorePluginVersion}; use serde::{Serialize, Deserialize}; -use super::IStorage; +use super::{IStorage, IQueryHandler}; #[derive(Serialize, Deserialize, Clone)] pub struct PluginMetadata { @@ -21,6 +21,10 @@ pub struct PluginMetadata { impl PluginMetadata { fn complete(self, name: String, versions: Vec, image: String) -> StorePlugin { + let downloads = versions.iter().map(|v| v.downloads.unwrap_or(0)).sum(); + let updates = versions.iter().map(|v| v.updates.unwrap_or(0)).sum(); + let created_time = versions.iter().map(|v| v.created.unwrap_or_default()).min().unwrap_or_default(); + let updated_time = versions.iter().map(|v| v.created.unwrap_or_default()).max().unwrap_or_default(); StorePlugin { id: self.id, name: name, @@ -29,6 +33,10 @@ impl PluginMetadata { description: self.description, tags: self.tags, image_url: image, + downloads: Some(downloads), + updates: Some(updates), + created: Some(created_time), + updated: Some(updated_time), } } } @@ -37,6 +45,7 @@ pub struct FileStorage { stats: Option>>, // TODO collect hit counts on actions root: PathBuf, domain_root: String, + query_handler: Box, } impl FileStorage { @@ -45,6 +54,7 @@ impl FileStorage { root: root, domain_root: domain_root, stats: if enable_stats { Some(RwLock::new(HashMap::new())) } else { None }, + query_handler: Box::new(super::StandardPostRetrievalSort), } } @@ -70,7 +80,7 @@ impl FileStorage { .join(format!("image.png")) } - fn read_all_plugins(&self) -> std::io::Result { + fn read_all_plugins(&self, query: Option<&str>) -> std::io::Result { let plugins = self.plugins_path(); let dir_reader = plugins.read_dir()?; let mut results = Vec::with_capacity(dir_reader.size_hint().1.unwrap_or(32)); @@ -91,9 +101,39 @@ impl FileStorage { } } } + if let Some(query) = query { + let relevant_params = self.query_handler.supported_params(query); + results = self.query_handler.filter(&relevant_params, results); + } Ok(results) } + fn version_stat_entry_name(plugin_name: &str, version_name: &str) -> String { + format!("{} {}", plugin_name, version_name) + } + + fn plugin_stat_entry_name(plugin_name: &str) -> String { + format!("{}", plugin_name) + } + + fn artifact_url(&self, plugin_name: &str, version_name: &str, hash_str: &str) -> String { + format!("{}/plugins/{}/{}/{}.zip", self.domain_root, plugin_name, version_name, hash_str) + } + + fn image_url(&self, plugin_name: &str) -> String { + format!("{}/plugins/{}.png", self.domain_root, plugin_name) + } + + fn get_single_version_stats(&self, plugin_name: &str, version_name: &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) { + Some(stat_counter.load(Ordering::SeqCst)) + } else { + Some(0) + } + } + fn read_single_plugin(&self, path: &PathBuf) -> std::io::Result { let plugin_name = path.file_name().unwrap().to_string_lossy().into_owned(); let json_path = self.plugin_json_path(path); @@ -115,17 +155,21 @@ impl FileStorage { if extension == "zip" { let version_name = entry_path.file_stem().unwrap().to_string_lossy().into_owned(); let hash_str = sha256::try_digest(&entry_path)?; - let artifact_url = format!("{}/plugins/{}/{}/{}.zip", self.domain_root, 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); versions.push(StorePluginVersion { name: version_name, hash: hash_str, - artifact: Some(artifact_url) + artifact: Some(artifact_url), + created: Some(entry.metadata()?.created()?.into()), + downloads: downloads_stat, + updates: None, // TODO what is this? }); } } } versions.sort_by(|a, b| b.name.cmp(&a.name)); // sort e.g. v2 before v1 - let image_url = format!("{}/plugins/{}.png", self.domain_root, plugin_name); + let image_url = self.image_url(&plugin_name); Ok( plugin_info.complete( plugin_name, @@ -137,9 +181,8 @@ impl FileStorage { } impl IStorage for FileStorage { - fn plugins(&self, _query: &str) -> StorePluginList { - // TODO handle query string - match self.read_all_plugins() { + fn plugins(&self, query: &str) -> StorePluginList { + match self.read_all_plugins(Some(query)) { Err(e) => { log::error!("Plugins read error: {}", e); vec![] @@ -174,7 +217,7 @@ impl IStorage for FileStorage { fn get_statistics(&self) -> std::collections::HashMap { if let Some(stats) = &self.stats { - if let Ok(plugins) = self.read_all_plugins() { + if let Ok(plugins) = self.read_all_plugins(None) { let lock = stats.read().expect("Failed to acquire stats read lock"); let mut map = std::collections::HashMap::with_capacity(lock.len()); for plugin in plugins { @@ -183,10 +226,10 @@ impl IStorage for FileStorage { if let Some(count) = lock.get(&version.hash) { let count_val = count.load(Ordering::SeqCst); total += count_val; - map.insert(format!("{} {}", plugin.name, version.name), count_val); + map.insert(Self::version_stat_entry_name(&plugin.name, &version.name), count_val); } } - map.insert(format!("{}", plugin.name), total); + map.insert(Self::plugin_stat_entry_name(&plugin.name), total); } map } else { diff --git a/src/storage/interface.rs b/src/storage/interface.rs index 966581c..ce4d484 100644 --- a/src/storage/interface.rs +++ b/src/storage/interface.rs @@ -21,3 +21,12 @@ impl IStorage for EmptyStorage { Vec::new() } } + +pub trait IQueryHandler: Send + Sync { + fn filter(&self, query: &str, plugins: decky_api::StorePluginList) -> decky_api::StorePluginList; + + /// Filter out recognized query parameters from query string, returning only those that the query handler can use in IQueryHandler::filter + fn supported_params(&self, query: &str) -> String { + query.to_owned() + } +} diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 1c6128d..f974697 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -3,9 +3,11 @@ mod filesystem; mod interface; mod merge; mod proxy; +mod standard_sort; pub use cache::CachedStorage; pub use filesystem::FileStorage; -pub use interface::{IStorage, EmptyStorage}; +pub use interface::{IStorage, EmptyStorage, IQueryHandler}; pub use merge::MergedStorage; pub use proxy::ProxiedStorage; +pub use standard_sort::StandardPostRetrievalSort; diff --git a/src/storage/standard_sort.rs b/src/storage/standard_sort.rs new file mode 100644 index 0000000..c856649 --- /dev/null +++ b/src/storage/standard_sort.rs @@ -0,0 +1,57 @@ +use decky_api::{StorePluginQuery, StorePluginQuerySortColumn, StorePluginQuerySortDirection}; + +use super::IQueryHandler; + +pub struct StandardPostRetrievalSort; + +impl IQueryHandler for StandardPostRetrievalSort { + fn filter(&self, query: &str, mut plugins: decky_api::StorePluginList) -> decky_api::StorePluginList { + log::debug!("StandardPostRetrievalSort::filter got query string `{}`", query); + let query: StorePluginQuery = match serde_urlencoded::from_str(query) { + Ok(q) => q, + Err(e) => { + log::error!("Failed to parse query string {}: {}", query, e); + Default::default() + } + }; + match (query.sort_by, query.sort_direction) { + (None, _) => plugins, + (Some(StorePluginQuerySortColumn::Name), direction) => { + plugins.sort_by_key(|p| p.name.to_lowercase()); + match direction { + None | Some(StorePluginQuerySortDirection::Ascending) => {}, + Some(StorePluginQuerySortDirection::Descending) => plugins.reverse(), + } + plugins + }, + (Some(StorePluginQuerySortColumn::Date), direction) => { + plugins.sort_by_key( + |p| p.versions.iter() + .max_by_key( + |v| v.created.unwrap_or_default()) + .map(|v| v.created.unwrap_or_default()) + ); + match direction { + None | Some(StorePluginQuerySortDirection::Ascending) => {}, + Some(StorePluginQuerySortDirection::Descending) => plugins.reverse(), + } + plugins + }, + (Some(StorePluginQuerySortColumn::Downloads), direction) => { + plugins.sort_by_key(|p| p.versions.iter().map(|v| v.downloads.unwrap_or(0)).sum::()); + match direction { + None | Some(StorePluginQuerySortDirection::Ascending) => {}, + Some(StorePluginQuerySortDirection::Descending) => plugins.reverse(), + } + plugins + }, + } + } + + fn supported_params(&self, query: &str) -> String { + match serde_urlencoded::from_str::(query) { + Ok(q) => serde_urlencoded::to_string(&q).unwrap_or_default(), + Err(_) => "".to_owned() + } + } +}