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"
|
||||
authors = ["Rasmus Kaj <kaj@kth.se>"]
|
||||
|
||||
[[bin]]
|
||||
name = "rphotos"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "readkpa"
|
||||
path = "src/readkpa.rs"
|
||||
|
||||
[dependencies]
|
||||
nickel = "*"
|
||||
env_logger = "*"
|
||||
@ -15,3 +23,4 @@ plugin = "*"
|
||||
image = "*"
|
||||
hyper = "*"
|
||||
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 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;
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
mod env;
|
||||
use env::dburl;
|
||||
|
||||
struct RustormMiddleware {
|
||||
pool: ManagedPool
|
||||
@ -65,6 +55,8 @@ impl<D> Middleware<D> for RustormMiddleware {
|
||||
pub trait RustormRequestExtensions {
|
||||
fn db_conn(&self) -> &Database;
|
||||
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> {
|
||||
@ -75,6 +67,21 @@ impl<'a, 'b, D> RustormRequestExtensions for Request<'a, 'b, D> {
|
||||
query_for::<T>().filter_eq(key, val)
|
||||
.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> {
|
||||
@ -100,7 +107,7 @@ fn main() {
|
||||
|
||||
server.utilize(router! {
|
||||
get "/" => |req, res| {
|
||||
let photos: Vec<Photo> = query_for::<Photo>()
|
||||
let photos: Vec<Photo> = query_for::<Photo>().limit(16)
|
||||
.collect(req.db_conn()).unwrap();
|
||||
info!("Got some photos: {:?}", photos);
|
||||
let mut data = HashMap::new();
|
||||
@ -109,27 +116,32 @@ fn main() {
|
||||
return res.render("templates/index.tpl", &data);
|
||||
}
|
||||
get "/details/:id" => |req, res| {
|
||||
let id = req.param("id").unwrap().parse::<i32>().unwrap();
|
||||
let photo = req.orm_get::<Photo>("id", &id);
|
||||
let mut data = HashMap::new();
|
||||
data.insert("photo", &photo);
|
||||
return res.render("templates/details.tpl", &data);
|
||||
if let Ok(id) = req.param("id").unwrap().parse::<i32>() {
|
||||
let photo = req.orm_get::<Photo>("id", &id);
|
||||
let tags : Vec<Tag> = req.orm_get_related(&photo);
|
||||
return res.render("templates/details.tpl", &DetailsData {
|
||||
photo: photo,
|
||||
tags: tags
|
||||
});
|
||||
}
|
||||
}
|
||||
get "/icon/:id" => |req, mut res| {
|
||||
let id = req.param("id").unwrap().parse::<i32>().unwrap();
|
||||
let photo = req.orm_get::<Photo>("id", &id);
|
||||
let buf = get_scaled_image(photo, 200, 180);
|
||||
res.set(MediaType::Jpeg);
|
||||
res.set(Expires(HttpDate(time::now() + Duration::days(14))));
|
||||
return res.send(buf);
|
||||
if let Ok(id) = req.param("id").unwrap().parse::<i32>() {
|
||||
let photo = req.orm_get::<Photo>("id", &id);
|
||||
let buf = get_scaled_image(photo, 200, 180);
|
||||
res.set(MediaType::Jpeg);
|
||||
res.set(Expires(HttpDate(time::now() + Duration::days(14))));
|
||||
return res.send(buf);
|
||||
}
|
||||
}
|
||||
get "/view/:id" => |req, mut res| {
|
||||
let id = req.param("id").unwrap().parse::<i32>().unwrap();
|
||||
let photo = req.orm_get::<Photo>("id", &id);
|
||||
let buf = get_scaled_image(photo, 800, 600);
|
||||
res.set(MediaType::Jpeg);
|
||||
res.set(Expires(HttpDate(time::now() + Duration::days(14))));
|
||||
return res.send(buf);
|
||||
if let Ok(id) = req.param("id").unwrap().parse::<i32>() {
|
||||
let photo = req.orm_get::<Photo>("id", &id);
|
||||
let buf = get_scaled_image(photo, 800, 600);
|
||||
res.set(MediaType::Jpeg);
|
||||
res.set(Expires(HttpDate(time::now() + Duration::days(14))));
|
||||
return res.send(buf);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
151
src/models.rs
151
src/models.rs
@ -1,8 +1,9 @@
|
||||
//extern crate chrono;
|
||||
|
||||
use rustorm::query::Query;
|
||||
use rustorm::dao::{Dao,IsDao};
|
||||
use rustorm::table::{IsTable, Table};
|
||||
use rustorm::dao::{Dao, IsDao, ToValue};
|
||||
use rustorm::table::{IsTable, Table, Column};
|
||||
use rustorm::database::Database;
|
||||
|
||||
#[derive(Debug, Clone, RustcEncodable)]
|
||||
pub struct Photo {
|
||||
@ -27,21 +28,145 @@ impl IsDao for Photo {
|
||||
|
||||
impl IsTable for Photo {
|
||||
fn table() -> Table {
|
||||
Table {
|
||||
schema: "public".to_owned(),
|
||||
name: "photo".to_owned(),
|
||||
parent_table: None,
|
||||
sub_table: vec![],
|
||||
comment: None,
|
||||
columns: vec![],
|
||||
is_view: false
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
let mut q = Query::select_all();
|
||||
q.from_table(&T::table().complete_name());
|
||||
let mut q = Query::select();
|
||||
q.from(&T::table());
|
||||
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>
|
||||
<p>{{photo.path}}</p>
|
||||
<p><img src="/view/{{photo.id}}"></p>
|
||||
<p>Tags: {{#tags}}<a href="/tag/{{slug}}">{{tag}}</a>, {{/tags}}</p>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user