diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index dfe7552ce5204d33b24a65c010508505ae79c018..faa282f7895534a7a5234f04d95b9298c35aea17 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -31,8 +31,14 @@ libc = "0.2" lazy_static = "^1" thiserror = "^1" -smallvec = { version = "^1", default-features = false, features = ["serde"] } hashbrown = { version = "^0.13", features = ["serde"] } +smallvec = { version = "^1", default-features = false, features = [ + "serde", + "write", + "union", + "const_generics", + "const_new", +] } toml = { version = "^0.5", features = ["preserve_order"] } serde = { version = "^1", default-features = false, features = [ diff --git a/src/rust/amalib/src/connexion.rs b/src/rust/amalib/src/connexion.rs index 1e2f9bdc1ce08603e75243e6ed459b4738688852..e202f2856a5054a352fe028ab15c1720825139c1 100644 --- a/src/rust/amalib/src/connexion.rs +++ b/src/rust/amalib/src/connexion.rs @@ -1,3 +1,5 @@ +//! Connexion to the lektord server. + use crate::*; use tokio::net::TcpStream; diff --git a/src/rust/amalib/src/constants.rs b/src/rust/amalib/src/constants.rs index 00977f31d71660bdbccea92318689d569645ea08..2d31b15f9e2edd07558feb70b967bfb31d7578f0 100644 --- a/src/rust/amalib/src/constants.rs +++ b/src/rust/amalib/src/constants.rs @@ -1,9 +1,8 @@ //! Contains standard constants from lektord, must be updated manually if they //! change in lektord... -#![allow(dead_code)] - /// MPD commands are at most 32 character long. +#[allow(dead_code)] pub(crate) const LKT_COMMAND_LEN_MAX: usize = 32; /// At most 32 words in a command are supported. @@ -13,9 +12,11 @@ pub(crate) const LKT_MESSAGE_ARGS_MAX: usize = 32; pub(crate) const LKT_MESSAGE_MAX: usize = 2048; /// At most 64 commands per client. +#[allow(dead_code)] pub(crate) const COMMAND_LIST_MAX: usize = 64; /// At most 16 messages per client. +#[allow(dead_code)] pub(crate) const BUFFER_OUT_MAX: usize = 16; /// Expected version from the lektord daemin. diff --git a/src/rust/amalib/src/cache.rs b/src/rust/amalib/src/db_cache.rs similarity index 96% rename from src/rust/amalib/src/cache.rs rename to src/rust/amalib/src/db_cache.rs index f86ec433b2085dadf66edad0ddaa35b867372cd0..776c713baca4c74bcbb327c57a50d3cf530f54ff 100644 --- a/src/rust/amalib/src/cache.rs +++ b/src/rust/amalib/src/db_cache.rs @@ -1,9 +1,8 @@ +//! Datastructure used to cache requests to the lektord server. Also permit to +//! patch returned data with local data. + use crate::*; -use hashbrown::HashMap; use lru::LruCache; -use serde::{Deserialize, Serialize}; -use smallstring::SmallString; -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 diff --git a/src/rust/amalib/src/db_objects.rs b/src/rust/amalib/src/db_objects.rs index 2667a2567637b3b6a2f96e20796c18ef56f95af6..5ec8f058c548762375d5f543e4514dc4f948cc59 100644 --- a/src/rust/amalib/src/db_objects.rs +++ b/src/rust/amalib/src/db_objects.rs @@ -1,7 +1,7 @@ -use crate::tags::*; -use getset::{CopyGetters, Getters}; -use serde::{Deserialize, Serialize}; -use smallstring::SmallString; +//! Collection of structures to describe objects retrieved from the lektord +//! server. + +use crate::*; /// Represent a global id for a kara, the pair (database, id). #[derive(Debug, Deserialize, Serialize, Getters, CopyGetters)] @@ -60,55 +60,83 @@ impl From<(SmallString, i64)> for KaraId { } 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; + type TK = <SimpleTagSystem as TaggedObject<'a>>::TK; + type TV = <SimpleTagSystem as TaggedObject<'a>>::TV; + type TKIter = <SimpleTagSystem as TaggedObject<'a>>::TKIter; + type TVIter = <SimpleTagSystem as TaggedObject<'a>>::TVIter; - fn add_tag(&mut self, tag: Self::TagKeyType, value: Option<Self::TagValueType>) { + fn add_tag(&mut self, tag: Self::TK, value: Option<Self::TV>) -> bool { self.tags.add_tag(tag, value) } - fn delete_tag<S: AsRef<str>>(&mut self, tag: S) { - self.tags.delete_tag(tag) + fn remove_tag<S1: AsRef<str>, S2: AsRef<str>>(&mut self, tag: S1, value: S2) -> bool { + self.tags.remove_tag(tag, value) + } + + fn drop_tag<S: AsRef<str>>(&mut self, tag: S) -> bool { + self.tags.drop_tag(tag) + } + + fn empty_tag<S: AsRef<str>>(&mut self, tag: S) -> bool { + self.tags.empty_tag(tag) } - fn clear_tags(&mut self) { - self.tags.clear() + fn clear_tags(&mut self) -> bool { + self.tags.clear_tags() } - fn tags(&'a self) -> Self::TagKeyIterator { + fn tags<'b>(&'b self) -> Self::TKIter + where + 'b: 'a, + { self.tags.tags() } - fn iter_tag<S: AsRef<str>>(&'a self, tag: S) -> Option<Self::TagValueIterator> { + fn iter_tag<'b, S: AsRef<str>>(&'b self, tag: S) -> Option<Self::TVIter> + where + 'b: 'a, + { 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; + type TK = <SimpleTagSystem as TaggedObject<'a>>::TK; + type TV = <SimpleTagSystem as TaggedObject<'a>>::TV; + type TKIter = <SimpleTagSystem as TaggedObject<'a>>::TKIter; + type TVIter = <SimpleTagSystem as TaggedObject<'a>>::TVIter; - fn add_tag(&mut self, tag: Self::TagKeyType, value: Option<Self::TagValueType>) { + fn add_tag(&mut self, tag: Self::TK, value: Option<Self::TV>) -> bool { self.tags.add_tag(tag, value) } - fn delete_tag<S: AsRef<str>>(&mut self, tag: S) { - self.tags.delete_tag(tag) + fn remove_tag<S1: AsRef<str>, S2: AsRef<str>>(&mut self, tag: S1, value: S2) -> bool { + self.tags.remove_tag(tag, value) + } + + fn drop_tag<S: AsRef<str>>(&mut self, tag: S) -> bool { + self.tags.drop_tag(tag) + } + + fn empty_tag<S: AsRef<str>>(&mut self, tag: S) -> bool { + self.tags.empty_tag(tag) } - fn clear_tags(&mut self) { - self.tags.clear() + fn clear_tags(&mut self) -> bool { + self.tags.clear_tags() } - fn tags(&'a self) -> Self::TagKeyIterator { + fn tags<'b>(&'b self) -> Self::TKIter + where + 'b: 'a, + { self.tags.tags() } - fn iter_tag<S: AsRef<str>>(&'a self, tag: S) -> Option<Self::TagValueIterator> { + fn iter_tag<'b, S: AsRef<str>>(&'b self, tag: S) -> Option<Self::TVIter> + where + 'b: 'a, + { self.tags.iter_tag(tag) } } diff --git a/src/rust/amalib/src/db_tags.rs b/src/rust/amalib/src/db_tags.rs new file mode 100644 index 0000000000000000000000000000000000000000..0433d4c9e7c79c8743b64acf06279d39a4275602 --- /dev/null +++ b/src/rust/amalib/src/db_tags.rs @@ -0,0 +1,186 @@ +//! Represent tags on playlists and karas in the database. + +use crate::*; + +/// 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 TK: AsRef<str> + Clone + 'a; + + /// The type for the values of the tags. We enforce a string representation + /// for such tag. + type TV: AsRef<str> + Clone + 'a; + + /// The type of iterator for the list of keys. + type TKIter: IntoIterator<Item = &'a Self::TK>; + + /// The type of iterator for the list the values of a tag. + type TVIter: IntoIterator<Item = &'a Self::TV>; + + /// Add a tag to the object. Returns true if the action did something. + fn add_tag(&mut self, tag: Self::TK, value: Option<Self::TV>) -> bool; + + /// Delete a value for a tag. Returns true if the action did something. + fn remove_tag<S1: AsRef<str>, S2: AsRef<str>>(&mut self, tag: S1, value: S2) -> bool; + + /// Delete all values for a tag and the tag itself for the object. Returns + /// true if the action did something. + fn drop_tag<S: AsRef<str>>(&mut self, tag: S) -> bool; + + /// Delete all values for a tag for the object. If the tag did not exist, + /// create it. Returns true if the action did something. + fn empty_tag<S: AsRef<str>>(&mut self, tag: S) -> bool; + + /// Remove all tags from the object. Returns true if the action did + /// something. + fn clear_tags(&mut self) -> bool; + + /// Get all the tags present on a kara. + fn tags<'b>(&'b self) -> Self::TKIter + where + 'b: 'a; + + /// Iterate over the values of a tag. + fn iter_tag<'b, S: AsRef<str>>(&'b self, tag: S) -> Option<Self::TVIter> + where + 'b: 'a; + + /// Has a tag with the specified name. + fn has_tag<'b, S: AsRef<str>>(&'b self, tag: S) -> bool + where + 'b: 'a, + { + self.tags().into_iter().any(|t| t.as_ref() == tag.as_ref()) + } + + /// Contains the (tag, value) pair. + fn has_tag_value<'b, S: AsRef<str>>(&'b self, tag: S, value: S) -> bool + where + 'b: 'a, + { + self.iter_tag(tag) + .map(|values| values.into_iter().any(|v| v.as_ref() == value.as_ref())) + .unwrap_or_default() + } + + /// Patch the tagged object with a list of actions to do in the passed + /// order. If the object was patched, return true, otherwise return false. + fn patch<'b, I>(&'b mut self, overrides: I) -> bool + where + I: IntoIterator<Item = TagOverride<Self::TK, Self::TV>>, + 'a: 'b, + { + use TagOverride::*; + overrides + .into_iter() + .map(|patch| match patch { + ClearTags => self.clear_tags(), + DeleteTag(tag) => self.drop_tag(tag), + AddTagValue(tag, value) => self.add_tag(tag, Some(value)), + DeleteTagValue(tag, value) => self.remove_tag(tag, value), + EmptyTag(tag) => self.empty_tag(tag), + }) + .count() + != 0 + } +} + +/// 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 type SimpleTagSystem = HashMap<SmallString, HashSet<SmallString>>; + +impl<'a> TaggedObject<'a> for SimpleTagSystem { + type TK = SmallString; + type TV = SmallString; + type TKIter = hashbrown::hash_map::Keys<'a, SmallString, HashSet<SmallString>>; + type TVIter = hashbrown::hash_set::Iter<'a, SmallString>; + + fn add_tag(&mut self, tag: SmallString, value: Option<SmallString>) -> bool { + match self.get_mut(&tag) { + Some(values) => { + let old_count = values.len(); + values.extend(value); + old_count != values.len() + } + None => { + self.insert(tag, value.into_iter().collect()); + true + } + } + } + + fn remove_tag<S1: AsRef<str>, S2: AsRef<str>>(&mut self, tag: S1, value: S2) -> bool { + self.get_mut(tag.as_ref()) + .map(|values| values.remove(value.as_ref())) + .unwrap_or_default() + } + + fn drop_tag<S: AsRef<str>>(&mut self, tag: S) -> bool { + either!(!self.has_tag(tag.as_ref()) => false; { + self.remove(tag.as_ref()); + true + }) + } + + fn empty_tag<S: AsRef<str>>(&mut self, tag: S) -> bool { + self.get_mut(tag.as_ref()) + .map(|values| { + let old_count = values.len(); + values.clear(); + old_count != values.len() + }) + .or_else(|| Some(self.add_tag(SmallString::from(tag.as_ref()), None))) + .unwrap_or_default() + } + + fn clear_tags(&mut self) -> bool { + either!(self.tags().count() == 0 => false; { + self.clear(); + true + }) + } + + fn tags<'b>(&'b self) -> Self::TKIter + where + 'b: 'a, + { + self.keys() + } + + fn iter_tag<'b, S: AsRef<str>>(&'b self, tag: S) -> Option<Self::TVIter> + where + 'b: 'a, + { + self.get(tag.as_ref()).map(|tags| tags.iter()) + } + + fn has_tag<'b, S: AsRef<str>>(&self, tag: S) -> bool + where + 'b: 'a, + { + self.contains_key(tag.as_ref()) + } +} + +#[derive(Debug, Clone)] +/// Represents a patch action to do on a tagged object. +pub enum TagOverride<TK: AsRef<str> + Clone, TV: AsRef<str>> { + /// Remove all tags. + ClearTags, + + /// Remove a tag and all it's values. + DeleteTag(TK), + + /// Clear all the values for a tag, or if the tag doesn't exist, create one + /// with no associated values for the object. + EmptyTag(TK), + + /// Add a value to the tag's value list. If the tag is not already present, + /// create the tag. + AddTagValue(TK, TV), + + /// Remove a value from a tag's value list. + DeleteTagValue(TK, TV), +} diff --git a/src/rust/amalib/src/lib.rs b/src/rust/amalib/src/lib.rs index 87ac4a9b5835fecd697099086350ff5368a4142e..d02b0fd32284012971492952d47efc4750f5cebb 100644 --- a/src/rust/amalib/src/lib.rs +++ b/src/rust/amalib/src/lib.rs @@ -4,25 +4,29 @@ //! communicate with the lektord server and elements to store and organise the //! queried informations. -mod cache; mod connexion; mod constants; +mod db_cache; mod db_objects; +mod db_tags; mod query; mod response; -mod tags; mod uri; -pub use cache::*; pub use connexion::*; +pub use db_cache::*; pub use db_objects::*; +pub use db_tags::*; pub use query::*; pub use response::*; -pub use tags::*; pub use uri::*; pub(crate) use commons::{log::*, *}; -pub(crate) use std::str::FromStr; +pub(crate) use getset::{CopyGetters, Getters}; +pub(crate) use hashbrown::{HashMap, HashSet}; +pub(crate) use serde::{Deserialize, Serialize}; +pub(crate) use smallstring::SmallString; +pub(crate) use std::{num::NonZeroUsize, ops::Range, str::FromStr, string::ToString}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LektorPriorityLevel { diff --git a/src/rust/amalib/src/query.rs b/src/rust/amalib/src/query.rs index ad0bc165e71e1059c6d267f17ad60d028186da84..07dc88576692399b3ced6d163a386e1abc336d3f 100644 --- a/src/rust/amalib/src/query.rs +++ b/src/rust/amalib/src/query.rs @@ -1,7 +1,6 @@ //! Contains files to create and build queries to send latter to lektord. use crate::*; -use std::{ops::Range, string::ToString}; #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum LektorQueryLineType { diff --git a/src/rust/amalib/src/tags.rs b/src/rust/amalib/src/tags.rs deleted file mode 100644 index 0d003575e25c1ad19aa087bba1e06f086255496b..0000000000000000000000000000000000000000 --- a/src/rust/amalib/src/tags.rs +++ /dev/null @@ -1,89 +0,0 @@ -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()) - } -}