Db-Relation Tag added.
				
					
				
			This commit is contained in:
		| @@ -2,11 +2,14 @@ CREATE TABLE images ( | ||||
|   id VARCHAR(40) PRIMARY KEY NOT NULL, | ||||
|   path VARCHAR(500) NOT NULL, | ||||
|   title VARCHAR(200), | ||||
|   image_type INTEGER NOT NULL, | ||||
|   last_changed TIMESTAMP NOT NULL, | ||||
|   published BOOLEAN NOT NULL DEFAULT 'f' | ||||
| ); | ||||
|  | ||||
| INSERT INTO images (id, path, title, last_changed, published) VALUES | ||||
| ('8e153cab-7dc9-46c7-9a72-91e56ac174ca', './BlueSquare.jpg', 'Blaues Quadrat', '2020-05-27 13:33:56.747473', false ); | ||||
| INSERT INTO images (id, path, title, last_changed, published) VALUES | ||||
| ('ea01f5cd-a532-4232-810c-bae977cc4336', './Canon_40D.jpg', 'Canon 40d', '2020-05-27 13:33:56.747473', false ); | ||||
| CREATE INDEX image_path_idx ON images (path); | ||||
|  | ||||
| INSERT INTO images (id, path, title, image_type, last_changed, published) VALUES | ||||
| ('8e153cab-7dc9-46c7-9a72-91e56ac174ca', './BlueSquare.jpg', 'Blaues Quadrat', 1, '2020-05-27 13:33:56.747473', false ); | ||||
| INSERT INTO images (id, path, title, image_type, last_changed, published) VALUES | ||||
| ('ea01f5cd-a532-4232-810c-bae977cc4336', './Canon_40D.jpg', 'Canon 40d', 1, '2020-05-27 13:33:56.747473', false ); | ||||
							
								
								
									
										2
									
								
								migrations/2020-07-01-172642_tags/down.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								migrations/2020-07-01-172642_tags/down.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| -- This file should undo anything in `up.sql` | ||||
| DROP TABLE IF EXISTS tags; | ||||
							
								
								
									
										8
									
								
								migrations/2020-07-01-172642_tags/up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								migrations/2020-07-01-172642_tags/up.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| -- Your SQL goes here | ||||
| CREATE TABLE tags | ||||
| ( | ||||
|     image_id VARCHAR(40)  NOT NULL REFERENCES images (id), | ||||
|     tag_type  VARCHAR(20)  NOT NULL, | ||||
|     name     VARCHAR(500) NOT NULL, | ||||
|     PRIMARY KEY (image_id, name) | ||||
| ); | ||||
| @@ -1,18 +1,33 @@ | ||||
| extern crate curators; | ||||
|  | ||||
| use anyhow::{Context, Result}; | ||||
| use curators::database::*; | ||||
| use curators::models::Image; | ||||
| use curators::models::{Image, Tag}; | ||||
| use std::path::Path; | ||||
|  | ||||
| fn main() { | ||||
|     let results = load_images(); | ||||
|     dotenv::dotenv().ok(); | ||||
|     let conn = establish_connection(); | ||||
|     let results = load_images(&conn, 0, 20); | ||||
|     println!("Displaying {} images", results.len()); | ||||
|     for img in results { | ||||
|         println!("{:?} ", img); | ||||
|         delete_image(&conn, img.id).ok(); | ||||
|     } | ||||
|     let mut images = Vec::new(); | ||||
|  | ||||
|     let p = Path::new(r"./BlueSquare.jpg"); | ||||
|     images.push(Image::try_from_path(&p).unwrap()); | ||||
|     let mut tags = Tag::create_tags(&p); | ||||
|     let p = Path::new(r"./Canon_40D.jpg"); | ||||
|  | ||||
|     images.push(Image::try_from_path(&p).unwrap()); | ||||
|     tags.append(&mut Tag::create_tags(&p)); | ||||
|  | ||||
|     for i in images { | ||||
|         println!("{:?}", &i); | ||||
|         save_image(&conn, &i).ok(); | ||||
|     } | ||||
|     for t in tags { | ||||
|         println!("{:?} = {:?}", &t, save_tags(&conn, &t)); | ||||
|     } | ||||
|     let p = Path::new(r"./BlueSquare.jpg").canonicalize().unwrap(); | ||||
|     println!("{:?}", Image::try_from(&p)); | ||||
|     let p = Path::new(r"./Canon_40D.jpg").canonicalize().unwrap(); | ||||
|     println!("{:?}", Image::try_from(&p)); | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,12 @@ use strum_macros::EnumString; | ||||
| 
 | ||||
| #[derive(EnumString, Debug)] | ||||
| enum Cmd { | ||||
|     #[strum(serialize = "read", serialize = "r")] | ||||
|     #[strum(
 | ||||
|         serialize = "read", | ||||
|         serialize = "r", | ||||
|         serialize = "get", | ||||
|         serialize = "g" | ||||
|     )] | ||||
|     Read, | ||||
|     #[strum(
 | ||||
