termusiclib/podcast/
podcast.rs

1use std::cmp::Ordering;
2
3use chrono::{DateTime, Utc};
4
5use crate::utils::StringUtils;
6
7use super::{
8    Menuable, PODCAST_UNPLAYED_TOTALS_LENGTH,
9    episode::{Episode, EpisodeNoId},
10};
11
12/// Struct holding data about an individual podcast feed. This includes a
13/// (possibly empty) vector of episodes.
14#[derive(Debug, Clone)]
15pub struct Podcast {
16    pub id: i64,
17    pub title: String,
18    pub sort_title: String,
19    pub url: String,
20    pub description: Option<String>,
21    pub author: Option<String>,
22    pub explicit: Option<bool>,
23    pub last_checked: DateTime<Utc>,
24    pub episodes: Vec<Episode>,
25    pub image_url: Option<String>,
26}
27
28impl Podcast {
29    // Counts and returns the number of unplayed episodes in the podcast.
30    #[must_use]
31    pub fn num_unplayed(&self) -> usize {
32        self.episodes
33            .iter()
34            .map(|ep| usize::from(!ep.is_played()))
35            .sum()
36    }
37}
38
39impl Menuable for Podcast {
40    /// Returns the database ID for the podcast.
41    fn get_id(&self) -> i64 {
42        self.id
43    }
44
45    /// Returns the title for the podcast, up to length characters.
46    fn get_title(&self, length: usize) -> String {
47        let mut title_length = length;
48
49        // if the size available is big enough, we add the unplayed data
50        // to the end
51        if length > PODCAST_UNPLAYED_TOTALS_LENGTH {
52            let meta_str = format!("({}/{})", self.num_unplayed(), self.episodes.len());
53            title_length = length - meta_str.chars().count() - 3;
54
55            let out = self.title.substr(0, title_length);
56
57            format!(
58                " {out} {meta_str:>width$} ",
59                width = length - out.grapheme_len() - 3
60            ) // this pads spaces between title and totals
61        } else {
62            format!(" {} ", self.title.substr(0, title_length - 2))
63        }
64    }
65
66    fn is_played(&self) -> bool {
67        self.num_unplayed() == 0
68    }
69}
70
71impl PartialEq for Podcast {
72    fn eq(&self, other: &Self) -> bool {
73        self.sort_title == other.sort_title
74    }
75}
76impl Eq for Podcast {}
77
78impl PartialOrd for Podcast {
79    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
80        Some(self.cmp(other))
81    }
82}
83
84impl Ord for Podcast {
85    fn cmp(&self, other: &Self) -> Ordering {
86        self.sort_title.cmp(&other.sort_title)
87    }
88}
89
90/// Struct holding data about an individual podcast feed, before it has
91/// been inserted into the database. This includes a
92/// (possibly empty) vector of episodes.
93#[derive(Debug, Clone, Eq, PartialEq)]
94#[allow(clippy::module_name_repetitions)]
95pub struct PodcastNoId {
96    pub title: String,
97    pub url: String,
98    pub description: Option<String>,
99    pub author: Option<String>,
100    pub explicit: Option<bool>,
101    pub last_checked: DateTime<Utc>,
102    pub episodes: Vec<EpisodeNoId>,
103    pub image_url: Option<String>,
104}