[niepce/lr-import: 7/20] Issue #40 - Added Albums to the database model




commit ed3c61bcc3c4813fb1ab3fe121ba7b87fa033121
Author: Hubert Figuière <hub figuiere net>
Date:   Sat Nov 13 22:49:51 2021 -0500

    Issue #40 - Added Albums to the database model
    
    - Bump DB version to 10
    
    https://gitlab.gnome.org/GNOME/niepce/-/issues/40

 crates/npc-engine/src/db.rs                        |  9 +++
 crates/npc-engine/src/db/album.rs                  | 91 ++++++++++++++++++++++
 crates/npc-engine/src/db/library.rs                | 59 +++++++++++++-
 crates/npc-engine/src/importer/lrimporter.rs       | 13 +++-
 crates/npc-engine/src/library/commands.rs          | 35 +++++++++
 crates/npc-engine/src/library/notification.rs      |  5 +-
 crates/npc-engine/src/libraryclient.rs             | 14 ++++
 crates/npc-engine/src/libraryclient/clientimpl.rs  | 25 +++++-
 .../src/libraryclient/clientinterface.rs           |  6 ++
 doc/database.txt                                   |  9 +++
 src/Makefile.am                                    |  1 +
 11 files changed, 262 insertions(+), 5 deletions(-)
---
diff --git a/crates/npc-engine/src/db.rs b/crates/npc-engine/src/db.rs
index faa2309..902fb9b 100644
--- a/crates/npc-engine/src/db.rs
+++ b/crates/npc-engine/src/db.rs
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+pub mod album;
 pub mod filebundle;
 pub mod fsfile;
 pub mod keyword;
@@ -29,7 +30,15 @@ pub mod props;
 
 pub type LibraryId = i64;
 
+#[derive(Copy, Clone, PartialEq)]
+pub enum SortOrder {
+    NoSorting,
+    Ascending,
+    Descending,
+}
+
 // flatten namespace a bit.
+pub use self::album::Album;
 pub use self::keyword::Keyword;
 pub use self::label::Label;
 pub use self::libfolder::LibFolder;
