diff --git a/.gitignore b/.gitignore index c6637ef..af8e43d 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,6 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test +# SQLite +*.sqlite3 +*.db diff --git a/Cargo.toml b/Cargo.toml index 9090ad4..b73203d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,25 @@ [package] -name = "curators" -version = "0.1.0" -authors = ["Micha Glave "] +authors = ["Micha Glave "] +description = "An image gallery, defined by plain files." edition = "2018" exclude = [".travis.yml", ".gitignore", "test-data/**"] -description = "Image Gallery. Defined by plain files." +name = "curators" +readme = "README.md" +version = "0.1.0" [dependencies] -toml = "0.5" +actix-rt = "1.0" +actix-web = "2.0" +diesel = { version ="1.4", features = ["sqlite", "r2d2", "uuid", "chrono"] } +uuid = { version="0.7", features = ["v5"] } +dotenv = "0.15" +env_logger = "0.7" +futures = "0.3" +kamadak-exif = "0.5" +listenfd = "0.3" structopt = "0.3" -#exempi = "2.5.0" strum = "0.18.0" strum_macros = "0.18.0" -kamadak-exif = "0.5" +toml = "0.5" +chrono = "0.4" +anyhow = "1" diff --git a/README.md b/README.md index 32db490..9ce5dbc 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,27 @@ -# curators +# curators - Picture data long-term archive Image Gallery. Defined by plain files. +## Problem definition: + +Managing many images from different sources is a challenge. Either there is a quantity limit +by storage space or program (iPhoto), vendor lock or security issues throughout the environment. + + ## Goal: -Image management for multiple (competing) users. Purely file-based, non-destructive, based on standards (as much as possible). Using +Image management for multiple (competing) users. Purely file-based, non-destructive, based on standards (as much as +possible). Using * [Exif](https://de.wikipedia.org/wiki/Exchangeable_Image_File_Format) * [IPTC](https://de.wikipedia.org/wiki/IPTC-IIM-Standard) -* [XMP](https://de.wikipedia.org/wiki/Extensible_Metadata_Platform) +* [XMP](https://de.wikipedia.org/wiki/Extensible_Metadata_Platform) + + + +## Principles: + +* Original file is not changed. +* Meta-data are next to the image file + * description of contents: people / faces, place, tags + * Authorization + * Reshaping (crop, color correction) diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..92267c8 --- /dev/null +++ b/diesel.toml @@ -0,0 +1,5 @@ +# For documentation on how to configure this file, +# see diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" diff --git a/migrations/.gitkeep b/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/migrations/2020-06-15-200823_images/down.sql b/migrations/2020-06-15-200823_images/down.sql new file mode 100644 index 0000000..c940cf1 --- /dev/null +++ b/migrations/2020-06-15-200823_images/down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS images; diff --git a/migrations/2020-06-15-200823_images/up.sql b/migrations/2020-06-15-200823_images/up.sql new file mode 100644 index 0000000..797b144 --- /dev/null +++ b/migrations/2020-06-15-200823_images/up.sql @@ -0,0 +1,12 @@ +CREATE TABLE images ( + id VARCHAR(40) PRIMARY KEY NOT NULL, + path VARCHAR(500) NOT NULL, + title VARCHAR(200), + 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 ); \ No newline at end of file diff --git a/src/bin/listdb.rs b/src/bin/listdb.rs new file mode 100644 index 0000000..cbbf706 --- /dev/null +++ b/src/bin/listdb.rs @@ -0,0 +1,18 @@ +extern crate curators; + +use anyhow::{Context, Result}; +use curators::database::*; +use curators::models::Image; +use std::path::Path; + +fn main() { + let results = load_images(); + println!("Displaying {} images", results.len()); + for img in results { + println!("{:?} ", img); + } + 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)); +} diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..b802fb1 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,26 @@ +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(); + + 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 { + use crate::schema::images::dsl::*; + + let connection = establish_connection(); + images + .filter(published.eq(false)) + .limit(50) + .load::(&connection) + .expect("Error loading images") +} diff --git a/src/handling.rs b/src/handling.rs new file mode 100644 index 0000000..cb5a9f7 --- /dev/null +++ b/src/handling.rs @@ -0,0 +1,24 @@ +use snafu::{ensure, Backtrace, ErrorCompat, ResultExt, Snafu}; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("Could not open config from {}: {}", filename.display(), source))] + OpenConfig { + filename: PathBuf, + source: std::io::Error, + }, + #[snafu(display("Could not save config to {}: {}", filename.display(), source))] + SaveConfig { + filename: PathBuf, + source: std::io::Error, + }, + #[snafu(display("Could not read file {}: {}", filename.display(), source))] + OpenFile { + filename: PathBuf, + source: std::io::Error, + }, +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b66232e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +#[macro_use] +extern crate diesel; +extern crate anyhow; +extern crate dotenv; + +pub mod database; +pub mod models; +pub mod schema; diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..61edb0a --- /dev/null +++ b/src/models.rs @@ -0,0 +1,53 @@ +use crate::schema::images; + +use anyhow::{Context, Result}; +use chrono::NaiveDateTime; +use std::ffi::OsString; +use std::{ + fs, + path::{Path, PathBuf}, + time::{SystemTime, UNIX_EPOCH}, +}; +use uuid::Uuid; + +#[derive(Debug, Clone, Queryable, Insertable, Identifiable)] +pub struct Image { + pub id: String, + pub path: String, + pub title: Option, + pub last_changed: NaiveDateTime, + pub published: bool, +} + +impl Image { + pub fn try_from(path: &Path) -> anyhow::Result { + 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() + )); + let uuid = Uuid::new_v5(&Uuid::NAMESPACE_URL, &path_str.as_bytes()); + let metadata = abs_path + .metadata() + .context(format!("Unable to load Metadata for: {}", path.display()))?; + let time = match metadata.modified() { + Ok(t) => t, + Err(_) => metadata.created().context(format!( + "Unable to read created-timestamp of: {}", + path.display() + ))?, + }; + let mtime = time.duration_since(UNIX_EPOCH).unwrap(); + Ok(Image { + id: uuid.to_string(), + path: path_str.to_string(), + title: None, + last_changed: NaiveDateTime::from_timestamp( + mtime.as_secs() as i64, + mtime.subsec_nanos() as u32, + ), + published: false, + }) + } +} diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..5150dee --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,9 @@ +table! { + images (id) { + id -> Text, + path -> Text, + title -> Nullable, + last_changed -> Timestamp, + published -> Bool, + } +}