Db-Relation Tag
added.
This commit is contained in:
parent
3eb6e425f5
commit
6ae5fbf334
@ -2,11 +2,14 @@ CREATE TABLE images (
|
|||||||
id VARCHAR(40) PRIMARY KEY NOT NULL,
|
id VARCHAR(40) PRIMARY KEY NOT NULL,
|
||||||
path VARCHAR(500) NOT NULL,
|
path VARCHAR(500) NOT NULL,
|
||||||
title VARCHAR(200),
|
title VARCHAR(200),
|
||||||
|
image_type INTEGER NOT NULL,
|
||||||
last_changed TIMESTAMP NOT NULL,
|
last_changed TIMESTAMP NOT NULL,
|
||||||
published BOOLEAN NOT NULL DEFAULT 'f'
|
published BOOLEAN NOT NULL DEFAULT 'f'
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO images (id, path, title, last_changed, published) VALUES
|
CREATE INDEX image_path_idx ON images (path);
|
||||||
('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
|
INSERT INTO images (id, path, title, image_type, last_changed, published) VALUES
|
||||||
('ea01f5cd-a532-4232-810c-bae977cc4336', './Canon_40D.jpg', 'Canon 40d', '2020-05-27 13:33:56.747473', false );
|
('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;
|
extern crate curators;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use curators::database::*;
|
use curators::database::*;
|
||||||
use curators::models::Image;
|
use curators::models::{Image, Tag};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
fn main() {
|
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());
|
println!("Displaying {} images", results.len());
|
||||||
for img in results {
|
for img in results {
|
||||||
println!("{:?} ", img);
|
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)]
|
#[derive(EnumString, Debug)]
|
||||||
enum Cmd {
|
enum Cmd {
|
||||||
#[strum(serialize = "read", serialize = "r")]
|
#[strum(
|
||||||
|
serialize = "read",
|
||||||
|
serialize = "r",
|
||||||
|
serialize = "get",
|
||||||
|
serialize = "g"
|
||||||
|
)]
|
||||||
Read,
|
Read,
|
||||||
#[strum(
|
#[strum(
|
||||||
serialize = "write",
|
serialize = "write",
|
@ -2,25 +2,51 @@ use diesel;
|
|||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::sqlite::SqliteConnection;
|
use diesel::sqlite::SqliteConnection;
|
||||||
|
|
||||||
use crate::models::Image;
|
|
||||||
use dotenv::dotenv;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
pub fn establish_connection() -> SqliteConnection {
|
use crate::models::{Image, Tag};
|
||||||
dotenv().ok();
|
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");
|
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||||
SqliteConnection::establish(&database_url)
|
SqliteConnection::establish(&database_url)
|
||||||
.expect(&format!("Error connecting to {}", database_url))
|
.expect(&format!("Error connecting to {}", database_url))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_images() -> Vec<Image> {
|
pub fn load_images(conn: &Conn, start: i64, limit: i64) -> Vec<Image> {
|
||||||
use crate::schema::images::dsl::*;
|
|
||||||
|
|
||||||
let connection = establish_connection();
|
|
||||||
images
|
images
|
||||||
.filter(published.eq(false))
|
.filter(published.eq(false))
|
||||||
.limit(50)
|
.offset(start)
|
||||||
.load::<Image>(&connection)
|
.limit(limit)
|
||||||
|
.load::<Image>(conn)
|
||||||
.expect("Error loading images")
|
.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 anyhow::{Context, Result};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use std::ffi::OsString;
|
use diesel::{
|
||||||
use std::{
|
deserialize::FromSqlRow,
|
||||||
fs,
|
expression::{helper_types::AsExprOf, AsExpression},
|
||||||
path::{Path, PathBuf},
|
row::Row,
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
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;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Queryable, Insertable, Identifiable)]
|
#[derive(Debug, Clone, Queryable, Identifiable, Insertable)]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
|
pub image_type: ImageType,
|
||||||
pub last_changed: NaiveDateTime,
|
pub last_changed: NaiveDateTime,
|
||||||
pub published: bool,
|
pub published: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
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 abs_path = path.canonicalize().context("Path not valid!")?;
|
||||||
let os_path = abs_path.clone().into_os_string();
|
let os_path = abs_path.clone().into_os_string();
|
||||||
let path_str = os_path.into_string().expect(&format!(
|
let path_str = os_path.into_string().expect(&format!(
|
||||||
@ -39,10 +55,12 @@ impl Image {
|
|||||||
))?,
|
))?,
|
||||||
};
|
};
|
||||||
let mtime = time.duration_since(UNIX_EPOCH).unwrap();
|
let mtime = time.duration_since(UNIX_EPOCH).unwrap();
|
||||||
|
let image_type = ImageType::from_path(path);
|
||||||
Ok(Image {
|
Ok(Image {
|
||||||
id: uuid.to_string(),
|
id: uuid.to_string(),
|
||||||
path: path_str.to_string(),
|
path: path_str.to_string(),
|
||||||
title: None,
|
title: None,
|
||||||
|
image_type,
|
||||||
last_changed: NaiveDateTime::from_timestamp(
|
last_changed: NaiveDateTime::from_timestamp(
|
||||||
mtime.as_secs() as i64,
|
mtime.as_secs() as i64,
|
||||||
mtime.subsec_nanos() as u32,
|
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,
|
id -> Text,
|
||||||
path -> Text,
|
path -> Text,
|
||||||
title -> Nullable<Text>,
|
title -> Nullable<Text>,
|
||||||
|
image_type -> Integer,
|
||||||
last_changed -> Timestamp,
|
last_changed -> Timestamp,
|
||||||
published -> Bool,
|
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,
|
||||||
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user