|         serialize = "write", | ||||
| @@ -2,25 +2,51 @@ use diesel; | ||||
| use diesel::prelude::*; | ||||
| use diesel::sqlite::SqliteConnection; | ||||
|  | ||||
| use crate::models::Image; | ||||
| use dotenv::dotenv; | ||||
| use std::env; | ||||
|  | ||||
| pub fn establish_connection() -> SqliteConnection { | ||||
|     dotenv().ok(); | ||||
| use crate::models::{Image, Tag}; | ||||
| use crate::schema::images::dsl::*; | ||||
| use crate::schema::tags::dsl::*; | ||||
|  | ||||
| type Conn = SqliteConnection; | ||||
|  | ||||
| pub fn establish_connection() -> Conn { | ||||
|     let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); | ||||
|     SqliteConnection::establish(&database_url) | ||||
|         .expect(&format!("Error connecting to {}", database_url)) | ||||
| } | ||||
|  | ||||
| pub fn load_images() -> Vec<Image> { | ||||
|     use crate::schema::images::dsl::*; | ||||
|  | ||||
|     let connection = establish_connection(); | ||||
| pub fn load_images(conn: &Conn, start: i64, limit: i64) -> Vec<Image> { | ||||
|     images | ||||
|         .filter(published.eq(false)) | ||||
|         .limit(50) | ||||
|         .load::<Image>(&connection) | ||||
|         .offset(start) | ||||
|         .limit(limit) | ||||
|         .load::<Image>(conn) | ||||
|         .expect("Error loading images") | ||||
| } | ||||
|  | ||||
| pub fn load_tags<T>(conn: &Conn, uuid: T) -> Vec<Tag> | ||||
| where | ||||
|     T: Into<String>, | ||||
| { | ||||
|     tags.filter(image_id.eq(uuid.into())) | ||||
|         .load::<Tag>(conn) | ||||
|         .expect("Error loading tags.") | ||||
| } | ||||
|  | ||||
| pub fn save_image(conn: &Conn, img: &Image) -> QueryResult<usize> { | ||||
|     diesel::insert_into(images).values(img).execute(conn) | ||||
| } | ||||
|  | ||||
| pub fn save_tags(conn: &Conn, tag: &Tag) -> QueryResult<usize> { | ||||
|     diesel::insert_into(tags).values(tag).execute(conn) | ||||
| } | ||||
|  | ||||
| pub fn delete_image<T>(conn: &Conn, uuid: T) -> QueryResult<usize> | ||||
| where | ||||
|     T: Into<String>, | ||||
| { | ||||
|     let uuid_str = uuid.into(); | ||||
|     diesel::delete(tags.filter(image_id.eq(&uuid_str))).execute(conn)?; | ||||
|     diesel::delete(images.filter(id.eq(&uuid_str))).execute(conn) | ||||
| } | ||||
|   | ||||
							
								
								
									
										214
									
								
								src/models.rs
									
									
									
									
									
								
							
							
						
						
									
										214
									
								
								src/models.rs
									
									
									
									
									
								
							| @@ -1,26 +1,42 @@ | ||||
| use crate::schema::images; | ||||
|  | ||||
| use crate::schema::{images, tags}; | ||||
| use anyhow::{Context, Result}; | ||||
| use chrono::NaiveDateTime; | ||||
| use std::ffi::OsString; | ||||
| use std::{ | ||||
|     fs, | ||||
|     path::{Path, PathBuf}, | ||||
|     time::{SystemTime, UNIX_EPOCH}, | ||||
| use diesel::{ | ||||
|     deserialize::FromSqlRow, | ||||
|     expression::{helper_types::AsExprOf, AsExpression}, | ||||
|     row::Row, | ||||
|     sql_types::{Integer, Text}, | ||||
|     sqlite::Sqlite, | ||||
|     Queryable, | ||||
| }; | ||||
| use std::convert::TryFrom; | ||||
| use std::error::Error; | ||||
| use std::{path::Path, time::UNIX_EPOCH}; | ||||
| use strum_macros::{Display, EnumString}; | ||||
| use uuid::Uuid; | ||||
|  | ||||
| #[derive(Debug, Clone, Queryable, Insertable, Identifiable)] | ||||
| #[derive(Debug, Clone, Queryable, Identifiable, Insertable)] | ||||
| pub struct Image { | ||||
|     pub id: String, | ||||
|     pub path: String, | ||||
|     pub title: Option<String>, | ||||
|     pub image_type: ImageType, | ||||
|     pub last_changed: NaiveDateTime, | ||||
|     pub published: bool, | ||||
| } | ||||
|  | ||||
| impl Image { | ||||
|     pub fn try_from(path: &Path) -> anyhow::Result<Self> { | ||||
|     pub fn create_uuid(path: &Path) -> anyhow::Result<Uuid> { | ||||
|         let abs_path = path.canonicalize().context("Path not valid!")?; | ||||
|         let os_path = abs_path.clone().into_os_string(); | ||||
|         let path_str = os_path.into_string().expect(&format!( | ||||
|             "Converting path-name problem! {:?}", | ||||
|             path.display() | ||||
|         )); | ||||
|         Ok(Uuid::new_v5(&Uuid::NAMESPACE_URL, &path_str.as_bytes())) | ||||
|     } | ||||
|  | ||||
|     pub fn try_from_path(path: &Path) -> anyhow::Result<Self> { | ||||
|         let abs_path = path.canonicalize().context("Path not valid!")?; | ||||
|         let os_path = abs_path.clone().into_os_string(); | ||||
|         let path_str = os_path.into_string().expect(&format!( | ||||
| @@ -39,10 +55,12 @@ impl Image { | ||||
|             ))?, | ||||
|         }; | ||||
|         let mtime = time.duration_since(UNIX_EPOCH).unwrap(); | ||||
|         let image_type = ImageType::from_path(path); | ||||
|         Ok(Image { | ||||
|             id: uuid.to_string(), | ||||
|             path: path_str.to_string(), | ||||
|             title: None, | ||||
|             image_type, | ||||
|             last_changed: NaiveDateTime::from_timestamp( | ||||
|                 mtime.as_secs() as i64, | ||||
|                 mtime.subsec_nanos() as u32, | ||||
| @@ -51,3 +69,181 @@ impl Image { | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Queryable, Identifiable, Insertable)] | ||||
| #[primary_key(image_id, name)] | ||||
| pub struct Tag { | ||||
|     pub image_id: String, | ||||
|     pub tag_type: TagType, | ||||
|     pub name: String, | ||||
| } | ||||
|  | ||||
| impl Tag { | ||||
|     pub fn create_tags(rel_path: &Path) -> Vec<Self> { | ||||
|         match Image::create_uuid(rel_path) { | ||||
|             Ok(uuid) => { | ||||
|                 let image_id = uuid.to_string(); | ||||
|                 rel_path | ||||
|                     .ancestors() | ||||
|                     .filter(|&p| p.display().to_string().len() > 0) | ||||
|                     .map(|p: &Path| Self { | ||||
|                         image_id: image_id.clone(), | ||||
|                         tag_type: TagType::Path, | ||||
|                         name: p.display().to_string(), | ||||
|                     }) | ||||
|                     .collect() | ||||
|             } | ||||
|             Err(_) => vec![], | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<&str> for TagType { | ||||
|     type Error = String; | ||||
|  | ||||
|     fn try_from(value: &str) -> Result<Self, Self::Error> { | ||||
|         match value { | ||||
|             "Location" => Ok(TagType::Location), | ||||
|             "Path" => Ok(TagType::Path), | ||||
|             "Face" => Ok(TagType::Face), | ||||
|             x => Err(format!("unknown TagType '{}'", x)), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy, Debug, Display, Eq, PartialEq, SqlType, AsExpression, FromSqlRow)] | ||||
| pub enum ImageType { | ||||
|     UNKNOWN, | ||||
|     JPEG, | ||||
|     PNG, | ||||
|     GIF, | ||||
|     TIFF, | ||||
|     RAW, | ||||
|     HEIF, | ||||
| } | ||||
|  | ||||
| impl ImageType { | ||||
|     pub fn from_path(path: &Path) -> Self { | ||||
|         let ext = path.extension(); | ||||
|         if ext == None { | ||||
|             return ImageType::UNKNOWN; | ||||
|         } | ||||
|         match path.extension().unwrap().to_str() { | ||||
|             Some("heif") => ImageType::HEIF, | ||||
|             Some("hevc") => ImageType::HEIF, | ||||
|             Some("jpeg") => ImageType::JPEG, | ||||
|             Some("jpg") => ImageType::JPEG, | ||||
|             Some("png") => ImageType::PNG, | ||||
|             Some("raw") => ImageType::RAW, | ||||
|             Some("tif") => ImageType::TIFF, | ||||
|             Some("tiff") => ImageType::TIFF, | ||||
|             _ => ImageType::UNKNOWN, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn to_mimetype(&self) -> &str { | ||||
|         match self { | ||||
|             Self::HEIF => "image/heif", | ||||
|             Self::GIF => "image/gif", | ||||
|             Self::JPEG => "image/jpeg", | ||||
|             Self::PNG => "image/png", | ||||
|             Self::TIFF => "image/tiff", | ||||
|             _ => "application/octet-stream", | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<i32> for ImageType { | ||||
|     fn from(int: i32) -> Self { | ||||
|         match int { | ||||
|             1 => ImageType::JPEG, | ||||
|             2 => ImageType::PNG, | ||||
|             3 => ImageType::GIF, | ||||
|             4 => ImageType::TIFF, | ||||
|             5 => ImageType::RAW, | ||||
|             6 => ImageType::HEIF, | ||||
|             _ => ImageType::UNKNOWN, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<&ImageType> for i32 { | ||||
|     fn from(image_type: &ImageType) -> Self { | ||||
|         match image_type { | ||||
|             ImageType::UNKNOWN => 0, | ||||
|             ImageType::JPEG => 1, | ||||
|             ImageType::PNG => 2, | ||||
|             ImageType::GIF => 3, | ||||
|             ImageType::TIFF => 4, | ||||
|             ImageType::RAW => 5, | ||||
|             ImageType::HEIF => 6, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<ImageType> for i32 { | ||||
|     fn from(image_type: ImageType) -> Self { | ||||
|         i32::from(&image_type) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // traits for diesel | ||||
|  | ||||
| impl diesel::Queryable<Integer, Sqlite> for ImageType { | ||||
|     type Row = i32; | ||||
|     fn build(row: Self::Row) -> Self { | ||||
|         row.into() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl FromSqlRow<Integer, Sqlite> for ImageType { | ||||
|     fn build_from_row<R: Row<Sqlite>>(row: &mut R) -> Result<Self, Box<dyn Error + Send + Sync>> { | ||||
|         Ok(i32::build_from_row(row)?.into()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl AsExpression<Integer> for ImageType { | ||||
|     type Expression = AsExprOf<i32, Integer>; | ||||
|     fn as_expression(self) -> Self::Expression { | ||||
|         <i32 as AsExpression<Integer>>::as_expression(self as i32) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> AsExpression<Integer> for &'a ImageType { | ||||
|     type Expression = AsExprOf<i32, Integer>; | ||||
|     fn as_expression(self) -> Self::Expression { | ||||
|         <i32 as AsExpression<Integer>>::as_expression(self.into()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive( | ||||
|     Clone, Copy, Debug, Display, Eq, EnumString, PartialEq, SqlType, AsExpression, FromSqlRow, | ||||
| )] | ||||
| #[sqlite_type = "Text"] | ||||
| pub enum TagType { | ||||
|     Path, | ||||
|     Face, | ||||
|     Location, | ||||
| } | ||||
|  | ||||
| // traits to help diesel. | ||||
| impl diesel::Queryable<Text, Sqlite> for TagType { | ||||
|     type Row = String; | ||||
|     fn build(row: Self::Row) -> Self { | ||||
|         TagType::try_from(row.as_str()).unwrap() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl AsExpression<Text> for TagType { | ||||
|     type Expression = AsExprOf<String, Text>; | ||||
|     fn as_expression(self) -> Self::Expression { | ||||
|         <String as AsExpression<Text>>::as_expression(self.to_string()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> AsExpression<Text> for &'a TagType { | ||||
|     type Expression = AsExprOf<String, Text>; | ||||
|     fn as_expression(self) -> Self::Expression { | ||||
|         <String as AsExpression<Text>>::as_expression(self.to_string()) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,23 @@ table! { | ||||
|         id -> Text, | ||||
|         path -> Text, | ||||
|         title -> Nullable<Text>, | ||||
|         image_type -> Integer, | ||||
|         last_changed -> Timestamp, | ||||
|         published -> Bool, | ||||
|     } | ||||
| } | ||||
|  | ||||
| table! { | ||||
|     tags (image_id, name) { | ||||
|         image_id -> Text, | ||||
|         tag_type -> Text, | ||||
|         name -> Text, | ||||
|     } | ||||
| } | ||||
|  | ||||
| joinable!(tags -> images (image_id)); | ||||
|  | ||||
| allow_tables_to_appear_in_same_query!( | ||||
|     images, | ||||
|     tags, | ||||
| ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user