Add camera support.

Store and show which camera each picture was taken with (if present in
exif data).
This commit is contained in:
Rasmus Kaj 2016-08-09 01:41:42 +02:00
parent 97f299b54f
commit 6ed42c0be7
7 changed files with 90 additions and 6 deletions

View File

@ -0,0 +1,2 @@
ALTER TABLE photos DROP COLUMN camera_id;
DROP TABLE cameras;

View 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);

View File

@ -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)]

View File

@ -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()

View File

@ -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)
}
}
}

View File

@ -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);

View File

@ -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>