feature/exif-endpoint #44
@@ -42,7 +42,7 @@ fn main() -> anyhow::Result<()> {
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_type().is_file())
|
||||
.filter(|e| exif::supports_exif(&e.path()))
|
||||
.filter(|e| exif::supports_exif(e.path()))
|
||||
.map(|e| e.path().to_path_buf())
|
||||
.collect();
|
||||
|
||||
@@ -156,15 +156,13 @@ fn main() -> anyhow::Result<()> {
|
||||
let mut updated_count = 0;
|
||||
let mut skipped_count = 0;
|
||||
|
||||
for result in &results {
|
||||
if let Ok((action, _)) = result {
|
||||
success_count += 1;
|
||||
match action.as_str() {
|
||||
"insert" => inserted_count += 1,
|
||||
"update" => updated_count += 1,
|
||||
"skip" => skipped_count += 1,
|
||||
_ => {}
|
||||
}
|
||||
for (action, _) in results.iter().flatten() {
|
||||
success_count += 1;
|
||||
match action.as_str() {
|
||||
"insert" => inserted_count += 1,
|
||||
"update" => updated_count += 1,
|
||||
"skip" => skipped_count += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ pub fn get_canonical_extension(mime_type: &str) -> String {
|
||||
"video/quicktime" => "mov",
|
||||
|
||||
// Fallback: use the last part of MIME type
|
||||
_ => mime_type.split('/').last().unwrap_or("unknown"),
|
||||
_ => mime_type.split('/').next_back().unwrap_or("unknown"),
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
@@ -110,11 +110,10 @@ fn find_file_with_alternative_extension(
|
||||
let test_path = parent.join(format!("{}.{}", stem, ext));
|
||||
if test_path.exists() {
|
||||
// Convert back to relative path
|
||||
if let Ok(rel) = test_path.strip_prefix(base_path) {
|
||||
if let Some(rel_str) = rel.to_str() {
|
||||
if let Ok(rel) = test_path.strip_prefix(base_path)
|
||||
&& let Some(rel_str) = rel.to_str() {
|
||||
return Some(rel_str.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ pub fn validate_file_types(
|
||||
true
|
||||
} else {
|
||||
// Interactive prompt
|
||||
match prompt_for_rename(&new_relative_path) {
|
||||
match prompt_for_rename(new_relative_path) {
|
||||
RenameDecision::Yes => true,
|
||||
RenameDecision::No => {
|
||||
user_skipped += 1;
|
||||
@@ -183,8 +183,8 @@ pub fn validate_file_types(
|
||||
|
||||
/// Check if a file is a supported media file based on extension
|
||||
fn is_supported_media_file(path: &Path) -> bool {
|
||||
if let Some(ext) = path.extension() {
|
||||
if let Some(ext_str) = ext.to_str() {
|
||||
if let Some(ext) = path.extension()
|
||||
&& let Some(ext_str) = ext.to_str() {
|
||||
let ext_lower = ext_str.to_lowercase();
|
||||
return matches!(
|
||||
ext_lower.as_str(),
|
||||
@@ -202,7 +202,6 @@ fn is_supported_media_file(path: &Path) -> bool {
|
||||
| "mov"
|
||||
);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,12 @@ pub struct SqliteUserDao {
|
||||
connection: SqliteConnection,
|
||||
}
|
||||
|
||||
impl Default for SqliteUserDao {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SqliteUserDao {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -138,6 +144,12 @@ pub struct SqliteFavoriteDao {
|
||||
connection: Arc<Mutex<SqliteConnection>>,
|
||||
}
|
||||
|
||||
impl Default for SqliteFavoriteDao {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SqliteFavoriteDao {
|
||||
pub fn new() -> Self {
|
||||
SqliteFavoriteDao {
|
||||
@@ -243,6 +255,12 @@ pub struct SqliteExifDao {
|
||||
connection: Arc<Mutex<SqliteConnection>>,
|
||||
}
|
||||
|
||||
impl Default for SqliteExifDao {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SqliteExifDao {
|
||||
pub fn new() -> Self {
|
||||
SqliteExifDao {
|
||||
|
||||
29
src/exif.rs
29
src/exif.rs
@@ -8,6 +8,7 @@ use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Default)]
|
||||
pub struct ExifData {
|
||||
pub camera_make: Option<String>,
|
||||
pub camera_model: Option<String>,
|
||||
@@ -25,26 +26,6 @@ pub struct ExifData {
|
||||
pub date_taken: Option<i64>,
|
||||
}
|
||||
|
||||
impl Default for ExifData {
|
||||
fn default() -> Self {
|
||||
ExifData {
|
||||
camera_make: None,
|
||||
camera_model: None,
|
||||
lens_model: None,
|
||||
width: None,
|
||||
height: None,
|
||||
orientation: None,
|
||||
gps_latitude: None,
|
||||
gps_longitude: None,
|
||||
gps_altitude: None,
|
||||
focal_length: None,
|
||||
aperture: None,
|
||||
shutter_speed: None,
|
||||
iso: None,
|
||||
date_taken: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supports_exif(path: &Path) -> bool {
|
||||
if let Some(ext) = path.extension() {
|
||||
@@ -265,13 +246,11 @@ fn extract_gps_altitude(exif: &exif::Exif) -> Option<f64> {
|
||||
let altitude = rational.num as f64 / rational.denom as f64;
|
||||
|
||||
// Check if below sea level
|
||||
if let Some(ref_field) = exif.get_field(Tag::GPSAltitudeRef, In::PRIMARY) {
|
||||
if let Some(ref_val) = get_u32_value(ref_field) {
|
||||
if ref_val == 1 {
|
||||
if let Some(ref_field) = exif.get_field(Tag::GPSAltitudeRef, In::PRIMARY)
|
||||
&& let Some(ref_val) = get_u32_value(ref_field)
|
||||
&& ref_val == 1 {
|
||||
return Some(-altitude);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(altitude)
|
||||
}
|
||||
|
||||
10
src/main.rs
10
src/main.rs
@@ -175,11 +175,10 @@ async fn get_file_metadata(
|
||||
let mut response: MetadataResponse = metadata.into();
|
||||
|
||||
// Query EXIF data if available
|
||||
if let Ok(mut dao) = exif_dao.lock() {
|
||||
if let Ok(Some(exif)) = dao.get_exif(&path.path) {
|
||||
if let Ok(mut dao) = exif_dao.lock()
|
||||
&& let Ok(Some(exif)) = dao.get_exif(&path.path) {
|
||||
response.exif = Some(exif.into());
|
||||
}
|
||||
}
|
||||
|
||||
span.add_event(
|
||||
"Metadata fetched",
|
||||
@@ -903,11 +902,10 @@ fn process_new_files(
|
||||
.filter(|entry| {
|
||||
// Filter by modification time if specified
|
||||
if let Some(since) = modified_since {
|
||||
if let Ok(metadata) = entry.metadata() {
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
if let Ok(metadata) = entry.metadata()
|
||||
&& let Ok(modified) = metadata.modified() {
|
||||
return modified >= since;
|
||||
}
|
||||
}
|
||||
// If we can't get metadata, include the file to be safe
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ async fn get_all_tags<D: TagDao>(
|
||||
let mut tag_dao = tag_dao.lock().expect("Unable to get TagDao");
|
||||
tag_dao
|
||||
.get_all_tags(&span_context, query.path.clone())
|
||||
.and_then(|tags| {
|
||||
.map(|tags| {
|
||||
span_context.span().set_status(Status::Ok);
|
||||
|
||||
let tags_response = tags
|
||||
@@ -122,10 +122,10 @@ async fn get_all_tags<D: TagDao>(
|
||||
.map(|(make, count)| CameraMakeCount { make, count })
|
||||
.collect::<Vec<CameraMakeCount>>();
|
||||
|
||||
Ok(HttpResponse::Ok().json(AllTagsResponse {
|
||||
HttpResponse::Ok().json(AllTagsResponse {
|
||||
tags: tags_response,
|
||||
camera_makes,
|
||||
}))
|
||||
})
|
||||
})
|
||||
.into_http_internal_err()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user