summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJakob L. Kreuze <zerodaysfordays@sdf.org>2020-05-19 21:37:28 -0400
committerJakob L. Kreuze <zerodaysfordays@sdf.org>2020-05-21 17:04:51 -0400
commit6c8066dd171bd22a03e371dcfa06851fa1366e27 (patch)
tree97942ae700c417fdd1dbeab2885b8904808e56bc /src
parent8de57fec67898ceeebd7620b1b5ba6bd423fb2fd (diff)
Implement the dreaded `images_by_tags`.
Diffstat (limited to 'src')
-rw-r--r--src/main.rs104
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() {