From bad7fcbb88d26b24233e03309ed17cc77efd7e96 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Sun, 23 Feb 2025 16:30:21 +0100 Subject: [PATCH] common: move types into separate files --- src/common.rs | 288 +----------------- src/common/types.rs | 38 +++ .../types/absolute_relative_song_position.rs | 25 ++ src/common/types/audio.rs | 27 ++ src/common/types/bool_or_oneshot.rs | 23 ++ src/common/types/one_or_range.rs | 24 ++ src/common/types/subsystem.rs | 66 ++++ src/common/types/tag.rs | 121 ++++++++ src/common/types/time_interval.rs | 25 ++ src/common/types/window_range.rs | 26 ++ 10 files changed, 377 insertions(+), 286 deletions(-) create mode 100644 src/common/types.rs create mode 100644 src/common/types/absolute_relative_song_position.rs create mode 100644 src/common/types/audio.rs create mode 100644 src/common/types/bool_or_oneshot.rs create mode 100644 src/common/types/one_or_range.rs create mode 100644 src/common/types/subsystem.rs create mode 100644 src/common/types/tag.rs create mode 100644 src/common/types/time_interval.rs create mode 100644 src/common/types/window_range.rs diff --git a/src/common.rs b/src/common.rs index 4c147c2..fe642b3 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,286 +1,2 @@ -use std::str::FromStr; - -use serde::{Deserialize, Serialize}; - -pub type SongPosition = u32; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum AbsouluteRelativeSongPosition { - Absolute(SongPosition), - RelativePlus(SongPosition), - RelativeMinus(SongPosition), -} - -impl FromStr for AbsouluteRelativeSongPosition { - type Err = (); - - fn from_str(s: &str) -> Result { - Ok(match s { - s if s.starts_with('+') => Self::RelativePlus(s[1..].parse().map_err(|_| ())?), - s if s.starts_with('-') => Self::RelativeMinus(s[1..].parse().map_err(|_| ())?), - s => Self::Absolute(s.parse().map_err(|_| ())?), - }) - } -} - -pub type SongId = u32; -pub type Seconds = u32; -pub type TimeWithFractions = f64; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum OneOrRange { - One(SongPosition), - Range(SongPosition, SongPosition), -} - -impl FromStr for OneOrRange { - type Err = (); - - fn from_str(s: &str) -> Result { - let mut parts = s.split(':'); - let start = parts.next().ok_or(())?.parse().map_err(|_| ())?; - Ok(match parts.next() { - Some(end) => Self::Range(start, end.parse().map_err(|_| ())?), - None => Self::One(start), - }) - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct WindowRange { - pub start: SongPosition, - pub end: Option, -} - -impl FromStr for WindowRange { - type Err = (); - - fn from_str(s: &str) -> Result { - let mut parts = s.split(':'); - let start = parts.next().ok_or(())?.parse().map_err(|_| ())?; - let end = parts.next().map(|s| s.parse().map_err(|_| ())); - Ok(Self { - start, - end: end.transpose()?, - }) - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct TimeInterval { - pub start: Option, - pub end: Option, -} - -impl FromStr for TimeInterval { - type Err = (); - - fn from_str(s: &str) -> Result { - let mut parts = s.split(':'); - let start = parts.next().map(|s| s.parse().map_err(|_| ())); - let end = parts.next().map(|s| s.parse().map_err(|_| ())); - Ok(Self { - start: start.transpose()?, - end: end.transpose()?, - }) - } -} - -pub type Priority = u8; -pub type PlaylistName = String; -pub type Offset = u32; - -// TODO: use a proper types -pub type TagName = String; -pub type TagValue = String; -pub type Uri = String; -pub type Path = String; -pub type Sort = String; -pub type Version = String; -pub type Feature = String; -pub type PartitionName = String; -pub type AudioOutputId = String; -pub type ChannelName = String; -pub type StickerType = String; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Tag { - /// The artist name. Its meaning is not well-defined; see “composer” and “performer” for more specific tags. - Artist(String), - /// Same as artist, but for sorting. This usually omits prefixes such as “The”. - ArtistSort(String), - /// The album name. - Album(String), - /// Same as album, but for sorting. - AlbumSort(String), - /// On multi-artist albums, this is the artist name which shall be used for the whole album. The exact meaning of this tag is not well-defined. - AlbumArtist(String), - /// Same as albumartist, but for sorting. - AlbumArtistSort(String), - /// The song title. - Title(String), - /// Same as title, but for sorting. - TitleSort(String), - /// The decimal track number within the album. - Track(String), - /// A name for this song. This is not the song title. The exact meaning of this tag is not well-defined. It is often used by badly configured internet radio stations with broken tags to squeeze both the artist name and the song title in one tag. - Name(String), - /// The music genre. - Genre(String), - /// The mood of the audio with a few keywords. - Mood(String), - /// The song's release date. This is usually a 4-digit year. - Date(String), - /// The song's original release date. - OriginalDate(String), - /// The artist who composed the song. - Composer(String), - /// Same as composer, but for sorting. - ComposerSort(String), - /// The artist who performed the song. - Performer(String), - /// The conductor who conducted the song. - Conductor(String), - /// “a work is a distinct intellectual or artistic creation, which can be expressed in the form of one or more audio recordings” - Work(String), - /// The ensemble performing this song, e.g. “Wiener Philharmoniker”. - Ensemble(String), - /// Name of the movement, e.g. “Andante con moto”. - Movement(String), - /// Movement number, e.g. “2” or “II”. - MovementNumber(String), - /// If this tag is set to “1” players supporting this tag will display the work, movement, and movementnumber` instead of the track title. - ShowMovement(String), - /// Location of the recording, e.g. “Royal Albert Hall”. - Location(String), - /// “used if the sound belongs to a larger category of sounds/music” (from the IDv2.4.0 TIT1 description). - Grouping(String), - /// A human-readable comment about this song. The exact meaning of this tag is not well-defined. - Comment(String), - /// The decimal disc number in a multi-disc album. - Disc(String), - /// The name of the label or publisher. - Label(String), - /// The artist id in the MusicBrainz database. - MusicBrainzArtistId(String), - /// The album id in the MusicBrainz database. - MusicBrainzAlbumId(String), - /// The album artist id in the MusicBrainz database. - MusicBrainzAlbumArtistId(String), - /// The track id in the MusicBrainz database. - MusicBrainzTrackId(String), - /// The release group id in the MusicBrainz database. - MusicBrainzReleaseGroupId(String), - /// The release track id in the MusicBrainz database. - MusicBrainzReleaseTrackId(String), - /// The work id in the MusicBrainz database. - MusicBrainzWorkId(String), - - /// Other tags not covered by the above - Other(String, String), -} - -/// These are different parts of the canonical MPD server. -/// They are mostly used in the protocol with the `idle` command, -/// signalling that the client is waiting for changes in any, one or multiple of these subsystems. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum SubSystem { - /// The song database has been modified after update. - Database, - /// A database update has started or finished. If the database was modified during the update, the database event is also emitted. - Update, - /// A stored playlist has been modified, renamed, created or deleted - StoredPlaylist, - /// The queue (i.e. the current playlist) has been modified - Playlist, - /// The player has been started, stopped or seeked or tags of the currently playing song have changed (e.g. received from stream) - Player, - /// The volume has been changed - Mixer, - /// An audio output has been added, removed or modified (e.g. renamed, enabled or disabled) - Output, - /// Options like repeat, random, crossfade, replay gain - Options, - /// A partition was added, removed or changed - Partition, - /// The sticker database has been modified. - Sticker, - /// A client has subscribed or unsubscribed to a channel - Subscription, - /// A message was received on a channel this client is subscribed to; this event is only emitted when the client’s message queue is empty - Message, - /// A neighbor was found or lost - Neighbor, - /// The mount list has changed - Mount, - - /// Other subsystems not covered by the above - Other(String), -} - -impl FromStr for SubSystem { - type Err = (); - - fn from_str(s: &str) -> Result { - Ok(match s { - "database" => Self::Database, - "update" => Self::Update, - "stored_playlist" => Self::StoredPlaylist, - "playlist" => Self::Playlist, - "player" => Self::Player, - "mixer" => Self::Mixer, - "output" => Self::Output, - "options" => Self::Options, - "partition" => Self::Partition, - "sticker" => Self::Sticker, - "subscription" => Self::Subscription, - "message" => Self::Message, - "neighbor" => Self::Neighbor, - "mount" => Self::Mount, - other => Self::Other(other.to_string()), - }) - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Audio { - pub sample_rate: u64, - pub bits: u8, - pub channels: u8, -} - -impl FromStr for Audio { - type Err = (); - - fn from_str(s: &str) -> Result { - let mut parts = s.split(':'); - let sample_rate = parts.next().ok_or(())?.parse().map_err(|_| ())?; - let bits = u8::from_str_radix(parts.next().ok_or(())?, 16).map_err(|_| ())? + 1; - let channels = parts.next().ok_or(())?.parse().map_err(|_| ())?; - Ok(Self { - sample_rate, - bits, - channels, - }) - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum BoolOrOneshot { - True, - False, - Oneshot, -} - -impl FromStr for BoolOrOneshot { - type Err = (); - - fn from_str(s: &str) -> Result { - Ok(match s { - "0" => Self::False, - "1" => Self::True, - "oneshot" => Self::Oneshot, - _ => return Err(()), - }) - } -} +mod types; +pub use types::*; diff --git a/src/common/types.rs b/src/common/types.rs new file mode 100644 index 0000000..1d9f095 --- /dev/null +++ b/src/common/types.rs @@ -0,0 +1,38 @@ +mod absolute_relative_song_position; +mod audio; +mod bool_or_oneshot; +mod one_or_range; +mod subsystem; +mod tag; +mod time_interval; +mod window_range; + +pub use absolute_relative_song_position::AbsouluteRelativeSongPosition; +pub use audio::Audio; +pub use bool_or_oneshot::BoolOrOneshot; +pub use one_or_range::OneOrRange; +pub use subsystem::SubSystem; +pub use tag::Tag; +pub use time_interval::TimeInterval; +pub use window_range::WindowRange; + +pub type SongPosition = u32; +pub type SongId = u32; +pub type Seconds = u32; +pub type TimeWithFractions = f64; +pub type Priority = u8; +pub type PlaylistName = String; +pub type Offset = u32; + +// TODO: use a proper types +pub type TagName = String; +pub type TagValue = String; +pub type Uri = String; +pub type Path = String; +pub type Sort = String; +pub type Version = String; +pub type Feature = String; +pub type PartitionName = String; +pub type AudioOutputId = String; +pub type ChannelName = String; +pub type StickerType = String; \ No newline at end of file diff --git a/src/common/types/absolute_relative_song_position.rs b/src/common/types/absolute_relative_song_position.rs new file mode 100644 index 0000000..b87ba59 --- /dev/null +++ b/src/common/types/absolute_relative_song_position.rs @@ -0,0 +1,25 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +use super::SongPosition; + + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum AbsouluteRelativeSongPosition { + Absolute(SongPosition), + RelativePlus(SongPosition), + RelativeMinus(SongPosition), +} + +impl FromStr for AbsouluteRelativeSongPosition { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s { + s if s.starts_with('+') => Self::RelativePlus(s[1..].parse().map_err(|_| ())?), + s if s.starts_with('-') => Self::RelativeMinus(s[1..].parse().map_err(|_| ())?), + s => Self::Absolute(s.parse().map_err(|_| ())?), + }) + } +} \ No newline at end of file diff --git a/src/common/types/audio.rs b/src/common/types/audio.rs new file mode 100644 index 0000000..b6ef157 --- /dev/null +++ b/src/common/types/audio.rs @@ -0,0 +1,27 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Audio { + pub sample_rate: u64, + pub bits: u8, + pub channels: u8, +} + +impl FromStr for Audio { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let sample_rate = parts.next().ok_or(())?.parse().map_err(|_| ())?; + let bits = u8::from_str_radix(parts.next().ok_or(())?, 16).map_err(|_| ())? + 1; + let channels = parts.next().ok_or(())?.parse().map_err(|_| ())?; + Ok(Self { + sample_rate, + bits, + channels, + }) + } +} \ No newline at end of file diff --git a/src/common/types/bool_or_oneshot.rs b/src/common/types/bool_or_oneshot.rs new file mode 100644 index 0000000..6affc76 --- /dev/null +++ b/src/common/types/bool_or_oneshot.rs @@ -0,0 +1,23 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum BoolOrOneshot { + True, + False, + Oneshot, +} + +impl FromStr for BoolOrOneshot { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s { + "0" => Self::False, + "1" => Self::True, + "oneshot" => Self::Oneshot, + _ => return Err(()), + }) + } +} \ No newline at end of file diff --git a/src/common/types/one_or_range.rs b/src/common/types/one_or_range.rs new file mode 100644 index 0000000..556a4f8 --- /dev/null +++ b/src/common/types/one_or_range.rs @@ -0,0 +1,24 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +use super::SongPosition; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum OneOrRange { + One(SongPosition), + Range(SongPosition, SongPosition), +} + +impl FromStr for OneOrRange { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let start = parts.next().ok_or(())?.parse().map_err(|_| ())?; + Ok(match parts.next() { + Some(end) => Self::Range(start, end.parse().map_err(|_| ())?), + None => Self::One(start), + }) + } +} \ No newline at end of file diff --git a/src/common/types/subsystem.rs b/src/common/types/subsystem.rs new file mode 100644 index 0000000..0fe0c33 --- /dev/null +++ b/src/common/types/subsystem.rs @@ -0,0 +1,66 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + + +/// These are different parts of the canonical MPD server. +/// They are mostly used in the protocol with the `idle` command, +/// signalling that the client is waiting for changes in any, one or multiple of these subsystems. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum SubSystem { + /// The song database has been modified after update. + Database, + /// A database update has started or finished. If the database was modified during the update, the database event is also emitted. + Update, + /// A stored playlist has been modified, renamed, created or deleted + StoredPlaylist, + /// The queue (i.e. the current playlist) has been modified + Playlist, + /// The player has been started, stopped or seeked or tags of the currently playing song have changed (e.g. received from stream) + Player, + /// The volume has been changed + Mixer, + /// An audio output has been added, removed or modified (e.g. renamed, enabled or disabled) + Output, + /// Options like repeat, random, crossfade, replay gain + Options, + /// A partition was added, removed or changed + Partition, + /// The sticker database has been modified. + Sticker, + /// A client has subscribed or unsubscribed to a channel + Subscription, + /// A message was received on a channel this client is subscribed to; this event is only emitted when the client’s message queue is empty + Message, + /// A neighbor was found or lost + Neighbor, + /// The mount list has changed + Mount, + + /// Other subsystems not covered by the above + Other(String), +} + +impl FromStr for SubSystem { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s { + "database" => Self::Database, + "update" => Self::Update, + "stored_playlist" => Self::StoredPlaylist, + "playlist" => Self::Playlist, + "player" => Self::Player, + "mixer" => Self::Mixer, + "output" => Self::Output, + "options" => Self::Options, + "partition" => Self::Partition, + "sticker" => Self::Sticker, + "subscription" => Self::Subscription, + "message" => Self::Message, + "neighbor" => Self::Neighbor, + "mount" => Self::Mount, + other => Self::Other(other.to_string()), + }) + } +} \ No newline at end of file diff --git a/src/common/types/tag.rs b/src/common/types/tag.rs new file mode 100644 index 0000000..e5a1c86 --- /dev/null +++ b/src/common/types/tag.rs @@ -0,0 +1,121 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Tag { + /// The artist name. Its meaning is not well-defined; see “composer” and “performer” for more specific tags. + Artist(String), + /// Same as artist, but for sorting. This usually omits prefixes such as “The”. + ArtistSort(String), + /// The album name. + Album(String), + /// Same as album, but for sorting. + AlbumSort(String), + /// On multi-artist albums, this is the artist name which shall be used for the whole album. The exact meaning of this tag is not well-defined. + AlbumArtist(String), + /// Same as albumartist, but for sorting. + AlbumArtistSort(String), + /// The song title. + Title(String), + /// Same as title, but for sorting. + TitleSort(String), + /// The decimal track number within the album. + Track(String), + /// A name for this song. This is not the song title. The exact meaning of this tag is not well-defined. It is often used by badly configured internet radio stations with broken tags to squeeze both the artist name and the song title in one tag. + Name(String), + /// The music genre. + Genre(String), + /// The mood of the audio with a few keywords. + Mood(String), + /// The song's release date. This is usually a 4-digit year. + Date(String), + /// The song's original release date. + OriginalDate(String), + /// The artist who composed the song. + Composer(String), + /// Same as composer, but for sorting. + ComposerSort(String), + /// The artist who performed the song. + Performer(String), + /// The conductor who conducted the song. + Conductor(String), + /// “a work is a distinct intellectual or artistic creation, which can be expressed in the form of one or more audio recordings” + Work(String), + /// The ensemble performing this song, e.g. “Wiener Philharmoniker”. + Ensemble(String), + /// Name of the movement, e.g. “Andante con moto”. + Movement(String), + /// Movement number, e.g. “2” or “II”. + MovementNumber(String), + /// If this tag is set to “1” players supporting this tag will display the work, movement, and movementnumber` instead of the track title. + ShowMovement(String), + /// Location of the recording, e.g. “Royal Albert Hall”. + Location(String), + /// “used if the sound belongs to a larger category of sounds/music” (from the IDv2.4.0 TIT1 description). + Grouping(String), + /// A human-readable comment about this song. The exact meaning of this tag is not well-defined. + Comment(String), + /// The decimal disc number in a multi-disc album. + Disc(String), + /// The name of the label or publisher. + Label(String), + /// The artist id in the MusicBrainz database. + MusicBrainzArtistId(String), + /// The album id in the MusicBrainz database. + MusicBrainzAlbumId(String), + /// The album artist id in the MusicBrainz database. + MusicBrainzAlbumArtistId(String), + /// The track id in the MusicBrainz database. + MusicBrainzTrackId(String), + /// The release group id in the MusicBrainz database. + MusicBrainzReleaseGroupId(String), + /// The release track id in the MusicBrainz database. + MusicBrainzReleaseTrackId(String), + /// The work id in the MusicBrainz database. + MusicBrainzWorkId(String), + + /// Other tags not covered by the above + Other(String, String), +} + +impl Tag { + pub fn new(key: String, value: String) -> Tag { + match key.as_str() { + "Artist" => Self::Artist(value), + "ArtistSort" => Self::ArtistSort(value), + "Album" => Self::Album(value), + "AlbumSort" => Self::AlbumSort(value), + "AlbumArtist" => Self::AlbumArtist(value), + "AlbumArtistSort" => Self::AlbumArtistSort(value), + "Title" => Self::Title(value), + "TitleSort" => Self::TitleSort(value), + "Track" => Self::Track(value), + "Name" => Self::Name(value), + "Genre" => Self::Genre(value), + "Mood" => Self::Mood(value), + "Date" => Self::Date(value), + "OriginalDate" => Self::OriginalDate(value), + "Composer" => Self::Composer(value), + "ComposerSort" => Self::ComposerSort(value), + "Performer" => Self::Performer(value), + "Conductor" => Self::Conductor(value), + "Work" => Self::Work(value), + "Ensemble" => Self::Ensemble(value), + "Movement" => Self::Movement(value), + "MovementNumber" => Self::MovementNumber(value), + "ShowMovement" => Self::ShowMovement(value), + "Location" => Self::Location(value), + "Grouping" => Self::Grouping(value), + "Comment" => Self::Comment(value), + "Disc" => Self::Disc(value), + "Label" => Self::Label(value), + "MusicBrainzArtistId" => Self::MusicBrainzArtistId(value), + "MusicBrainzAlbumId" => Self::MusicBrainzAlbumId(value), + "MusicBrainzAlbumArtistId" => Self::MusicBrainzAlbumArtistId(value), + "MusicBrainzTrackId" => Self::MusicBrainzTrackId(value), + "MusicBrainzReleaseGroupId" => Self::MusicBrainzReleaseGroupId(value), + "MusicBrainzReleaseTrackId" => Self::MusicBrainzReleaseTrackId(value), + "MusicBrainzWorkId" => Self::MusicBrainzWorkId(value), + other => Self::Other(other.to_string(), value), + } + } +} \ No newline at end of file diff --git a/src/common/types/time_interval.rs b/src/common/types/time_interval.rs new file mode 100644 index 0000000..d7f0661 --- /dev/null +++ b/src/common/types/time_interval.rs @@ -0,0 +1,25 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +use super::TimeWithFractions; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TimeInterval { + pub start: Option, + pub end: Option, +} + +impl FromStr for TimeInterval { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let start = parts.next().map(|s| s.parse().map_err(|_| ())); + let end = parts.next().map(|s| s.parse().map_err(|_| ())); + Ok(Self { + start: start.transpose()?, + end: end.transpose()?, + }) + } +} \ No newline at end of file diff --git a/src/common/types/window_range.rs b/src/common/types/window_range.rs new file mode 100644 index 0000000..92e17b2 --- /dev/null +++ b/src/common/types/window_range.rs @@ -0,0 +1,26 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +use super::SongPosition; + + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct WindowRange { + pub start: SongPosition, + pub end: Option, +} + +impl FromStr for WindowRange { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let start = parts.next().ok_or(())?.parse().map_err(|_| ())?; + let end = parts.next().map(|s| s.parse().map_err(|_| ())); + Ok(Self { + start, + end: end.transpose()?, + }) + } +} \ No newline at end of file