summaryrefslogtreecommitdiff
path: root/src/main.rs
blob: 3e66f9a1bb9b4bcd6d3075123ca149392f54f7ac (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
#[macro_use]
extern crate anyhow;

use anyhow::Result;
use blake2::{Blake2b, Digest};
use rusqlite::{params, Connection};
use std::io;
use std::path::Path;

/// Return a hex-encoded string representing the contents of `hash`.
fn hexlify(hash: &[u8]) -> String {
    hash.iter()
        .map(|b| format!("{:x}", b))
        .collect::<Vec<String>>()
        .join("")
}

#[derive(Debug)]
struct TagBase {
    conn: Connection,
}

impl TagBase {
    /// Instantiate a new `TagBase` whose backing database is at `path`.
    fn new(path: &str) -> Result<Self> {
        let conn = Connection::open(path)?;
        conn.execute(
            "CREATE TABLE IF NOT EXISTS images (
                 id       INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
                 blake2   TEXT NOT NULL,
                 filename TEXT NOT NULL,
                 orig_dir TEXT NOT NULL
             )",
            params![],
        )?;
        conn.execute(
            "CREATE TABLE IF NOT EXISTS tags (
                 id   INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
                 name TEXT NOT NULL
             )",
            params![],
        )?;
        conn.execute(
            "CREATE TABLE IF NOT EXISTS mapping (
                 image  INTEGER NOT NULL,
                 tag    INTEGER NOT NULL
             )",
            params![],
        )?;
        Ok(TagBase { conn })
    }

    /// Insert the image at `path` into the data.
    fn import_image(&self, path: &Path) -> Result<i64> {
        if path.is_dir() {
            bail!("`path` does not name a file.");
        }

        let mut file = std::fs::File::open(path)?;
        let mut hasher = Blake2b::new();
        let _ = io::copy(&mut file, &mut hasher)?;
        let hash = base64::encode(&hasher.result());

        let file_name = path
            .file_name()
            .and_then(|os| os.to_str())
            .ok_or(anyhow!("Couldn't parse file name."))?;
        let path = path.to_str().ok_or(anyhow!("Couldn't parse path."))?;

        let split_index = path.len() - file_name.len();
        let parent_directory = &path[0..split_index];
        let id = self
            .conn
            .prepare("INSERT INTO images (blake2, filename, orig_dir) VALUES(?,?,?)")?
            .insert(params![hash, file_name, parent_directory])?;
        Ok(id)
    }

    /// Insert `tag` into the database, if it does not already exist.
    fn add_tag(&self, tag: &str) -> Result<()> {
        let exists = self
            .conn
            .prepare("SELECT * FROM tags WHERE name = ?")?
            .exists(params![tag]);
        if let Err(_) = exists {
            self.conn
                .execute("INSERT INTO tags (name) VALUES(?)", params![tag])?;
        }
        Ok(())
    }

    /// Associate `tag` with the image specified by `image_id`.
    fn tag_image(&self, image_id: i64, tag: &str) -> Result<()> {
        let tag_id: i64 = self
            .conn
            .prepare("SELECT * FROM tags WHERE name = ?")?
            .query_row(params![tag], |row| Ok(row.get(0)))??;
        self.conn.execute(
            "INSERT INTO mapping (image, tag) VALUES(?, ?)",
            params![image_id, tag_id],
        )?;
        Ok(())
    }

    fn tags_for_image(&self, image_id: i64) -> Result<Vec<String>> {
        Ok(self
            .conn
            .prepare(
                "SELECT tags.name, COUNT(mapping.tag) as tag_count
                      FROM mapping
                      INNER JOIN images ON images.id = ?
                      INNER JOIN tags
                      GROUP BY tags.name",
            )?
            .query_map(params![image_id], |row| row.get(0))?
            .filter_map(|tag| tag.ok())
            .collect())
    }
}

fn main() {
    let tb = TagBase::new("/tmp/test.db").unwrap();
    tb.import_image(Path::new("/home/jakob/Sync/06fc9ae71549eb4d.jpeg"))
        .unwrap();
    println!("{:?}", tb);
}