[niepce] Issue #11 - Implement file info metadata panel



commit 8cfebb2fac5955f01ddaf95f17747f25084d3811
Author: Hubert Figuière <hub figuiere net>
Date:   Fri Mar 23 23:40:43 2018 -0400

    Issue #11 - Implement file info metadata panel
    
    - database version is now 8
    - added readonly StringArray widget
    - JPG and XMP sidecar are in the sidecar table
    - Extended LibMetadata
    
    https://gitlab.gnome.org/GNOME/niepce/issues/11

 doc/database.txt                         |  5 ++-
 src/engine/db/filebundle.rs              | 18 +++++++++
 src/engine/db/libmetadata.rs             | 64 +++++++++++++++++++++++++-------
 src/engine/db/library.rs                 | 56 +++++++++++++++++++++++-----
 src/engine/db/metadata.hpp               | 14 ++++++-
 src/engine/db/properties-def.hpp         | 12 ++++--
 src/fwk/toolkit/metadatawidget.cpp       |  6 ++-
 src/fwk/toolkit/metadatawidget.hpp       |  5 ++-
 src/niepce/ui/metadatapanecontroller.cpp | 13 ++++++-
 9 files changed, 159 insertions(+), 34 deletions(-)
---
diff --git a/doc/database.txt b/doc/database.txt
index a5ae145..48439cd 100644
--- a/doc/database.txt
+++ b/doc/database.txt
@@ -16,7 +16,7 @@ Files in the library
 
 files           id             Unique ID in the database
                main_file      ID in fsfiles for the main file.
-               path           The absolute path to the file
+               name           The (display) name of the file
                parent_id      The ID on the containing folder (= folders.id)
                orientation    The Exif orientation of the file
                file_type      The file type. See db::LibFile::FileType for
@@ -29,7 +29,7 @@ files           id             Unique ID in the database
                import_date    The date of import in the database (time_t)
                mod_date       The date modified (time_t)
                xmp            The XMP blob
-               xmp_data       The date the XMP is rewritten on disk (time_t)
+               xmp_date       The date the XMP is rewritten on disk (time_t)
                xmp_file       The id of the fsfile that represent the XMP (int)
                jpeg_file      The id of the JPEG for RAW+JPEG. Implies
                                file_type is LibFile::FILE_TYPE_RAW_JPEG (int)
@@ -44,6 +44,7 @@ Sidecars are fsfiles attached to a file (excepted XMP and JPEG alternate).
 
 sidecars        file_id        ID of the file.
                 fsfile_id      ID of the fsfile sidecar.
+                ext            String: the extension (no dot). [ version = 8 ]
                 type           Sidecar type. 1 = Live, 2 = Thumbnail.
                                (See the Sidecar enum)
 
