Photos can have tags.

Read old data from kphotoalbum.
This commit is contained in:
Rasmus Kaj 2015-11-23 06:48:09 +01:00
parent 704877abd9
commit b1d53c0762
7 changed files with 350 additions and 59 deletions

View File

@ -3,6 +3,14 @@ name = "rphotos"
version = "0.1.0" version = "0.1.0"
authors = ["Rasmus Kaj <kaj@kth.se>"] authors = ["Rasmus Kaj <kaj@kth.se>"]
[[bin]]
name = "rphotos"
path = "src/main.rs"
[[bin]]
name = "readkpa"
path = "src/readkpa.rs"
[dependencies] [dependencies]
nickel = "*" nickel = "*"
env_logger = "*" env_logger = "*"
@ -15,3 +23,4 @@ plugin = "*"
image = "*" image = "*"
hyper = "*" hyper = "*"
time = "*" time = "*"
xml-rs = "*"

15
db/00001_init.sql Normal file
View File

@ -0,0 +1,15 @@
create table public.photo (
id serial primary key,
path varchar(100) unique not null
);
create table public.tag (
id serial primary key,
tag varchar(100) unique not null,
slug varchar(100) unique not null
);
create table public.photo_tag (
photo integer not null references public.photo (id),
tag integer not null references public.tag (id)
);

14
src/env.rs Normal file
View File

@ -0,0 +1,14 @@
use std::env::var;
use std::process::exit;
pub fn dburl() -> String {
let db_var = "RPHOTOS_DB";
match var(db_var) {
Ok(result) => result,
Err(error) => {
println!("A database url needs to be given in env {}: {}",
db_var, error);
exit(1);
}
}
}

View File

