termusiclib/library_db/
track_db.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use std::time::{Duration, UNIX_EPOCH};

use rusqlite::{named_params, Connection, Row};

use crate::{const_str, track::Track};

/// A struct representing a [`Track`](Track) in the database
#[derive(Clone, Debug)]
pub struct TrackDB {
    pub id: u64,
    pub artist: String,
    pub title: String,
    pub album: String,
    pub genre: String,
    pub file: String,
    pub duration: Duration,
    pub name: String,
    pub ext: String,
    pub directory: String,
    pub last_modified: String,
    pub last_position: Duration,
}

impl TrackDB {
    /// Try to convert a given row to a [`TrackDB`] instance, expecting correct row order.
    ///
    /// Use [`Self::try_from_row_named`] if possible.
    pub fn try_from_row_id(row: &Row<'_>) -> Result<Self, rusqlite::Error> {
        let d_u64: u64 = row.get(6)?;
        let last_position_u64: u64 = row.get(11)?;
        Ok(TrackDB {
            id: row.get(0)?,
            artist: row.get(1)?,
            title: row.get(2)?,
            album: row.get(3)?,
            genre: row.get(4)?,
            file: row.get(5)?,
            duration: Duration::from_secs(d_u64),
            name: row.get(7)?,
            ext: row.get(8)?,
            directory: row.get(9)?,
            last_modified: row.get(10)?,
            last_position: Duration::from_secs(last_position_u64),
        })
    }

    /// Try to convert a given row to a [`TrackDB`] instance, using column names to resolve the values
    pub fn try_from_row_named(row: &Row<'_>) -> Result<Self, rusqlite::Error> {
        // NOTE: all the names in "get" below are the *column names* as defined in migrations/002.sql#table_tracks (pseudo link)
        let d_u64: u64 = row.get("duration")?;
        let last_position_u64: u64 = row.get("last_position")?;
        Ok(TrackDB {
            id: row.get("id")?,
            artist: row.get("artist")?,
            title: row.get("title")?,
            album: row.get("album")?,
            genre: row.get("genre")?,
            file: row.get("file")?,
            duration: Duration::from_secs(d_u64),
            name: row.get("name")?,
            ext: row.get("ext")?,
            directory: row.get("directory")?,
            last_modified: row.get("last_modified")?,
            last_position: Duration::from_secs(last_position_u64),
        })
    }
}

/// A struct representing a [`Track`](Track) in the database to be inserted
///
/// This is required as some fields are auto-generated by the database compared to [`TrackDB`]
#[derive(Clone, Debug)]
pub struct TrackDBInsertable<'a> {
    // generated by the database
    // pub id: u64,
    pub artist: &'a str,
    pub title: &'a str,
    pub album: &'a str,
    pub genre: &'a str,
    pub file: &'a str,
    pub duration: Duration,
    pub name: &'a str,
    pub ext: &'a str,
    pub directory: &'a str,
    pub last_modified: String,
    pub last_position: Duration,
}

const_str! {
    UNKNOWN_ARTIST "Unknown Artist",
    UNKNOWN_TITLE "Unknown Title",
    UNKNOWN_ALBUM "empty",
    UNKNOWN_GENRE "no type",
    UNKNOWN_FILE "Unknown File",
}

impl<'a> From<&'a Track> for TrackDBInsertable<'a> {
    fn from(value: &'a Track) -> Self {
        Self {
            artist: value.artist().unwrap_or(UNKNOWN_ARTIST),
            title: value.title().unwrap_or(UNKNOWN_TITLE),
            album: value.album().unwrap_or(UNKNOWN_ALBUM),
            genre: value.genre().unwrap_or(UNKNOWN_GENRE),
            file: value.file().unwrap_or(UNKNOWN_FILE),
            duration: value.duration(),
            name: value.name().unwrap_or_default(),
            ext: value.ext().unwrap_or_default(),
            directory: value.directory().unwrap_or_default(),
            last_modified: value
                .last_modified
                .duration_since(UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs()
                .to_string(),
            last_position: Duration::default(),
        }
    }
}

impl TrackDBInsertable<'_> {
    /// Insert the current [`TrackDBInsertable`] into the `tracks` table
    #[inline]
    pub fn insert_track(&self, con: &Connection) -> Result<usize, rusqlite::Error> {
        con.execute(
            "INSERT INTO tracks (artist, title, album, genre, file, duration, name, ext, directory, last_modified, last_position) 
            values (:artist, :title, :album, :genre, :file, :duration, :name, :ext, :directory, :last_modified, :last_position)",
            named_params![
                ":artist": &self.artist,
                ":title": &self.title,
                ":album": &self.album,
                ":genre": &self.genre,
                ":file": &self.file,
                ":duration": &self.duration.as_secs(),
                ":name": &self.name,
                ":ext": &self.ext,
                ":directory": &self.directory,
                ":last_modified": &self.last_modified,
                ":last_position": &self.last_position.as_secs().to_string(),
            ],
        )
    }
}