diff --git a/src/engine/db/filebundle.rs b/src/engine/db/filebundle.rs
index ed629a8..4dc28f3 100644
--- a/src/engine/db/filebundle.rs
+++ b/src/engine/db/filebundle.rs
@@ -32,6 +32,22 @@ pub enum Sidecar {
     Live(String),
     /// Thumbnail file (THM from Canon)
     Thumbnail(String),
+    /// XMP Sidecar
+    Xmp(String),
+    /// JPEG Sidecar (RAW + JPEG)
+    Jpeg(String),
+}
+
+impl Sidecar {
+    pub fn to_int(&self) -> i32 {
+        match self {
+            &Sidecar::Live(_) => 1,
+            &Sidecar::Thumbnail(_) => 2,
+            &Sidecar::Xmp(_) => 3,
+            &Sidecar::Jpeg(_) => 4,
+            &Sidecar::Invalid => 0,
+        }
+    }
 }
 
 impl From<(i32, String)> for Sidecar {
@@ -39,6 +55,8 @@ impl From<(i32, String)> for Sidecar {
         match t.0 {
             1 => Sidecar::Live(t.1),
             2 => Sidecar::Thumbnail(t.1),
+            3 => Sidecar::Xmp(t.1),
+            4 => Sidecar::Jpeg(t.1),
             _ => Sidecar::Invalid,
         }
     }
diff --git a/src/engine/db/libmetadata.rs b/src/engine/db/libmetadata.rs
index 0b33c51..cf56e42 100644
--- a/src/engine/db/libmetadata.rs
+++ b/src/engine/db/libmetadata.rs
@@ -36,10 +36,15 @@ use super::{
     LibraryId
 };
 use root::eng::NiepceProperties as Np;
+use engine::db::libfile::FileType;
 
 pub struct LibMetadata {
     xmp: XmpMeta,
-    id: LibraryId
+    id: LibraryId,
+    pub sidecars: Vec<String>,
+    pub file_type: FileType,
+    pub name: String,
+    pub folder: String,
 }
 
 struct IndexToXmp {
@@ -60,18 +65,24 @@ fn property_index_to_xmp(meta: Np) -> Option<IndexToXmp> {
 }
 
 impl LibMetadata {
-
     pub fn new(id: LibraryId) -> LibMetadata {
-        LibMetadata{
+        LibMetadata {
             xmp: XmpMeta::new(),
-            id: id
+            id: id,
+            sidecars: vec![], file_type: FileType::UNKNOWN,
+            name: String::new(),
+            folder: String::new()
         }
     }
 
     pub fn new_with_xmp(id: LibraryId, xmp: XmpMeta) -> LibMetadata {
-        LibMetadata{
+        LibMetadata {
             xmp: xmp,
-            id: id
+            id: id,
+            sidecars: vec![],
+            file_type: FileType::UNKNOWN,
+            name: String::new(),
+            folder: String::new()
         }
     }
 
@@ -178,7 +189,28 @@ impl LibMetadata {
                         keywords.push(String::from(value.to_str()));
                     }
                     props.set_value(*prop_id, PropertyValue::StringArray(keywords));
-                },
+                }
+                Np::NpFileNameProp => {
+                    props.set_value(*prop_id, PropertyValue::String(self.name.clone()));
+                }
+                Np::NpFileTypeProp => {
+                    // XXX this to string convert should be elsewhere
+                    let file_type = match self.file_type {
+                        FileType::UNKNOWN => "Unknown",
+                        FileType::RAW => "RAW",
+                        FileType::RAW_JPEG => "RAW + JPEG",
+                        FileType::IMAGE => "Image",
+                        FileType::VIDEO => "Video",
+                    };
+                    props.set_value(*prop_id, PropertyValue::String(String::from(file_type)));
+                }
+                Np::NpFileSizeProp => {}
+                Np::NpFolderProp => {
+                    props.set_value(*prop_id, PropertyValue::String(self.folder.clone()));
+                }
+                Np::NpSidecarsProp => {
+                    props.set_value(*prop_id, PropertyValue::StringArray(self.sidecars.clone()));
+                }
                 _ => {
                     if let Some(propval) = self.get_metadata(prop_id_np) {
                         props.set_value(*prop_id, propval);
@@ -193,19 +225,19 @@ impl LibMetadata {
 
     pub fn touch(&mut self) -> bool {
         let xmpdate = xmp_date_from(&Utc::now());
-        return self.xmp.xmp.set_property_date(NS_XAP, "MetadataDate",
-                                              &xmpdate, exempi::PROP_NONE);
+        return self.xmp
+            .xmp
+            .set_property_date(NS_XAP, "MetadataDate", &xmpdate, exempi::PROP_NONE);
     }
 }
 
 impl FromDb for LibMetadata {
-
     fn read_db_columns() -> &'static str {
-        "id,xmp"
+        "files.id,xmp,file_type,files.name,folders.name"
     }
 
     fn read_db_tables() -> &'static str {
-        "files"
+        "files LEFT JOIN folders ON folders.id = files.parent_id"
     }
 
     fn read_from(row: &rusqlite::Row) -> Self {
@@ -214,9 +246,13 @@ impl FromDb for LibMetadata {
 
         let mut xmpmeta = XmpMeta::new();
         xmpmeta.unserialize(&xmp);
-        LibMetadata::new_with_xmp(id, xmpmeta)
+        let mut libmeta = LibMetadata::new_with_xmp(id, xmpmeta);
+        let col: i32 = row.get(2);
+        libmeta.file_type = FileType::from(col);
+        libmeta.name = row.get(3);
+        libmeta.folder = row.get(4);
+        libmeta
     }
-
 }
 
 #[no_mangle]
diff --git a/src/engine/db/library.rs b/src/engine/db/library.rs
index 3f67f75..d53c55b 100644
--- a/src/engine/db/library.rs
+++ b/src/engine/db/library.rs
@@ -1,7 +1,7 @@
 /*
  * niepce - engine/db/library.rs
  *
- * Copyright (C) 2017 Hubert Figuière
+ * Copyright (C) 2017-2018 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -46,7 +46,7 @@ pub enum Managed {
     YES = 1,
 }
 
-const DB_SCHEMA_VERSION: i32 = 7;
+const DB_SCHEMA_VERSION: i32 = 8;
 const DATABASENAME: &str = "niepcelibrary.db";
 
 pub struct Library {
@@ -183,7 +183,8 @@ impl Library {
             // version = 7
             conn.execute(
                 "CREATE TABLE sidecars (file_id INTEGER,\
-                 fsfile_id INTEGER, type INTEGER, UNIQUE(file_id, fsfile_id))",
+                 fsfile_id INTEGER, type INTEGER, ext TEXT NOT NULL,\
+                 UNIQUE(file_id, fsfile_id))",
                 &[],
             ).unwrap();
             conn.execute(
@@ -276,18 +277,35 @@ impl Library {
 
     pub fn add_sidecar_file_to_bundle(&self, file_id: LibraryId, sidecar: &Sidecar) -> bool {
         let sidecar_t: (i32, &String) = match sidecar {
-            &Sidecar::Live(ref p) => (1, p),
-            &Sidecar::Thumbnail(ref p) => (2, p),
+            &Sidecar::Live(ref p)
+            | &Sidecar::Thumbnail(ref p)
+            | &Sidecar::Xmp(ref p)
+            | &Sidecar::Jpeg(ref p) => (sidecar.to_int(), p),
+            _ => return false,
+        };
+        let p = Path::new(sidecar_t.1);
+        let ext = match p.extension() {
+            Some(ext2) => ext2.to_string_lossy(),
             _ => return false,
         };
         let fsfile_id = self.add_fs_file(&sidecar_t.1);
         if fsfile_id == -1 {
             return false;
         }
+        self.add_sidecar_fsfile_to_bundle(file_id, fsfile_id, sidecar_t.0, &*ext)
+    }
+
+    fn add_sidecar_fsfile_to_bundle(
+        &self,
+        file_id: LibraryId,
+        fsfile_id: LibraryId,
+        sidecar_type: i32,
+        ext: &str,
+    ) -> bool {
         if let Some(ref conn) = self.dbconn {
             if let Ok(c) = conn.execute(
-                "INSERT INTO sidecars (file_id, fsfile_id, type) VALUES(:1, :2, :3)",
-                &[&file_id, &fsfile_id, &sidecar_t.0],
+                "INSERT INTO sidecars (file_id, fsfile_id, type, ext) VALUES(:1, :2, :3, :4)",
+                &[&file_id, &fsfile_id, &sidecar_type, &ext],
             ) {
                 if c == 1 {
                     return true;
@@ -474,12 +492,24 @@ impl Library {
                 let fsfile_id = self.add_fs_file(bundle.xmp_sidecar());
                 if fsfile_id > 0 {
                     self.add_xmp_sidecar_to_bundle(file_id, fsfile_id);
+                    self.add_sidecar_fsfile_to_bundle(
+                        file_id,
+                        fsfile_id,
+                        Sidecar::Xmp(String::new()).to_int(),
+                        "xmp",
+                    );
                 }
             }
             if !bundle.jpeg().is_empty() {
                 let fsfile_id = self.add_fs_file(bundle.jpeg());
                 if fsfile_id > 0 {
                     self.add_jpeg_file_to_bundle(file_id, fsfile_id);
+                    self.add_sidecar_fsfile_to_bundle(
+                        file_id,
+                        fsfile_id,
+                        Sidecar::Jpeg(String::new()).to_int(),
+                        "jpg",
+                    );
                 }
             }
         }
@@ -635,14 +665,22 @@ impl Library {
     pub fn get_metadata(&self, file_id: LibraryId) -> Option<LibMetadata> {
         let conn = try_opt!(self.dbconn.as_ref());
         let sql = format!(
-            "SELECT {} FROM {} WHERE id=:1",
+            "SELECT {} FROM {} WHERE files.id=:1",
             LibMetadata::read_db_columns(),
             LibMetadata::read_db_tables()
         );
         let mut stmt = try_opt!(conn.prepare(&sql).ok());
         let mut rows = try_opt!(stmt.query(&[&file_id]).ok());
         let row = try_opt!(try_opt!(rows.next()).ok());
-        return Some(LibMetadata::read_from(&row));
+        let mut metadata = LibMetadata::read_from(&row);
+
+        let sql = "SELECT ext FROM sidecars WHERE file_id=:1";
+        let mut stmt = try_opt!(conn.prepare(&sql).ok());
+        let mut rows = try_opt!(stmt.query(&[&file_id]).ok());
+        while let Some(Ok(row)) = rows.next() {
+            metadata.sidecars.push(row.get(0));
+        }
+        return Some(metadata);
     }
 
     fn unassign_all_keywords_for_file(&self, file_id: LibraryId) -> bool {
diff --git a/src/engine/db/metadata.hpp b/src/engine/db/metadata.hpp
index e80856c..8818f65 100644
--- a/src/engine/db/metadata.hpp
+++ b/src/engine/db/metadata.hpp
@@ -29,7 +29,9 @@ enum {
     META_NS_TIFF,
     META_NS_EXIF,
     META_NS_IPTC,
-    META_NS_NIEPCE
+    META_NS_NIEPCE,
+
+    META_NS_INTERNAL = 0x0f00,
 };
 
 /** Metadata for xmpcore. Combine with %META_NS_XMPCORE */
@@ -81,6 +83,16 @@ enum {
     META_NIEPCE_FLAG = 0
 };
 
+
+/** Internal metadata */
+enum {
+    META_INTERNAL_FILENAME = 0,
+    META_INTERNAL_FILETYPE,
+    META_INTERNAL_FILESIZE,
+    META_INTERNAL_FOLDER,
+    META_INTERNAL_SIDECARS,
+};
+
 /** make a metadata index with a namespace and a value */
 #define MAKE_METADATA_IDX(x,y) (x<<16|y)
 /** get the NS form the metadata index */
diff --git a/src/engine/db/properties-def.hpp b/src/engine/db/properties-def.hpp
index fdf0cbf..37ff0ea 100644
--- a/src/engine/db/properties-def.hpp
+++ b/src/engine/db/properties-def.hpp
@@ -1,7 +1,7 @@
 /*
- * niepce - eng/db/proeprties-def.hpp
+ * niepce - eng/db/properties-def.hpp
  *
- * Copyright (C) 2011 Hubert Figuiere
+ * Copyright (C) 2011-2018 Hubert Figuiere
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,13 +21,19 @@
 #error DEFINE_PROPERTY must be defined
 #endif
 
-// format is 
+// format is
 // - const
 // - the value of the id
 // - XMP NS - NULL if none
 // - XMP property - NULL if none
 // - type
 
+DEFINE_PROPERTY(NpFileNameProp, MAKE_METADATA_IDX(META_NS_INTERNAL, META_INTERNAL_FILENAME), nullptr, 
nullptr, std::string)
+DEFINE_PROPERTY(NpFileTypeProp, MAKE_METADATA_IDX(META_NS_INTERNAL, META_INTERNAL_FILETYPE), nullptr, 
nullptr, std::string)
+DEFINE_PROPERTY(NpFileSizeProp, MAKE_METADATA_IDX(META_NS_INTERNAL, META_INTERNAL_FILESIZE), nullptr, 
nullptr, off_t)
+DEFINE_PROPERTY(NpFolderProp, MAKE_METADATA_IDX(META_NS_INTERNAL, META_INTERNAL_FOLDER), nullptr, nullptr, 
std::string)
+DEFINE_PROPERTY(NpSidecarsProp, MAKE_METADATA_IDX(META_NS_INTERNAL, META_INTERNAL_SIDECARS), nullptr, 
nullptr, std::vector<std::string>)
+
 DEFINE_PROPERTY(NpXmpRatingProp, MAKE_METADATA_IDX(META_NS_XMPCORE, META_XMPCORE_RATING), NS_XAP, "Rating", 
int32_t )
 DEFINE_PROPERTY(NpXmpLabelProp,  MAKE_METADATA_IDX(META_NS_XMPCORE, META_XMPCORE_LABEL), NS_XAP, "Label", 
int32_t )
 
diff --git a/src/fwk/toolkit/metadatawidget.cpp b/src/fwk/toolkit/metadatawidget.cpp
index 39f846e..b393914 100644
--- a/src/fwk/toolkit/metadatawidget.cpp
+++ b/src/fwk/toolkit/metadatawidget.cpp
@@ -318,13 +318,15 @@ bool MetaDataWidget::set_star_rating_data(Gtk::Widget* w,
     return true;
 }
 
-bool MetaDataWidget::set_string_array_data(Gtk::Widget* w, const PropertyValuePtr& value)
+bool MetaDataWidget::set_string_array_data(Gtk::Widget* w, bool readonly,
+                                           const PropertyValuePtr& value)
 {
     try {
         AutoFlag flag(m_update);
         std::vector<std::string> tokens = fwk::property_value_get_string_array(*value);
 
         static_cast<fwk::TokenTextView*>(w)->set_tokens(tokens);
+        static_cast<fwk::TokenTextView*>(w)->set_editable(!readonly);
     }
     catch(...) {
         return false;
@@ -431,7 +433,7 @@ void MetaDataWidget::add_data(const MetaDataFormat * current,
         set_star_rating_data(w, value);
         break;
     case MetaDT::STRING_ARRAY:
-        set_string_array_data(w, value);
+        set_string_array_data(w, current->readonly, value);
         break;
     case MetaDT::TEXT:
         set_text_data(w, current->readonly, value);
diff --git a/src/fwk/toolkit/metadatawidget.hpp b/src/fwk/toolkit/metadatawidget.hpp
index 8a45cd1..24459bd 100644
--- a/src/fwk/toolkit/metadatawidget.hpp
+++ b/src/fwk/toolkit/metadatawidget.hpp
@@ -44,7 +44,8 @@ enum class MetaDT {
     DATE,
     FRAC,
     FRAC_DEC, // Fraction as decimal
-    STAR_RATING
+    STAR_RATING,
+    SIZE,     // Size in bytes
 };
 
 struct MetaDataFormat {
@@ -97,7 +98,7 @@ private:
     // Fraction as fraction
     bool set_fraction_data(Gtk::Widget* w, const PropertyValuePtr& value);
     bool set_star_rating_data(Gtk::Widget* w, const PropertyValuePtr& value);
-    bool set_string_array_data(Gtk::Widget* w, const PropertyValuePtr& value);
+    bool set_string_array_data(Gtk::Widget* w, bool readonly, const PropertyValuePtr& value);
     bool set_text_data(Gtk::Widget* w, bool readonly,
                        const PropertyValuePtr& value);
     bool set_string_data(Gtk::Widget* w, bool readonly,
diff --git a/src/niepce/ui/metadatapanecontroller.cpp b/src/niepce/ui/metadatapanecontroller.cpp
index 760bb22..a3aa3e0 100644
--- a/src/niepce/ui/metadatapanecontroller.cpp
+++ b/src/niepce/ui/metadatapanecontroller.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/ui/metadatapanecontroller.cpp
  *
- * Copyright (C) 2008-2013 Hubert Figuiere
+ * Copyright (C) 2008-2018 Hubert Figuiere
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -32,6 +32,14 @@ namespace ui {
 const fwk::MetaDataSectionFormat *
 MetaDataPaneController::get_format()
 {
+    static const fwk::MetaDataFormat s_fileinfo_format[] = {
+        { _("File Name:"), eng::NpFileNameProp, fwk::MetaDT::STRING, true },
+        { _("Folder:"), eng::NpFolderProp, fwk::MetaDT::STRING, true },
+        { _("File Type:"), eng::NpFileTypeProp, fwk::MetaDT::STRING, true },
+        { _("File Size:"), eng::NpFileSizeProp, fwk::MetaDT::SIZE, true },
+        { _("Sidecar Files:"), eng::NpSidecarsProp, fwk::MetaDT::STRING_ARRAY, true },
+        { nullptr, 0, fwk::MetaDT::NONE, true }
+    };
     static const fwk::MetaDataFormat s_camerainfo_format[] = {
         { _("Make:"), eng::NpTiffMakeProp, fwk::MetaDT::STRING, true },
         { _("Model:"), eng::NpTiffModelProp, fwk::MetaDT::STRING, true },
@@ -61,6 +69,9 @@ MetaDataPaneController::get_format()
         { nullptr, 0, fwk::MetaDT::NONE, true }
     };
     static const fwk::MetaDataSectionFormat s_format[] = {
+        { _("File Information"),
+          s_fileinfo_format
+        },
         { _("Camera Information"),
           s_camerainfo_format
         },


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]