@ -9,36 +9,26 @@ extern crate image;
extern crate hyper; extern crate hyper;
extern crate time; extern crate time;
use time::Duration;
use hyper::header::{Expires, HttpDate};
use std::collections::HashMap;
use nickel::{Nickel, Request, Response, Middleware, Continue, MiddlewareResult};
use rustorm::pool::{ManagedPool,Platform};
use rustorm::database::Database;
use rustorm::table::IsTable;
use rustorm::dao::{IsDao, ToValue};
use std::env::var;
use std::process::exit;
use typemap::Key;
use plugin::{Pluggable, Extensible};
use image::open as image_open;
use image::{FilterType, GenericImage, ImageFormat};
use nickel::mimes::MediaType;
mod models; mod models;
use models::{Photo, query_for}; use hyper::header::{Expires, HttpDate};
use image::open as image_open;
use image::{FilterType, ImageFormat};
use models::{Photo, Tag, query_for};
use nickel::mimes::MediaType;
use nickel::{Nickel, Request, Response, Middleware, Continue, MiddlewareResult};
use plugin::{Pluggable, Extensible};
use rustc_serialize::Encodable;
use rustorm::dao::{IsDao, ToValue};
use rustorm::database::Database;
use rustorm::pool::{ManagedPool,Platform};
use rustorm::query::Query;
use rustorm::table::IsTable;
use std::collections::HashMap;
use time::Duration;
use typemap::Key;
fn dburl() -> String { mod env;
let db_var = "RPHOTOS_DB"; use env::dburl;
match var(db_var) {
Ok(result) => result,
Err(error) => {
println!("A database url needs to be given in env {}: {}",
db_var, error);
exit(1);
}
}
}
struct RustormMiddleware { struct RustormMiddleware {
pool: ManagedPool pool: ManagedPool
@ -65,6 +55,8 @@ impl<D> Middleware<D> for RustormMiddleware {
pub trait RustormRequestExtensions { pub trait RustormRequestExtensions {
fn db_conn(&self) -> &Database; fn db_conn(&self) -> &Database;
fn orm_get<T: IsTable + IsDao>(&self, key: &str, val: &ToValue) -> T; fn orm_get<T: IsTable + IsDao>(&self, key: &str, val: &ToValue) -> T;
fn orm_get_related<T2: IsTable + IsDao>
(&self, related_to: &Photo) -> Vec<T2>;
} }
impl<'a, 'b, D> RustormRequestExtensions for Request<'a, 'b, D> { impl<'a, 'b, D> RustormRequestExtensions for Request<'a, 'b, D> {
@ -75,6 +67,21 @@ impl<'a, 'b, D> RustormRequestExtensions for Request<'a, 'b, D> {
query_for::<T>().filter_eq(key, val) query_for::<T>().filter_eq(key, val)
.collect_one(self.db_conn()).unwrap() .collect_one(self.db_conn()).unwrap()
} }
fn orm_get_related<T2: IsTable + IsDao>
(&self, related_to: &Photo) -> Vec<T2>
{
let mut q = Query::select();
q.only_from(&T2::table());
q.left_join_table("photo_tag", "tag.id", "photo_tag.tag")
.filter_eq("photo_tag.photo", &related_to.id)
.collect(self.db_conn()).unwrap()
}
}
#[derive(Debug, Clone, RustcEncodable)]
struct DetailsData {
photo: Photo,
tags: Vec<Tag>
} }
fn get_scaled_image(photo: Photo, width: u32, height: u32) -> Vec<u8> { fn get_scaled_image(photo: Photo, width: u32, height: u32) -> Vec<u8> {
@ -100,7 +107,7 @@ fn main() {
server.utilize(router! { server.utilize(router! {
get "/" => |req, res| { get "/" => |req, res| {
let photos: Vec<Photo> = query_for::<Photo>() let photos: Vec<Photo> = query_for::<Photo>().limit(16)
.collect(req.db_conn()).unwrap(); .collect(req.db_conn()).unwrap();
info!("Got some photos: {:?}", photos); info!("Got some photos: {:?}", photos);
let mut data = HashMap::new(); let mut data = HashMap::new();
@ -109,28 +116,33 @@ fn main() {
return res.render("templates/index.tpl", &data); return res.render("templates/index.tpl", &data);
} }
get "/details/:id" => |req, res| { get "/details/:id" => |req, res| {
let id = req.param("id").unwrap().parse::<i32>().unwrap(); if let Ok(id) = req.param("id").unwrap().parse::<i32>() {
let photo = req.orm_get::<Photo>("id", &id); let photo = req.orm_get::<Photo>("id", &id);
let mut data = HashMap::new(); let tags : Vec<Tag> = req.orm_get_related(&photo);
data.insert("photo", &photo); return res.render("templates/details.tpl", &DetailsData {
return res.render("templates/details.tpl", &data); photo: photo,
tags: tags
});
}
} }
get "/icon/:id" => |req, mut res| { get "/icon/:id" => |req, mut res| {
let id = req.param("id").unwrap().parse::<i32>().unwrap(); if let Ok(id) = req.param("id").unwrap().parse::<i32>() {
let photo = req.orm_get::<Photo>("id", &id); let photo = req.orm_get::<Photo>("id", &id);
let buf = get_scaled_image(photo, 200, 180); let buf = get_scaled_image(photo, 200, 180);
res.set(MediaType::Jpeg); res.set(MediaType::Jpeg);
res.set(Expires(HttpDate(time::now() + Duration::days(14)))); res.set(Expires(HttpDate(time::now() + Duration::days(14))));
return res.send(buf); return res.send(buf);
} }
}
get "/view/:id" => |req, mut res| { get "/view/:id" => |req, mut res| {
let id = req.param("id").unwrap().parse::<i32>().unwrap(); if let Ok(id) = req.param("id").unwrap().parse::<i32>() {
let photo = req.orm_get::<Photo>("id", &id); let photo = req.orm_get::<Photo>("id", &id);
let buf = get_scaled_image(photo, 800, 600); let buf = get_scaled_image(photo, 800, 600);
res.set(MediaType::Jpeg); res.set(MediaType::Jpeg);
res.set(Expires(HttpDate(time::now() + Duration::days(14)))); res.set(Expires(HttpDate(time::now() + Duration::days(14))));
return res.send(buf); return res.send(buf);
} }
}
}); });
server.listen("127.0.0.1:6767"); server.listen("127.0.0.1:6767");

View File

@ -1,8 +1,9 @@
//extern crate chrono; //extern crate chrono;
use rustorm::query::Query; use rustorm::query::Query;
use rustorm::dao::{Dao,IsDao}; use rustorm::dao::{Dao, IsDao, ToValue};
use rustorm::table::{IsTable, Table}; use rustorm::table::{IsTable, Table, Column};
use rustorm::database::Database;
#[derive(Debug, Clone, RustcEncodable)] #[derive(Debug, Clone, RustcEncodable)]
pub struct Photo { pub struct Photo {
@ -27,21 +28,145 @@ impl IsDao for Photo {
impl IsTable for Photo { impl IsTable for Photo {
fn table() -> Table { fn table() -> Table {
table("photo", vec![
Column {
name: "id".to_string(),
data_type: "i32".to_string(),
db_data_type: "serial".to_string(),
is_primary: true,
is_unique: true,
default: None,
comment: None,
not_null: true,
foreign: None,
is_inherited: false
},
Column {
name: "path".to_string(),
data_type: "String".to_string(),
db_data_type: "varchar(100)".to_string(),
is_primary: false,
is_unique: true,
default: None,
comment: None,
not_null: true,
foreign: None,
is_inherited: false
}
])
}
}
#[derive(Debug, Clone, RustcEncodable)]
pub struct Tag {
pub id: i32,
pub tag: String,
pub slug: String,
}
impl IsDao for Tag {
fn from_dao(dao: &Dao) -> Self {
Tag {
id: dao.get("id"),
tag: dao.get("tag"),
slug: dao.get("slug"),
}
}
fn to_dao(&self) -> Dao {
let mut dao = Dao::new();
dao.set("id", &self.id);
dao.set("tag", &self.tag);
dao.set("slug", &self.slug);
dao
}
}
impl IsTable for Tag {
fn table() -> Table {
table("tag", vec![
Column {
name: "id".to_string(),
data_type: "i32".to_string(),
db_data_type: "serial".to_string(),
is_primary: true,
is_unique: true,
default: None,
comment: None,
not_null: true,
foreign: None,
is_inherited: false
},
Column {
name: "tag".to_string(),
data_type: "String".to_string(),
db_data_type: "varchar(100)".to_string(),
is_primary: false,
is_unique: true,
default: None,
comment: None,
not_null: true,
foreign: None,
is_inherited: false
},
Column {
name: "slug".to_string(),
data_type: "String".to_string(),
db_data_type: "varchar(100)".to_string(),
is_primary: false,
is_unique: true,
default: None,
comment: None,
not_null: true,
foreign: None,
is_inherited: false
}
])
}
}
fn table(name: &str, columns: Vec<Column>) -> Table {
Table { Table {
schema: "public".to_owned(), schema: "public".to_owned(),
name: "photo".to_owned(), name: name.to_owned(),
parent_table: None, parent_table: None,
sub_table: vec![], sub_table: vec![],
comment: None, comment: None,
columns: vec![], columns: columns,
is_view: false is_view: false
} }
} }
}
pub fn query_for<T: IsTable>() -> Query { pub fn query_for<T: IsTable>() -> Query {
let mut q = Query::select_all(); let mut q = Query::select();
q.from_table(&T::table().complete_name()); q.from(&T::table());
q q
} }
pub fn get_or_create<T: IsTable + IsDao>(db: &Database, key: &str, val: &ToValue) -> T {
if let Ok(result) = query_for::<T>().filter_eq(key, val).collect_one(db) {
result
} else {
let table = T::table();
Query::insert().into_(&table).set(key, val)
.returns(table.columns.iter().map(|c| &*c.name).collect())
.collect_one(db).unwrap()
}
}
pub fn get_or_create_t<'a, T: IsTable + IsDao, F>
(db: &Database, key: &str, val: &ToValue, get_slug: F) -> T
where F: FnOnce() -> String
{
if let Ok(result) = query_for::<T>().filter_eq(key, val).collect_one(db) {
result
} else {
let table = T::table();
Query::insert().into_(&table)
.set(key, val)
.set("slug", &get_slug())
.returns(table.columns.iter().map(|c| &*c.name).collect())
.collect_one(db).unwrap()
}
}

115
src/readkpa.rs Normal file
View File

@ -0,0 +1,115 @@
extern crate xml;
extern crate rustorm;
extern crate rustc_serialize;
use rustorm::database::Database;
use rustorm::pool::ManagedPool;
use rustorm::query::Query;
use std::fs::File;
use xml::attribute::OwnedAttribute;
use xml::reader::EventReader;
use xml::reader::XmlEvent; // ::{EndDocument, StartElement};
mod models;
use models::{Photo, Tag, get_or_create, get_or_create_t};
mod env;
use env::dburl;
fn find_attr(name: &str, attrs: &Vec<OwnedAttribute>) -> Option<String> {
for attr in attrs {
if attr.name.local_name == name {
return Some(attr.value.clone())
}
}
None
}
fn slugify(val: String) -> String {
val.chars().map(|c| match c {
c @ '0' ... '9' => c,
c @ 'a' ... 'z' => c,
c @ 'A' ... 'Z' => c.to_lowercase().next().unwrap(),
'Å' | 'å' | 'Ä' | 'ä' => 'a',
'Ö' | 'ö' => 'o',
'É' | 'é' => 'e',
_ => '_'
}).collect()
}
fn tag_photo(db: &Database, photo: &Photo, tag: String) {
let v2 : String = tag.clone();
let tag : Tag = get_or_create_t(db, "tag", &tag, || { slugify(v2) });
println!(" tag {:?}", tag);
let mut q = Query::select();
q.from_table("public.photo_tag");
q.filter_eq("photo", &photo.id);
q.filter_eq("tag", &tag.id);
if let Ok(Some(result)) = q.retrieve_one(db) {
println!(" match {:?}", result)
} else {
println!(" new tag!");
let mut q = Query::insert();
q.into_table("public.photo_tag");
q.set("photo", &photo.id);
q.set("tag", &tag.id);
q.execute(db);
}
}
fn main() {
println!("Hello");
let mut pool = ManagedPool::init(&dburl(), 1).unwrap();
let mut db = pool.connect().unwrap();
let file = File::open("/home/kaj/Bilder/foto/index.xml").unwrap();
let mut xml = EventReader::new(file);
let mut option : Option<String> = None;
let mut photo : Option<Photo> = None;
while let Ok(event) = xml.next() {
match event {
XmlEvent::EndDocument => {
println!("End of xml");
break;
},
XmlEvent::StartElement{ref name, ref attributes, ref namespace} => {
match &*name.local_name {
"image" => {
if let Some(file) = find_attr("file", attributes) {
let img = get_or_create::<Photo>(db.as_ref(), "path", &file);
println!("Found image {:?}", img);
photo = Some(img);
}
}
"option" => {
option = find_attr("name", attributes);
}
"value" => {
if let Some(ref o) = option {
if let Some(v) = find_attr("value", attributes) {
match &**o {
"Nyckelord" => {
if let Some(ref photo) = photo {
tag_photo(db.as_ref(), &photo, v);
}
},
o => { println!(" {} = {}", o, v); }
}
}
}
}
_ => {}
}
},
XmlEvent::EndElement{ref name} => {
match &*name.local_name {
"option" => { option = None; }
_ => {}
}
}
_ => {
}
}
}
println!("Bye");
}

View File

@ -7,5 +7,6 @@
<h1>Photo details</h1> <h1>Photo details</h1>
<p>{{photo.path}}</p> <p>{{photo.path}}</p>
<p><img src="/view/{{photo.id}}"></p> <p><img src="/view/{{photo.id}}"></p>
<p>Tags: {{#tags}}<a href="/tag/{{slug}}">{{tag}}</a>, {{/tags}}</p>
</body> </body>
</html> </html>