Add camera support.
Store and show which camera each picture was taken with (if present in exif data).
This commit is contained in:
parent
97f299b54f
commit
6ed42c0be7
2
migrations/20160808022325_create_cameras_table/down.sql
Normal file
2
migrations/20160808022325_create_cameras_table/down.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE photos DROP COLUMN camera_id;
|
||||||
|
DROP TABLE cameras;
|
9
migrations/20160808022325_create_cameras_table/up.sql
Normal file
9
migrations/20160808022325_create_cameras_table/up.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE cameras (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
manufacturer VARCHAR NOT NULL,
|
||||||
|
model VARCHAR NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX cameras_idx ON cameras (manufacturer, model);
|
||||||
|
|
||||||
|
ALTER TABLE photos ADD COLUMN camera_id INTEGER REFERENCES cameras (id);
|
@ -16,7 +16,7 @@ use std::path::Path;
|
|||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use diesel::pg::PgConnection;
|
use diesel::pg::PgConnection;
|
||||||
use self::diesel::prelude::*;
|
use self::diesel::prelude::*;
|
||||||
use rphotos::models::{Modification, Photo};
|
use rphotos::models::{Modification, Photo, Camera};
|
||||||
|
|
||||||
mod env;
|
mod env;
|
||||||
use env::{dburl, photos_dir};
|
use env::{dburl, photos_dir};
|
||||||
@ -62,7 +62,8 @@ fn save_photo(db: &PgConnection,
|
|||||||
-> FindPhotoResult<()> {
|
-> FindPhotoResult<()> {
|
||||||
let photo = match try!(Photo::create_or_set_basics(db, file_path,
|
let photo = match try!(Photo::create_or_set_basics(db, file_path,
|
||||||
Some(try!(find_date(&exif))),
|
Some(try!(find_date(&exif))),
|
||||||
try!(find_rotation(&exif)))) {
|
try!(find_rotation(&exif)),
|
||||||
|
try!(find_camera(db, exif)))) {
|
||||||
Modification::Created(photo) => {
|
Modification::Created(photo) => {
|
||||||
info!("Created {:?}", photo);
|
info!("Created {:?}", photo);
|
||||||
photo
|
photo
|
||||||
@ -103,6 +104,19 @@ fn save_photo(db: &PgConnection,
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_camera(db: &PgConnection, exif: &ExifData) -> FindPhotoResult<Option<Camera>> {
|
||||||
|
if let (Some(maketag), Some(modeltag)) = (find_entry(exif, &ExifTag::Make),
|
||||||
|
find_entry(exif, &ExifTag::Model)) {
|
||||||
|
if let (TagValue::Ascii(make), TagValue::Ascii(model)) =
|
||||||
|
(maketag.clone().value, modeltag.clone().value) {
|
||||||
|
let cam = try!(Camera::get_or_create(db, &make, &model));
|
||||||
|
return Ok(Some(cam));
|
||||||
|
}
|
||||||
|
// TODO else Err(...?)
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
type FindPhotoResult<T> = Result<T, FindPhotoError>;
|
type FindPhotoResult<T> = Result<T, FindPhotoError>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -39,7 +39,7 @@ use diesel::prelude::*;
|
|||||||
use diesel::pg::PgConnection;
|
use diesel::pg::PgConnection;
|
||||||
use chrono::naive::date::NaiveDate;
|
use chrono::naive::date::NaiveDate;
|
||||||
|
|
||||||
use rphotos::models::{Person, Photo, Place, Tag};
|
use rphotos::models::{Camera, Person, Photo, Place, Tag};
|
||||||
|
|
||||||
mod env;
|
mod env;
|
||||||
use env::{dburl, env_or, jwt_key, photos_dir};
|
use env::{dburl, env_or, jwt_key, photos_dir};
|
||||||
@ -444,6 +444,12 @@ fn photo_details<'mw>(req: &mut Request,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
camera: Option<Camera> = {
|
||||||
|
use rphotos::schema::cameras::dsl::*;
|
||||||
|
tphoto.camera_id.map(|i| {
|
||||||
|
cameras.find(i).first(c).unwrap()
|
||||||
|
})
|
||||||
|
},
|
||||||
time: String = match tphoto.date {
|
time: String = match tphoto.date {
|
||||||
Some(d) => d.format("%T").to_string(),
|
Some(d) => d.format("%T").to_string(),
|
||||||
None => "".to_string()
|
None => "".to_string()
|
||||||
|
@ -4,6 +4,7 @@ use diesel::pg::PgConnection;
|
|||||||
use diesel::result::Error as DieselError;
|
use diesel::result::Error as DieselError;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Queryable)]
|
#[derive(Debug, Clone, Queryable)]
|
||||||
|
#[belongs_to(Camera)]
|
||||||
pub struct Photo {
|
pub struct Photo {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
@ -11,6 +12,7 @@ pub struct Photo {
|
|||||||
pub grade: Option<i16>,
|
pub grade: Option<i16>,
|
||||||
pub rotation: i16,
|
pub rotation: i16,
|
||||||
pub is_public: bool,
|
pub is_public: bool,
|
||||||
|
pub camera_id: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NaiveDateTime isn't Encodable, so we have to implement this by hand.
|
// NaiveDateTime isn't Encodable, so we have to implement this by hand.
|
||||||
@ -40,6 +42,7 @@ pub struct NewPhoto<'a> {
|
|||||||
pub path: &'a str,
|
pub path: &'a str,
|
||||||
pub date: Option<NaiveDateTime>,
|
pub date: Option<NaiveDateTime>,
|
||||||
pub rotation: i16,
|
pub rotation: i16,
|
||||||
|
pub camera_id: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -73,16 +76,19 @@ impl Photo {
|
|||||||
pub fn create_or_set_basics(db: &PgConnection,
|
pub fn create_or_set_basics(db: &PgConnection,
|
||||||
file_path: &str,
|
file_path: &str,
|
||||||
exifdate: Option<NaiveDateTime>,
|
exifdate: Option<NaiveDateTime>,
|
||||||
exifrotation: i16)
|
exifrotation: i16,
|
||||||
|
camera: Option<Camera>)
|
||||||
-> Result<Modification<Photo>, DieselError> {
|
-> Result<Modification<Photo>, DieselError> {
|
||||||
use diesel;
|
use diesel;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use schema::photos::dsl::*;
|
use schema::photos::dsl::*;
|
||||||
|
let cameraid = camera.map(|c| c.id);
|
||||||
if let Some(mut pic) =
|
if let Some(mut pic) =
|
||||||
try!(photos.filter(path.eq(&file_path.to_string()))
|
try!(photos.filter(path.eq(&file_path.to_string()))
|
||||||
.first::<Photo>(db)
|
.first::<Photo>(db)
|
||||||
.optional()) {
|
.optional()) {
|
||||||
let mut change = false;
|
let mut change = false;
|
||||||
|
// TODO Merge updates to one update statement!
|
||||||
if exifdate.is_some() && exifdate != pic.date {
|
if exifdate.is_some() && exifdate != pic.date {
|
||||||
change = true;
|
change = true;
|
||||||
pic = try!(diesel::update(photos.find(pic.id))
|
pic = try!(diesel::update(photos.find(pic.id))
|
||||||
@ -95,6 +101,12 @@ impl Photo {
|
|||||||
.set(rotation.eq(exifrotation))
|
.set(rotation.eq(exifrotation))
|
||||||
.get_result::<Photo>(db));
|
.get_result::<Photo>(db));
|
||||||
}
|
}
|
||||||
|
if cameraid != pic.camera_id {
|
||||||
|
change = true;
|
||||||
|
pic = try!(diesel::update(photos.find(pic.id))
|
||||||
|
.set(camera_id.eq(cameraid))
|
||||||
|
.get_result::<Photo>(db));
|
||||||
|
}
|
||||||
Ok(if change {
|
Ok(if change {
|
||||||
Modification::Updated(pic)
|
Modification::Updated(pic)
|
||||||
} else {
|
} else {
|
||||||
@ -105,6 +117,7 @@ impl Photo {
|
|||||||
path: &file_path,
|
path: &file_path,
|
||||||
date: exifdate,
|
date: exifdate,
|
||||||
rotation: exifrotation,
|
rotation: exifrotation,
|
||||||
|
camera_id: cameraid,
|
||||||
};
|
};
|
||||||
let pic = try!(diesel::insert(&pic)
|
let pic = try!(diesel::insert(&pic)
|
||||||
.into(photos)
|
.into(photos)
|
||||||
@ -221,3 +234,42 @@ pub struct NewUser<'a> {
|
|||||||
pub username: &'a str,
|
pub username: &'a str,
|
||||||
pub password: &'a str,
|
pub password: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Identifiable, RustcEncodable, Queryable)]
|
||||||
|
#[has_many(photos)]
|
||||||
|
pub struct Camera {
|
||||||
|
pub id: i32,
|
||||||
|
pub manufacturer: String,
|
||||||
|
pub model: String,
|
||||||
|
}
|
||||||
|
use super::schema::cameras;
|
||||||
|
#[insertable_into(cameras)]
|
||||||
|
pub struct NewCamera {
|
||||||
|
pub manufacturer: String,
|
||||||
|
pub model: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Camera {
|
||||||
|
pub fn get_or_create(db: &PgConnection,
|
||||||
|
make: &str,
|
||||||
|
modl: &str)
|
||||||
|
-> Result<Camera, DieselError> {
|
||||||
|
use diesel;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use schema::cameras::dsl::*;
|
||||||
|
if let Some(camera) = try!(cameras.filter(manufacturer.eq(make))
|
||||||
|
.filter(model.eq(modl))
|
||||||
|
.first::<Camera>(db)
|
||||||
|
.optional()) {
|
||||||
|
Ok(camera)
|
||||||
|
} else {
|
||||||
|
let camera = NewCamera {
|
||||||
|
manufacturer: make.to_string(),
|
||||||
|
model: modl.to_string(),
|
||||||
|
};
|
||||||
|
diesel::insert(&camera)
|
||||||
|
.into(cameras)
|
||||||
|
.get_result(db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -194,7 +194,7 @@ fn main() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let date = find_image_date(attributes);
|
let date = find_image_date(attributes);
|
||||||
photo = Some(match Photo::create_or_set_basics
|
photo = Some(match Photo::create_or_set_basics
|
||||||
(&db, &file, date, angle)
|
(&db, &file, date, angle, None)
|
||||||
.expect("Create or update photo") {
|
.expect("Create or update photo") {
|
||||||
Modification::Created(photo) => {
|
Modification::Created(photo) => {
|
||||||
info!("Created {:?}", photo);
|
info!("Created {:?}", photo);
|
||||||
|
@ -35,5 +35,6 @@
|
|||||||
<p>Places: {{#places}}<a href="/place/{{slug}}">{{place_name}}</a>, {{/places}}</p>
|
<p>Places: {{#places}}<a href="/place/{{slug}}">{{place_name}}</a>, {{/places}}</p>
|
||||||
<p>Tags: {{#tags}}<a href="/tag/{{slug}}">{{tag_name}}</a>, {{/tags}}</p>
|
<p>Tags: {{#tags}}<a href="/tag/{{slug}}">{{tag_name}}</a>, {{/tags}}</p>
|
||||||
{{#position}}<p>Position: {{x}} {{y}}</p>{{/position}}
|
{{#position}}<p>Position: {{x}} {{y}}</p>{{/position}}
|
||||||
|
{{#camera}}<p>Camera: {{model}} ({{manufacturer}})</p>{{/camera}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
Reference in New Issue
Block a user