diff --git a/crates/npc-engine/src/db/album.rs b/crates/npc-engine/src/db/album.rs
new file mode 100644
index 0000000..58f62ea
--- /dev/null
+++ b/crates/npc-engine/src/db/album.rs
@@ -0,0 +1,91 @@
+/*
+ * niepce - npc-engine/db/album.rs
+ *
+ * Copyright (C) 2021 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+use super::FromDb;
+use super::LibraryId;
+use super::SortOrder;
+
+/// Represents an album, that contains image
+#[derive(Clone)]
+pub struct Album {
+    /// Album ID
+    id: LibraryId,
+    /// Album name as displayed
+    name: String,
+    /// Album Parent. -1 for no parent.
+    parent: LibraryId,
+    /// Sorting
+    order: SortOrder,
+    /// Key
+    order_by: String,
+}
+
+impl Album {
+    pub fn new(id: LibraryId, name: &str, parent: LibraryId) -> Self {
+        Album {
+            id,
+            name: name.to_owned(),
+            parent,
+            order: SortOrder::NoSorting,
+            order_by: "".to_owned(),
+        }
+    }
+
+    /// Get the album ID
+    pub fn id(&self) -> LibraryId {
+        self.id
+    }
+
+    /// Get the album name
+    pub fn name(&self) -> &str {
+        &self.name
+    }
+
+    /// Get parent album ID.
+    pub fn parent(&self) -> LibraryId {
+        self.parent
+    }
+
+    pub fn order(&self) -> SortOrder {
+        self.order
+    }
+
+    pub fn set_order(&mut self, order: SortOrder) {
+        self.order = order;
+    }
+}
+
+impl FromDb for Album {
+    fn read_db_columns() -> &'static str {
+        "id,name,parent_id"
+    }
+
+    fn read_db_tables() -> &'static str {
+        "albums"
+    }
+
+    fn read_db_where_id() -> &'static str {
+        "id"
+    }
+
+    fn read_from(row: &rusqlite::Row) -> rusqlite::Result<Self> {
+        let name: String = row.get(1)?;
+        Ok(Album::new(row.get(0)?, &name, row.get(2)?))
+    }
+}
diff --git a/crates/npc-engine/src/db/library.rs b/crates/npc-engine/src/db/library.rs
index d5decf7..6e010a4 100644
--- a/crates/npc-engine/src/db/library.rs
+++ b/crates/npc-engine/src/db/library.rs
@@ -29,6 +29,7 @@ use rusqlite::{functions::FunctionFlags, params};
 use super::props::NiepceProperties as Np;
 use super::props::NiepcePropertyIdx::*;
 use super::{FromDb, LibraryId};
+use crate::db::album::Album;
 use crate::db::filebundle::{FileBundle, Sidecar};
 use crate::db::keyword::Keyword;
 use crate::db::label::Label;
@@ -48,7 +49,7 @@ pub enum Managed {
     YES = 1,
 }
 
-const DB_SCHEMA_VERSION: i32 = 9;
+const DB_SCHEMA_VERSION: i32 = 10;
 const DATABASENAME: &str = "niepcelibrary.db";
 
 #[derive(Debug)]
@@ -258,6 +259,22 @@ impl Library {
             )
             .unwrap();
 
+            // version 10
+            conn.execute(
+                "CREATE TABLE albums (id INTEGER PRIMARY KEY,\
+                 name TEXT, \
+                 parent_id INTEGER)",
+                [],
+            )
+            .unwrap();
+            conn.execute(
+                "CREATE TABLE albuming (\
+                 file_id INTEGER, album_id INTEGER)",
+                [],
+            )
+            .unwrap();
+            //
+
             conn.execute(
                 "CREATE TABLE files (id INTEGER PRIMARY KEY,\
                  main_file INTEGER, name TEXT, parent_id INTEGER,\
@@ -295,6 +312,11 @@ impl Library {
                  BEGIN \
                  DELETE FROM sidecars WHERE file_id = old.id; \
                  DELETE FROM keywording WHERE file_id = old.id; \
+                 DELETE FROM albuming WHERE file_id = old.id; \
+                 END; \
+                 CREATE TRIGGER album_delete_trigger AFTER DELETE ON albums \
+                 BEGIN \
+                 DELETE FROM albuming WHERE album_id = old.id; \
                  END; \
                  COMMIT;",
             )
@@ -605,6 +627,41 @@ impl Library {
         Err(Error::NoSqlDb)
     }
 
+    /// Add an album to the library
+    pub fn add_album(&self, name: &str, parent: LibraryId) -> Result<Album> {
+        if let Some(ref conn) = self.dbconn {
+            let c = conn.execute(
+                "INSERT INTO albums (name,parent_id) VALUES(?1, ?2)",
+                params![name, parent],
+            )?;
+            if c != 1 {
+                return Err(Error::InvalidResult);
+            }
+            let id = conn.last_insert_rowid();
+            return Ok(Album::new(id, &name, parent));
+        }
+        Err(Error::NoSqlDb)
+    }
+
+    /// Get all the albums.
+    pub fn get_all_albums(&self) -> Result<Vec<Album>> {
+        if let Some(ref conn) = self.dbconn {
+            let sql = format!(
+                "SELECT {} FROM {}",
+                Album::read_db_columns(),
+                Album::read_db_tables()
+            );
+            let mut stmt = conn.prepare(&sql)?;
+            let mut rows = stmt.query([])?;
+            let mut albums: Vec<Album> = vec![];
+            while let Ok(Some(row)) = rows.next() {
+                albums.push(Album::read_from(&row)?);
+            }
+            return Ok(albums);
+        }
+        Err(Error::NoSqlDb)
+    }
+
     pub fn add_fs_file<P: AsRef<Path>>(&self, f: P) -> Result<LibraryId> {
         if let Some(ref conn) = self.dbconn {
             let file = f.as_ref().to_string_lossy();
diff --git a/crates/npc-engine/src/importer/lrimporter.rs b/crates/npc-engine/src/importer/lrimporter.rs
index 349dd08..7f6c602 100644
--- a/crates/npc-engine/src/importer/lrimporter.rs
+++ b/crates/npc-engine/src/importer/lrimporter.rs
@@ -22,7 +22,9 @@ use gettextrs::gettext;
 use std::collections::BTreeMap;
 use std::path::Path;
 
-use lrcat::{Catalog, CatalogVersion, Folder, Keyword, KeywordTree, LibraryFile, LrId, LrObject};
+use lrcat::{
+    Catalog, CatalogVersion, Collection, Folder, Keyword, KeywordTree, LibraryFile, LrId, LrObject,
+};
 
 use super::libraryimporter::LibraryImporter;
 use crate::db::filebundle::FileBundle;
@@ -69,6 +71,12 @@ impl LrImporter {
         self.folder_map.insert(folder.id(), (nid, path.into()));
     }
 
+    fn import_collection(&mut self, collection: &Collection, libclient: &mut LibraryClient) {
+        let parent = self.collection_map.get(&collection.parent).unwrap_or(&-1);
+        let nid = libclient.create_album_sync(collection.name.clone(), *parent);
+        self.collection_map.insert(collection.id(), nid);
+    }
+
     fn import_library_file(&mut self, file: &LibraryFile, libclient: &mut LibraryClient) {
         if let Some(folder_id) = self.folder_map.get(&file.folder) {
             let main_file = format!("{}/{}.{}", &folder_id.1, &file.basename, &file.extension);
@@ -144,7 +152,8 @@ impl LibraryImporter for LrImporter {
         let collections = catalog.load_collections();
         collections.iter().for_each(|collection| {
             if !collection.system_only {
-                dbg_out!("Found collection {}", collection.name);
+                dbg_out!("Found collection {}", &collection.name);
+                self.import_collection(&collection, libclient);
             }
         });
 
diff --git a/crates/npc-engine/src/library/commands.rs b/crates/npc-engine/src/library/commands.rs
index 6ecc0c1..38e66f2 100644
--- a/crates/npc-engine/src/library/commands.rs
+++ b/crates/npc-engine/src/library/commands.rs
@@ -178,6 +178,41 @@ pub fn cmd_delete_folder(lib: &Library, id: LibraryId) -> bool {
     }
 }
 
+pub fn cmd_list_all_albums(lib: &Library) -> bool {
+    match lib.get_all_albums() {
+        Ok(albums) => {
+            // XXX change this notification type
+            for album in albums {
+                if let Err(err) = lib.notify(LibNotification::AddedAlbum(album)) {
+                    err_out!("Failed to notify AddedAlbum {:?}", err);
+                    return false;
+                }
+            }
+            true
+        }
+        Err(err) => {
+            err_out_line!("get_all_albums failed: {:?}", err);
+            false
+        }
+    }
+}
+
+pub fn cmd_create_album(lib: &Library, name: &str, parent: LibraryId) -> LibraryId {
+    match lib.add_album(name, parent) {
+        Ok(album) => {
+            let id = album.id();
+            if lib.notify(LibNotification::AddedAlbum(album)).is_err() {
+                err_out!("Failed to notify AddedAlbum");
+            }
+            id
+        }
+        Err(err) => {
+            err_out_line!("Album creation failed {:?}", err);
+            -1
+        }
+    }
+}
+
 pub fn cmd_request_metadata(lib: &Library, file_id: LibraryId) -> bool {
     match lib.get_metadata(file_id) {
         Ok(lm) => {
diff --git a/crates/npc-engine/src/library/notification.rs b/crates/npc-engine/src/library/notification.rs
index 30452c9..86bf61e 100644
--- a/crates/npc-engine/src/library/notification.rs
+++ b/crates/npc-engine/src/library/notification.rs
@@ -19,7 +19,7 @@
 
 use super::queriedcontent::QueriedContent;
 use crate::db::libfile::FileStatus;
-use crate::db::{Keyword, Label, LibFolder, LibMetadata, LibraryId, NiepceProperties};
+use crate::db::{Album, Keyword, Label, LibFolder, LibMetadata, LibraryId, NiepceProperties};
 use npc_fwk::base::PropertyIndex;
 use npc_fwk::toolkit;
 use npc_fwk::toolkit::thumbnail;
@@ -38,6 +38,7 @@ pub enum NotificationType {
     ADDED_FILES,
     ADDED_KEYWORD,
     ADDED_LABEL,
+    ADDED_ALBUM,
     FOLDER_CONTENT_QUERIED,
     FOLDER_DELETED,
     FOLDER_COUNTED,
@@ -121,6 +122,7 @@ pub enum LibNotification {
     AddedFolder(LibFolder),
     AddedKeyword(Keyword),
     AddedLabel(Label),
+    AddedAlbum(Album),
     FileMoved(FileMove),
     FileStatusChanged(FileStatusChange),
     FolderContentQueried(QueriedContent),
@@ -175,6 +177,7 @@ pub unsafe extern "C" fn engine_library_notification_type(
         Some(&LibNotification::AddedFolder(_)) => NotificationType::ADDED_FOLDER,
         Some(&LibNotification::AddedKeyword(_)) => NotificationType::ADDED_KEYWORD,
         Some(&LibNotification::AddedLabel(_)) => NotificationType::ADDED_LABEL,
+        Some(&LibNotification::AddedAlbum(_)) => NotificationType::ADDED_ALBUM,
         Some(&LibNotification::FileMoved(_)) => NotificationType::FILE_MOVED,
         Some(&LibNotification::FileStatusChanged(_)) => NotificationType::FILE_STATUS_CHANGED,
         Some(&LibNotification::FolderContentQueried(_)) => NotificationType::FOLDER_CONTENT_QUERIED,
diff --git a/crates/npc-engine/src/libraryclient.rs b/crates/npc-engine/src/libraryclient.rs
index 38207cf..36958fa 100644
--- a/crates/npc-engine/src/libraryclient.rs
+++ b/crates/npc-engine/src/libraryclient.rs
@@ -116,6 +116,16 @@ impl ClientInterface for LibraryClient {
         self.pimpl.delete_folder(id);
     }
 
+    /// get all the albums
+    fn get_all_albums(&mut self) {
+        self.pimpl.get_all_albums();
+    }
+
+    /// Create an album (async)
+    fn create_album(&mut self, name: String, parent: LibraryId) {
+        self.pimpl.create_album(name, parent);
+    }
+
     fn request_metadata(&mut self, id: LibraryId) {
         self.pimpl.request_metadata(id);
     }
@@ -171,6 +181,10 @@ impl ClientInterfaceSync for LibraryClient {
         self.pimpl.create_folder_sync(name, path)
     }
 
+    fn create_album_sync(&mut self, name: String, parent: LibraryId) -> LibraryId {
+        self.pimpl.create_album_sync(name, parent)
+    }
+
     fn add_bundle_sync(&mut self, bundle: &FileBundle, folder: LibraryId) -> LibraryId {
         self.pimpl.add_bundle_sync(bundle, folder)
     }
diff --git a/crates/npc-engine/src/libraryclient/clientimpl.rs 
b/crates/npc-engine/src/libraryclient/clientimpl.rs
index 41804c7..f2808e7 100644
--- a/crates/npc-engine/src/libraryclient/clientimpl.rs
+++ b/crates/npc-engine/src/libraryclient/clientimpl.rs
@@ -125,7 +125,7 @@ impl ClientInterface for ClientImpl {
         self.schedule_op(move |lib| commands::cmd_count_keyword(&lib, id));
     }
 
-    /// get all the folder
+    /// get all the folders
     fn get_all_folders(&mut self) {
         self.schedule_op(move |lib| commands::cmd_list_all_folders(&lib));
     }
@@ -146,6 +146,16 @@ impl ClientInterface for ClientImpl {
         self.schedule_op(move |lib| commands::cmd_delete_folder(&lib, id));
     }
 
+    /// get all the albums
+    fn get_all_albums(&mut self) {
+        self.schedule_op(move |lib| commands::cmd_list_all_albums(&lib));
+    }
+
+    /// Create an album (async)
+    fn create_album(&mut self, name: String, parent: LibraryId) {
+        self.schedule_op(move |lib| commands::cmd_create_album(&lib, &name, parent) != 0);
+    }
+
     fn request_metadata(&mut self, file_id: LibraryId) {
         self.schedule_op(move |lib| commands::cmd_request_metadata(&lib, file_id));
     }
@@ -232,6 +242,19 @@ impl ClientInterfaceSync for ClientImpl {
         rx.recv().unwrap()
     }
 
+    fn create_album_sync(&mut self, name: String, parent: LibraryId) -> LibraryId {
+        // can't use futures::sync::oneshot
+        let (tx, rx) = mpsc::sync_channel::<LibraryId>(1);
+
+        self.schedule_op(move |lib| {
+            tx.send(commands::cmd_create_album(&lib, &name, parent))
+                .unwrap();
+            true
+        });
+
+        rx.recv().unwrap()
+    }
+
     fn add_bundle_sync(&mut self, bundle: &FileBundle, folder: LibraryId) -> LibraryId {
         let (tx, rx) = mpsc::sync_channel::<LibraryId>(1);
 
diff --git a/crates/npc-engine/src/libraryclient/clientinterface.rs 
b/crates/npc-engine/src/libraryclient/clientinterface.rs
index 88aaa1f..80ba1ee 100644
--- a/crates/npc-engine/src/libraryclient/clientinterface.rs
+++ b/crates/npc-engine/src/libraryclient/clientinterface.rs
@@ -39,6 +39,9 @@ pub trait ClientInterface {
     fn create_folder(&mut self, name: String, path: Option<String>);
     fn delete_folder(&mut self, id: LibraryId);
 
+    fn get_all_albums(&mut self);
+    fn create_album(&mut self, name: String, parent: LibraryId);
+
     fn request_metadata(&mut self, id: LibraryId);
     /// set the metadata
     fn set_metadata(&mut self, id: LibraryId, meta: Np, value: &PropertyValue);
@@ -74,6 +77,9 @@ pub trait ClientInterfaceSync {
     /// Create a folder. Return the id of the newly created folder.
     fn create_folder_sync(&mut self, name: String, path: Option<String>) -> LibraryId;
 
+    /// Create an album. Return the id to the newly created album.
+    fn create_album_sync(&mut self, name: String, parent: LibraryId) -> LibraryId;
+
     /// Add a bundle.
     fn add_bundle_sync(&mut self, bundle: &FileBundle, folder: LibraryId) -> LibraryId;
 }
diff --git a/doc/database.txt b/doc/database.txt
index 48439cd..29b3537 100644
--- a/doc/database.txt
+++ b/doc/database.txt
@@ -77,6 +77,15 @@ labels          id             The ID of the label
                 name           The name of the label (user displayed)
                color          The RGB8 color in "R G B" format.
 
+Albums contain files
+
+albums          id             The ID of the album
+                name           The name of the album (user displayed)
+                parent_id      The parent album. -1 means on the top
+
+albuming        file_id        The file in the album.
+                album_id       The album the file is in.
+
 The update queue for XMP. When an XMP is changed in the DB it is
 queued in the table.
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 9d7bf4b..6f620f7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -10,6 +10,7 @@ RUST_SOURCES = \
        @top_srcdir@/crates/npc-engine/Cargo.toml \
        @top_srcdir@/crates/npc-engine/build.rs \
        @top_srcdir@/crates/npc-engine/src/db.rs \
+       @top_srcdir@/crates/npc-engine/src/db/album.rs \
        @top_srcdir@/crates/npc-engine/src/db/filebundle.rs \
        @top_srcdir@/crates/npc-engine/src/db/fsfile.rs \
        @top_srcdir@/crates/npc-engine/src/db/keyword.rs \


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