Photos can have tags.
Read old data from kphotoalbum.
This commit is contained in:
parent
704877abd9
commit
b1d53c0762
@ -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
15
db/00001_init.sql
Normal 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
14
src/env.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
src/main.rs
104
src/main.rs
@ -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,27 +116,32 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
151
src/models.rs
151
src/models.rs
@ -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 {
|
table("photo", vec![
|
||||||
schema: "public".to_owned(),
|
Column {
|
||||||
name: "photo".to_owned(),
|
name: "id".to_string(),
|
||||||
parent_table: None,
|
data_type: "i32".to_string(),
|
||||||
sub_table: vec![],
|
db_data_type: "serial".to_string(),
|
||||||
comment: None,
|
is_primary: true,
|
||||||
columns: vec![],
|
is_unique: true,
|
||||||
is_view: false
|
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 {
|
||||||
|
schema: "public".to_owned(),
|
||||||
|
name: name.to_owned(),
|
||||||
|
parent_table: None,
|
||||||
|
sub_table: vec![],
|
||||||
|
comment: None,
|
||||||
|
columns: columns,
|
||||||
|
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
115
src/readkpa.rs
Normal 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");
|
||||||
|
}
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user