diff options
| author | Jakob L. Kreuze <zerodaysfordays@sdf.org> | 2020-05-19 21:37:28 -0400 |
|---|---|---|
| committer | Jakob L. Kreuze <zerodaysfordays@sdf.org> | 2020-05-21 17:04:51 -0400 |
| commit | 6c8066dd171bd22a03e371dcfa06851fa1366e27 (patch) | |
| tree | 97942ae700c417fdd1dbeab2885b8904808e56bc /src | |
| parent | 8de57fec67898ceeebd7620b1b5ba6bd423fb2fd (diff) | |
Implement the dreaded `images_by_tags`.
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 104 |
1 files changed, 102 insertions, 2 deletions
diff --git a/src/main.rs b/src/main.rs index 0809159..4b3fcc1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,7 @@ struct TagBase { conn: Connection, } +/// Return a base64-encoded BLAKE2b hash identifying the file at `path`. fn hash_file(path: &Path) -> Result<String> { let mut file = std::fs::File::open(path)?; let mut hasher = Blake2b::new(); @@ -44,6 +45,13 @@ fn hash_file(path: &Path) -> Result<String> { Ok(base64::encode(&hasher.result())) } +struct Image { + id: i64, + blake2: String, + filename: String, + orig_dir: String, +} + impl TagBase { /// Instantiate a new `TagBase` whose backing database is at `path`. pub fn new(path: &str) -> Result<Self> { @@ -129,8 +137,7 @@ impl TagBase { Ok(self .conn .prepare("SELECT * FROM tags WHERE name = ?")? - .query_row(params![tag], |row| Ok(row.get::<_, String>(0)))?? - .parse::<i64>()?) + .query_row(params![tag], |row| Ok(row.get::<_, i64>(0)))??) } /// Associate `tag` with the image specified by `image_id`. @@ -157,6 +164,72 @@ impl TagBase { .filter_map(|tag| tag.ok()) .collect()) } + + /// Return the intersection of the sets of images matching each of `tags`. + fn images_by_tags(&self, tags: &[&str]) -> Result<Vec<Image>> { + let ids = tags.iter().map(|tag| self.tag_id(tag)); + let mut tags = Vec::from(tags); + tags.dedup(); + + if ids.len() != tags.len() { + return Ok(vec![]); + } + + let union = if tags.len() == 0 { + String::from( + "INNER JOIN mapping + ON images.id = mapping.image", + ) + } else { + (1..tags.len() + 1) + .zip(tags) + .map(|pair| { + let (n, tag) = pair; + format!( + "INNER JOIN mapping as m{n} + ON images.id = m{n}.image + AND {id} = m{n}.tag", + n = n, + id = tag + ) + }) + .collect::<Vec<String>>() + .join("\n") + }; + // WHERE id > {lastId} + // LIMIT 50 + let query = format!( + "SELECT id, blake2, filename, orig_dir + FROM images + {union} + GROUP BY id + ORDER BY id DESC", + union = union + ); + + Ok(self + .conn + .prepare(&query)? + .query_map( + params![], + |row| -> rusqlite::Result<(i64, String, String, String)> { + Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)) + }, + )? + .filter_map(|res| { + res.map(|pair| { + let (id, blake2, filename, orig_dir) = pair; + Image { + id, + blake2, + filename, + orig_dir, + } + }) + .ok() + }) + .collect()) + } } #[cfg(test)] @@ -200,6 +273,13 @@ mod tests { } #[test] + fn add_tag() { + let tb = TagBase::make_temporary().unwrap(); + tb.add_tag("test").unwrap(); + tb.tag_id("test").unwrap(); + } + + #[test] fn tag_image() { let tb = TagBase::make_temporary().unwrap(); let id = tb @@ -211,6 +291,26 @@ mod tests { assert_eq!(tags.len(), 1); assert!(tags.contains(&String::from("test"))); } + + #[test] + fn query_by_tag() { + let tb = TagBase::make_temporary().unwrap(); + let ids: Vec<i64> = (1..4) + .map(|i| { + let i = i.to_string(); + tb.import_image(Path::new(&i), &i).unwrap() + }) + .collect(); + tb.add_tag("1").unwrap(); + tb.add_tag("2").unwrap(); + tb.tag_image(ids[0], "1").unwrap(); + tb.tag_image(ids[1], "2").unwrap(); + tb.tag_image(ids[2], "1").unwrap(); + tb.tag_image(ids[2], "2").unwrap(); + assert_eq!(tb.images_by_tags(&["1"]).unwrap().len(), 2); + assert_eq!(tb.images_by_tags(&["2"]).unwrap().len(), 2); + assert_eq!(tb.images_by_tags(&["1", "2"]).unwrap().len(), 1); + } } fn main() { |