From 7755b39fe5ee87bc9ced38821f221250a56b88ba Mon Sep 17 00:00:00 2001 From: Kubat <mael.martin31@gmail.com> Date: Thu, 26 Jan 2023 18:35:35 +0100 Subject: [PATCH] AMALIB: Add base things for the cache on client-side --- src/rust/Cargo.lock | 2 + src/rust/Cargo.toml | 1 + src/rust/amalib/Cargo.toml | 1 + src/rust/amalib/src/{amacache.rs => cache.rs} | 87 ++++++------- src/rust/amalib/src/db_objects.rs | 114 ++++++++++++++++++ src/rust/amalib/src/lib.rs | 8 +- src/rust/amalib/src/tags.rs | 89 ++++++++++++++ 7 files changed, 252 insertions(+), 50 deletions(-) rename src/rust/amalib/src/{amacache.rs => cache.rs} (55%) create mode 100644 src/rust/amalib/src/db_objects.rs create mode 100644 src/rust/amalib/src/tags.rs diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 9b67a7a8..93fb1fba 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -30,6 +30,7 @@ version = "0.1.0" dependencies = [ "commons", "getset", + "hashbrown 0.13.2", "lru", "serde", "smallstring", @@ -315,6 +316,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ "ahash", + "serde", ] [[package]] diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 79db8e61..dfe7552c 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -32,6 +32,7 @@ lazy_static = "^1" thiserror = "^1" smallvec = { version = "^1", default-features = false, features = ["serde"] } +hashbrown = { version = "^0.13", features = ["serde"] } toml = { version = "^0.5", features = ["preserve_order"] } serde = { version = "^1", default-features = false, features = [ diff --git a/src/rust/amalib/Cargo.toml b/src/rust/amalib/Cargo.toml index 3c68c021..bbd3b6c1 100644 --- a/src/rust/amalib/Cargo.toml +++ b/src/rust/amalib/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true [dependencies] serde.workspace = true tokio.workspace = true +hashbrown.workspace = true smallstring = { path = "../smallstring" } commons = { path = "../commons" } diff --git a/src/rust/amalib/src/amacache.rs b/src/rust/amalib/src/cache.rs similarity index 55% rename from src/rust/amalib/src/amacache.rs rename to src/rust/amalib/src/cache.rs index 1b1ad27f..f86ec433 100644 --- a/src/rust/amalib/src/amacache.rs +++ b/src/rust/amalib/src/cache.rs @@ -1,45 +1,9 @@ +use crate::*; +use hashbrown::HashMap; use lru::LruCache; use serde::{Deserialize, Serialize}; use smallstring::SmallString; -use std::{collections::HashMap, num::NonZeroUsize}; - -/// Represent a global id for a kara, the pair (database, id). -#[derive(Debug, Deserialize, Serialize)] -pub struct KaraId { - pub repo: SmallString, - pub id: i64, -} - -/// Represent what we want to override in a kara. -#[derive(Debug, Deserialize, Serialize)] -pub struct KaraOverride {} - -/// A list of informations about a kara. This is the data from lektor. When -/// queried we patch it with the overrides given by the user. -#[derive(Debug)] -pub struct KaraInfo {} - -#[allow(dead_code)] -#[derive(Debug)] -pub struct Playlist { - name: SmallString, - ctime: i64, - creator: SmallString, - content: Vec<i64>, -} - -#[allow(dead_code)] -#[derive(Debug, Deserialize, Serialize)] -#[serde(transparent)] -pub struct AmaDB { - /// Contains the content of the queue. - #[serde(skip)] - queue: Vec<i64>, - - /// We cache the database so we don't have to make requests every time we - /// refresh the queue or a playlist. - databases: AmaDBCache, -} +use std::num::NonZeroUsize; /// We cache objects locally so we don't have to always make requests to /// lektord. When we get the update stored database event from mpv we clear the @@ -47,14 +11,13 @@ pub struct AmaDB { /// it's the database, we clear everything. #[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] -struct AmaDBCache { - /// Cache requests to lektord. - #[serde(skip, default = "new_empty_kara_lru")] - lru_lektor: LruCache<i64, KaraInfo>, - - /// Cache requests to lektord that where patched. +pub struct AmaDB { + /// Cache requests to lektord and patched karas. The first element is the + /// lektord entry, the second one is the patched version. + /// TODO: Find a structure that does COW to not duplicate too much + /// information in the patched version... #[serde(skip, default = "new_empty_kara_lru")] - lru_patched: LruCache<i64, KaraInfo>, + karas: LruCache<i64, (KaraInfo, Option<KaraInfo>)>, /// The playlists. We don't save them... #[serde(skip, default = "new_empty_playlist_lru")] @@ -63,7 +26,7 @@ struct AmaDBCache { /// The list of overrides, indexed by the local id of the kara. overrides: HashMap<i64, KaraOverride>, - /// The conversion list for local id <-> repo id. Overrides are declared + /// The conversion list for `local id <-> repo id`. Overrides are declared /// with the repo ids because it's the only thing that every client have in /// common. We must adapt to the local id. local_ids: HashMap<i64, KaraId>, @@ -71,7 +34,7 @@ struct AmaDBCache { /// Create a new empty LRU cache. We set the value to the maximal count of /// visible karas multiplied by a threeshold. -fn new_empty_kara_lru() -> LruCache<i64, KaraInfo> { +fn new_empty_kara_lru() -> LruCache<i64, (KaraInfo, Option<KaraInfo>)> { use crate::constants::*; LruCache::new( NonZeroUsize::new( @@ -108,4 +71,32 @@ impl AmaDB { pub fn clear_playlists_cache(&mut self) { todo!() } + + /// Add an override to the override list. If a previous override is present + /// for the specified ID, we merge the new override into the previous one. + /// If the kara is present in the cache, we generate the patched version. + pub fn add_kara_override(&mut self, _id: KaraId, _override: KaraOverride) { + todo!() + } + + /// Add a new kara into the lektord cache. If a patch is present for the + /// kara, it's patched and inserted into the patched cache. + pub fn add_kara(&mut self, _id: i64, _kara: KaraInfo) { + todo!() + } + + /// Get the [KaraInfo] correspondig to the id. If the kara was patched with + /// a [KaraOverride], we return the patched version, overwise we return the + /// original version. If the [KaraInfo] was not found then we return [None]. + pub fn kara(&mut self, id: i64) -> Option<&KaraInfo> { + match self.karas.get(&id)? { + (_, Some(info)) | (info, None) => Some(info), + } + } + + /// Get the [Playlist] correspondif to the name. If the [Playlist] was not + /// found, we return [None]. + pub fn playlist<'a>(&'a mut self, name: &SmallString) -> Option<&'a Playlist> { + self.playlists.get(name) + } } diff --git a/src/rust/amalib/src/db_objects.rs b/src/rust/amalib/src/db_objects.rs new file mode 100644 index 00000000..2667a256 --- /dev/null +++ b/src/rust/amalib/src/db_objects.rs @@ -0,0 +1,114 @@ +use crate::tags::*; +use getset::{CopyGetters, Getters}; +use serde::{Deserialize, Serialize}; +use smallstring::SmallString; + +/// Represent a global id for a kara, the pair (database, id). +#[derive(Debug, Deserialize, Serialize, Getters, CopyGetters)] +pub struct KaraId { + #[getset(get = "pub")] + repo: SmallString, + + #[getset(get_copy = "pub")] + id: i64, +} + +/// Represent what we want to override in a kara. +#[derive(Debug, Deserialize, Serialize)] +pub struct KaraOverride {} + +/// A list of informations about a kara. This is the data from lektor. When +/// queried we patch it with the overrides given by the user. +#[derive(Debug, Getters)] +#[getset(get = "pub")] +pub struct KaraInfo { + song_title: SmallString, + song_source: SmallString, + song_type: SmallString, + song_origin: SmallString, + languages: Vec<SmallString>, + kara_makers: Vec<SmallString>, + + #[getset(skip)] + tags: SimpleTagSystem, +} + +#[allow(dead_code)] +#[derive(Debug, Getters)] +#[getset(get = "pub")] +pub struct Playlist { + name: SmallString, + ctime: i64, + creator: SmallString, + content: Vec<i64>, + + #[getset(skip)] + tags: SimpleTagSystem, +} + +impl From<(&str, i64)> for KaraId { + fn from((repo, id): (&str, i64)) -> Self { + let repo = repo.into(); + Self { repo, id } + } +} + +impl From<(SmallString, i64)> for KaraId { + fn from((repo, id): (SmallString, i64)) -> Self { + Self { repo, id } + } +} + +impl<'a> TaggedObject<'a> for Playlist { + type TagKeyType = <SimpleTagSystem as TaggedObject<'a>>::TagKeyType; + type TagValueType = <SimpleTagSystem as TaggedObject<'a>>::TagValueType; + type TagKeyIterator = <SimpleTagSystem as TaggedObject<'a>>::TagKeyIterator; + type TagValueIterator = <SimpleTagSystem as TaggedObject<'a>>::TagValueIterator; + + fn add_tag(&mut self, tag: Self::TagKeyType, value: Option<Self::TagValueType>) { + self.tags.add_tag(tag, value) + } + + fn delete_tag<S: AsRef<str>>(&mut self, tag: S) { + self.tags.delete_tag(tag) + } + + fn clear_tags(&mut self) { + self.tags.clear() + } + + fn tags(&'a self) -> Self::TagKeyIterator { + self.tags.tags() + } + + fn iter_tag<S: AsRef<str>>(&'a self, tag: S) -> Option<Self::TagValueIterator> { + self.tags.iter_tag(tag) + } +} + +impl<'a> TaggedObject<'a> for KaraInfo { + type TagKeyType = <SimpleTagSystem as TaggedObject<'a>>::TagKeyType; + type TagValueType = <SimpleTagSystem as TaggedObject<'a>>::TagValueType; + type TagKeyIterator = <SimpleTagSystem as TaggedObject<'a>>::TagKeyIterator; + type TagValueIterator = <SimpleTagSystem as TaggedObject<'a>>::TagValueIterator; + + fn add_tag(&mut self, tag: Self::TagKeyType, value: Option<Self::TagValueType>) { + self.tags.add_tag(tag, value) + } + + fn delete_tag<S: AsRef<str>>(&mut self, tag: S) { + self.tags.delete_tag(tag) + } + + fn clear_tags(&mut self) { + self.tags.clear() + } + + fn tags(&'a self) -> Self::TagKeyIterator { + self.tags.tags() + } + + fn iter_tag<S: AsRef<str>>(&'a self, tag: S) -> Option<Self::TagValueIterator> { + self.tags.iter_tag(tag) + } +} diff --git a/src/rust/amalib/src/lib.rs b/src/rust/amalib/src/lib.rs index 476fc5b1..87ac4a9b 100644 --- a/src/rust/amalib/src/lib.rs +++ b/src/rust/amalib/src/lib.rs @@ -4,17 +4,21 @@ //! communicate with the lektord server and elements to store and organise the //! queried informations. -mod amacache; +mod cache; mod connexion; mod constants; +mod db_objects; mod query; mod response; +mod tags; mod uri; -pub use amacache::*; +pub use cache::*; pub use connexion::*; +pub use db_objects::*; pub use query::*; pub use response::*; +pub use tags::*; pub use uri::*; pub(crate) use commons::{log::*, *}; diff --git a/src/rust/amalib/src/tags.rs b/src/rust/amalib/src/tags.rs new file mode 100644 index 00000000..0d003575 --- /dev/null +++ b/src/rust/amalib/src/tags.rs @@ -0,0 +1,89 @@ +use hashbrown::HashMap; +use smallstring::SmallString; + +/// Represent an object that can be tagged, e.g. a playlist or a kara. We +/// describe all the operations on such object here. +pub trait TaggedObject<'a> { + /// The type for the keys, e.g. the tag names. + type TagKeyType: AsRef<str> + 'a; + + /// The type for the values of the tags. We enforce a string representation + /// for such tag. + type TagValueType: AsRef<str> + 'a; + + /// The type of iterator for the list of keys. + type TagKeyIterator: Iterator<Item = &'a Self::TagKeyType>; + + /// The type of iterator for the list the values of a tag. + type TagValueIterator: Iterator<Item = &'a Self::TagValueType>; + + /// Add a tag to the object. + fn add_tag(&mut self, tag: Self::TagKeyType, value: Option<Self::TagValueType>); + + /// Delete all values for a tag for the object. + fn delete_tag<S: AsRef<str>>(&mut self, tag: S); + + /// Remove all tags from the object. + fn clear_tags(&mut self); + + /// Get all the tags present on a kara. + fn tags(&'a self) -> Self::TagKeyIterator; + + /// Iterate over the values of a tag. + fn iter_tag<S: AsRef<str>>(&'a self, tag: S) -> Option<Self::TagValueIterator>; + + /// Has a tag with the specified name. + fn has_tag<S: AsRef<str>>(&'a self, tag: S) -> bool { + self.tags().any(|t| t.as_ref() == tag.as_ref()) + } + + /// Contains the (tag, value) pair. + fn has_tag_value<S: AsRef<str>>(&'a self, tag: S, value: S) -> bool { + self.iter_tag(tag) + .map(|mut values| values.any(|v| v.as_ref() == value.as_ref())) + .unwrap_or_default() + } +} + +/// A simple tag system. Any object can reuse this type or implement the +/// [TaggedObject] trait itself. To reuse this type, you can forward calls to +/// the simple tag field when implementing the trait. +pub(crate) type SimpleTagSystem = HashMap<SmallString, Vec<SmallString>>; + +impl<'a> TaggedObject<'a> for SimpleTagSystem { + type TagKeyType = SmallString; + type TagValueType = SmallString; + type TagKeyIterator = hashbrown::hash_map::Keys<'a, SmallString, Vec<SmallString>>; + type TagValueIterator = std::slice::Iter<'a, SmallString>; + + fn tags(&'a self) -> Self::TagKeyIterator { + self.keys() + } + + fn iter_tag<S: AsRef<str>>(&'a self, tag: S) -> Option<Self::TagValueIterator> { + self.get(tag.as_ref()).map(|tags| tags.iter()) + } + + fn add_tag(&mut self, tag: SmallString, value: Option<SmallString>) { + match self.get_mut(&tag) { + Some(values) => values.extend(value), + None => { + self.insert(tag, value.into_iter().collect()); + } + } + } + + fn delete_tag<S: AsRef<str>>(&mut self, tag: S) { + if let Some(values) = self.get_mut(tag.as_ref()) { + values.clear() + } + } + + fn clear_tags(&mut self) { + self.clear() + } + + fn has_tag<S: AsRef<str>>(&'a self, tag: S) -> bool { + self.contains_key(tag.as_ref()) + } +} -- GitLab