summaryrefslogtreecommitdiff
path: root/src/db.rs
blob: e42d2353efec2cb7b06949a29da7e739fef7d367 (plain)
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
143
144
145
146
use anyhow::{Ok, Result, anyhow};
use directories::BaseDirs;
use futures_util::StreamExt;
use libsql::{Builder, Connection, params};
use std::{
    ffi::OsStr,
    fmt::Display,
    path::{Path, absolute},
};

use crate::flac;

pub async fn open_db() -> Result<Connection> {
    let conn = if let Some(base_dir) = BaseDirs::new() {
        let db_name = Path::new(base_dir.data_dir()).join("reencoder.db");
        Builder::new_local(db_name).build().await?.connect()?
    } else {
        return Err(anyhow!("Failed to locate data directory"));
    };

    conn.execute("CREATE TABLE IF NOT EXISTS flacs (path TEXT PRIMARY KEY, vendor TEXT, toencode BOOLEAN NOT NULL)", ()).await?;

    Ok(conn)
}

#[derive(Debug)]
enum Errors {
    EmptyQuery,
}

impl Display for Errors {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Errors::EmptyQuery => write!(f, "Empty query"),
        }
    }
}

trait Reencoder {
    async fn insert_file(&self, filename: &impl AsRef<OsStr>) -> Result<()>;
    async fn update_file(&self, filename: &impl AsRef<OsStr>) -> Result<()>;
    async fn get_files_toencode(&self) -> Result<Vec<String>>;
}

impl Reencoder for Connection {
    async fn insert_file(&self, filename: &impl AsRef<OsStr>) -> Result<()> {
        let file = Path::new(filename);
        let vendor = flac::get_vendor(file);
        let toencode = !matches!(vendor.as_str(), flac::CURRENT_VENDOR);

        let abs_filename = absolute(file)?;

        self.execute(
            "INSERT INTO flacs (path, vendor, toencode) VALUES (?1, ?2, ?3)",
            params![abs_filename.to_str().unwrap(), vendor, toencode],
        )
        .await?;

        Ok(())
    }

    async fn update_file(&self, filename: &impl AsRef<OsStr>) -> Result<()> {
        let abs_filename = absolute(Path::new(filename))?;

        self.execute(
            "REPLACE INTO flacs (path, vendor, toencode) VALUES (?1, ?2, ?3)",
            params![abs_filename.to_str().unwrap(), flac::CURRENT_VENDOR, false],
        )
        .await?;

        Ok(())
    }

    async fn get_files_toencode(&self) -> Result<Vec<String>> {
        let rows = self
            .query("SELECT path FROM flacs WHERE toencode", ())
            .await?;
        if rows.column_count() == 0 {
            return Err(anyhow!(Errors::EmptyQuery));
        };

        let filenames = rows
            .into_stream()
            .map(|row| row.unwrap().get_str(0).unwrap().to_string())
            .collect::<Vec<String>>()
            .await;

        Ok(filenames)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    async fn dummy_db(name: impl AsRef<Path>) -> Connection {
        let conn = Builder::new_local(name)
            .build()
            .await
            .unwrap()
            .connect()
            .unwrap();
        conn.execute("CREATE TABLE IF NOT EXISTS flacs (path TEXT PRIMARY KEY, vendor TEXT, toencode BOOLEAN NOT NULL)", ()).await.unwrap();
        conn
    }

    #[tokio::test]
    async fn check_localfiles() {
        let dbname = String::from("temp1.db");
        let filenames = ["16bit.flac", "24bit.flac", "32bit.flac"];
        let conn = dummy_db(&dbname).await;
        for file in filenames {
            let _ = conn.insert_file(&file.to_string()).await;
        }
        let returned = conn.get_files_toencode().await.unwrap();
        std::fs::remove_file(dbname).unwrap();
        assert!(returned.is_empty())
    }

    #[tokio::test]
    async fn check_update() {
        let dbname = String::from("temp2.db");
        let filenames = ["16bit.flac", "24bit.flac", "32bit.flac"];
        let conn = dummy_db(&dbname).await;
        for file in filenames {
            let _ = conn.insert_file(&file.to_string()).await;
        }

        let _ = conn
            .execute(
                "REPLACE INTO flacs (path, vendor, toencode) VALUES (?1, ?2, ?3)",
                params![
                    absolute(Path::new("16bit.flac")).unwrap().to_str(),
                    "",
                    true
                ],
            )
            .await;

        conn.update_file(&"16bit.flac".to_string()).await.unwrap();

        let returned = conn.get_files_toencode().await.unwrap();
        std::fs::remove_file(dbname).unwrap();
        assert!(returned.is_empty())
    }
}