audiot_core 0.2.0

Library that helps build the search database through the CLI, and eventually can be used to read data from that same database.
Documentation
use deunicode::deunicode;
use serde::{Deserialize, Serialize};

use crate::brainz::{ArtistCredit, ArtistReference, GenreReference, Tag};

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ReleaseGroup {
    pub id: String,
    pub title: String,
    #[serde(default, alias = "primary-type-id")]
    pub primary_type_id: Option<String>,
    #[serde(default, alias = "primary-type")]
    pub primary_type: Option<String>,
    #[serde(default, alias = "first-release-date")]
    pub first_release_date: Option<String>,
    #[serde(default, alias = "artist-credit")]
    pub artist_credit: Vec<ArtistCredit>,
    #[serde(default)]
    pub genres: Vec<GenreReference>,
    #[serde(default)]
    pub tags: Vec<Tag>,
    #[serde(default, alias = "secondary-types")]
    pub secondary_types: Vec<String>,
}

fn primary_release_group_type_to_priority(release_group_type: &str) -> i64 {
    let lowered = release_group_type.to_lowercase();

    match lowered.as_str() {
        "album" => 5,
        "ep" => 4,
        "live" => 3,
        _ => 2,
    }
}

fn secondary_release_group_type_to_priority(release_group_type: &str) -> i64 {
    let lowered = release_group_type.to_lowercase();

    match lowered.as_str() {
        "album" => 2,
        "ep" => 3,
        "live" => 4,
        _ => 1,
    }
}

impl ReleaseGroup {
    /// The priority of this release group vs other ones. Where Albums would have a higher priority in search than EPs.
    /// Secondary types would also decrease the priority as well, pushing them down the search results.
    /// Priorities go from low to high, 1 being the lowest.
    pub fn priority(&self) -> i64 {
        if let Some(primary_type) = self.primary_type.as_ref() {
            let mut priority = primary_release_group_type_to_priority(primary_type);

            // If there are genres or tags, it might be more likely someone has gone to the
            // trouble of editing, so lets prioritize it slightly.
            if !self.genres.is_empty() {
                priority += 1;
            }

            if !self.tags.is_empty() {
                priority += 1;
            }

            // Similarly, if someone has gone to the trouble of adding a release date it'll be prioritized.
            let release_date_priority = self
                .first_release_date
                .as_ref()
                .map(|release_date| {
                    if !release_date.trim().is_empty() {
                        1
                    } else {
                        0
                    }
                })
                .unwrap_or(0);
            priority += release_date_priority;

            if self.secondary_types.is_empty() {
                priority * 10
            } else {
                for secondary_type in self.secondary_types.iter() {
                    priority -= secondary_release_group_type_to_priority(secondary_type);
                }

                if priority <= 0 {
                    priority = 1
                }

                priority * 5
            }
        } else {
            1
        }
    }

    pub fn artist_ids(&self) -> Vec<String> {
        self.artist_credit
            .iter()
            .filter_map(|artist_credit| artist_credit.artist.clone())
            .map(|artist| artist.id.clone())
            .collect()
    }

    pub fn artist_content(&self) -> String {
        self.artist_credit
            .iter()
            .flat_map(|artist_credit| {
                if let Some(artist) = artist_credit.artist.as_ref() {
                    Some(artist.name.clone())
                } else {
                    artist_credit.name.clone()
                }
            })
            .map(|name| {
                let normal = deunicode(&name);
                if name.contains(".") {
                    format!("{} {}", normal, normal.replace(".", ""))
                } else {
                    normal
                }
            })
            .collect::<Vec<_>>()
            .join("; ")
    }

    pub fn artist_references(&self) -> Vec<ArtistReference> {
        self.artist_credit
            .iter()
            .flat_map(|artist_credit| artist_credit.artist.clone())
            .collect::<Vec<_>>()
    }

    pub fn title_content(&self) -> String {
        deunicode(&self.title)
    }

    pub fn genres_content(&self) -> String {
        self.genres
            .iter()
            .map(|genre| genre.name.clone())
            .collect::<Vec<_>>()
            .join("; ")
    }

    pub fn matches_types(&self, release_types: &[String]) -> bool {
        if let Some(primary_type) = self.primary_type.as_ref() {
            if !release_types.contains(primary_type) {
                false
            } else if self.secondary_types.is_empty() {
                true
            } else {
                release_types
                    .iter()
                    .any(|release_type| self.secondary_types.contains(release_type))
            }
        } else {
            false
        }
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SearchReleaseGroup {
    pub id: String,
    pub title: String,
    pub artists: Vec<String>,
    pub genres: Vec<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct ReleaseGroupReference {
    pub id: String,
}