]> uap-core.de Git - note.git/commitdiff
implement creating notes from files main
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Sat, 8 Aug 2026 11:59:40 +0000 (13:59 +0200)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Sat, 8 Aug 2026 11:59:40 +0000 (13:59 +0200)
application/backend/src/backend.rs
application/backend/src/storage.rs
application/note/src/notebook.rs

index 551643ea3f465110f051b6162f0e501279524c39..39fd72d4e7cea465b3c1e501939158bae4cd98ab 100644 (file)
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  */
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  */
+use std::ffi::OsStr;
 use std::fs::Metadata;
 use std::future::Future;
 use std::fs::Metadata;
 use std::future::Future;
-use std::io::Error;
+use std::io::{Error, ErrorKind};
 use std::path::{Path, PathBuf};
 use std::pin::Pin;
 use sea_orm::{ActiveModelTrait, Database, DatabaseConnection, EntityTrait, QueryFilter, ColumnTrait, Set, QueryOrder, DbErr, ExprTrait, QuerySelect};
 use std::path::{Path, PathBuf};
 use std::pin::Pin;
 use sea_orm::{ActiveModelTrait, Database, DatabaseConnection, EntityTrait, QueryFilter, ColumnTrait, Set, QueryOrder, DbErr, ExprTrait, QuerySelect};
@@ -44,7 +45,7 @@ use migration::{Expr, Migrator, MigratorTrait};
 use entity::{collection, note, notecontent, profile, repository};
 use entity::profile::Entity as Profile;
 use entity::collection::{create_notebook_hierarchy, CollectionType, Entity as Collection, Node};
 use entity::{collection, note, notecontent, profile, repository};
 use entity::profile::Entity as Profile;
 use entity::collection::{create_notebook_hierarchy, CollectionType, Entity as Collection, Node};
-use entity::note::{Column, Entity as Note};
+use entity::note::{Column, Entity as Note, NoteType};
 use entity::notecontent::{Entity as NoteContent};
 use migration::prelude::Utc;
 use crate::lockmanager::LockManager;
 use entity::notecontent::{Entity as NoteContent};
 use migration::prelude::Utc;
 use crate::lockmanager::LockManager;
@@ -779,6 +780,86 @@ impl BackendHandle {
         let _ = self.tx.send(cmd);
     }
 
         let _ = self.tx.send(cmd);
     }
 
+    pub fn add_file_note<F>(&self, collection_id: i32, path: &Path, callback: F)
+    where F: FnOnce(Result<note::Model, DbErr>) + Send + 'static {
+        let bhandle = self.clone();
+        let pathbuf = path.to_path_buf();
+        let cmd = Box::pin(async move {
+            let name = pathbuf.as_path().file_name();
+            // check file and get the file name
+            let result: Result<&OsStr, Error> = async {
+                let metadata = fs::metadata(pathbuf.as_path()).await?;
+                if !metadata.is_file() {
+                    let msg = format!("{} is not a valid file", pathbuf.display());
+                    return Err(Error::new(ErrorKind::NotFound, msg));
+                }
+                let name = pathbuf.file_name();
+                if let Some(name) = name {
+                    Ok(name)
+                } else {
+                    // this shouldn't happen because the path points to a file
+                    Err(Error::new(ErrorKind::NotFound, "no file name".to_string()))
+                }
+            }.await;
+
+            let name = match result {
+                Ok(name) => {
+                    let name = name.to_string_lossy().to_string();
+                    if name.len() > 0 {
+                        name
+                    } else {
+                        // in case to_string_lossy removes everything
+                        "new_file".to_string()
+                    }
+                },
+                Err(e) => {
+                    callback(Err(DbErr::Custom(e.to_string())));
+                    return;
+                }
+            };
+
+            let result: Result<note::Model, DbErr> = async {
+                // prepare db insert
+                let nodename = generate_nodename_in_collection(&bhandle.backend.db, collection_id, name.as_str()).await?;
+                let title = nodename.clone(); // TODO
+                let contenttype = "image/png".to_string(); // TODO
+                let insert = note::ActiveModel {
+                    collection_id: Set(collection_id),
+                    nodename: Set(nodename),
+                    kind: Set(NoteType::File),
+                    title: Set(title),
+                    content_type: Set(contenttype),
+                    lastmodified: Set(Utc::now().into()),
+                    created: Set(Utc::now().into()),
+                    fixed_title: Set(true),
+                    version: Set(0),
+
+                    ..Default::default()
+                };
+                // do insert
+                let model = insert.insert(&bhandle.backend.db).await?;
+
+                // if local storage is enabled, copy the note to the note path
+                let note_local_path = get_note_storage_path(&bhandle.backend.db, model.note_id).await?;
+                if let Some(note_path) = note_local_path {
+                    // copy file to new note path
+                    let to_path = Path::new(&note_path);
+                    let copy_result = fs::copy(pathbuf.as_path(), to_path).await;
+                    if let Err(e) = copy_result {
+                        return Err(DbErr::Custom(e.to_string()))
+                    }
+                } else {
+                    // copy note content to the database
+                    // TODO
+                }
+
+                Ok(model)
+            }.await;
+            callback(result);
+        });
+        let _ = self.tx.send(cmd);
+    }
+
     pub fn get_file_metadata<F>(&self, path: &Path, callback: F)
     where F: FnOnce(std::io::Result<Metadata>) + Send + 'static {
         let p = PathBuf::from(path);
     pub fn get_file_metadata<F>(&self, path: &Path, callback: F)
     where F: FnOnce(std::io::Result<Metadata>) + Send + 'static {
         let p = PathBuf::from(path);
index 59694a708ce63152e47ad5335370ad453aa2a7c2..7ab717874741a9ce0fb31a75a6f72e57cf501638 100644 (file)
@@ -38,6 +38,7 @@ use entity::{collection, filecontent, note, repository};
 use entity::collection::LocalStorageSetting;
 use entity::filecontent::Model as FileContent;
 use migration::JoinType;
 use entity::collection::LocalStorageSetting;
 use entity::filecontent::Model as FileContent;
 use migration::JoinType;
+use crate::note::randomize_nodename;
 
 /// Checks if a collection should use local storage
 fn uses_local_storage(settings: LocalStorageSetting, repo_use_local: bool) -> bool {
 
 /// Checks if a collection should use local storage
 fn uses_local_storage(settings: LocalStorageSetting, repo_use_local: bool) -> bool {
@@ -170,4 +171,40 @@ pub async fn check_free_nodename(db: &DatabaseConnection, note_id: i32, nodename
     }
 
     Ok(true)
     }
 
     Ok(true)
+}
+
+pub async fn generate_nodename_in_collection(db: &DatabaseConnection, collection_id: i32, nodename: &str) -> Result<String, DbErr> {
+    let exists = note::Entity::find()
+        .filter(note::Column::CollectionId.eq(collection_id))
+        .filter(note::Column::Nodename.eq(nodename))
+        .select_only()
+        .column(note::Column::NoteId)
+        .into_tuple::<i32>()
+        .one(db).await?;
+
+    let collection_storage_path = get_collection_storage_path(db, collection_id).await?;
+    let mut local_exists = false;
+    if let Some(collection_path) = collection_storage_path {
+        let path = Path::new(&collection_path);
+        let node_path = path.join(nodename);
+        let metadata = fs::metadata(node_path).await;
+        match metadata {
+            Ok(metadata) => {
+                local_exists = true;
+            },
+            Err(err) if err.kind() == ErrorKind::NotFound => {
+                // not an error, not found means the nodename is free
+            },
+            Err(err) => {
+                return Err(DbErr::Custom(err.to_string()));
+            }
+        }
+    }
+
+    if exists.is_none() && !local_exists {
+        Ok(nodename.to_string())
+    } else {
+        // in theory the randomized nodename could exists, however it is very unlikely
+        Ok(randomize_nodename(nodename))
+    }
 }
\ No newline at end of file
 }
\ No newline at end of file
index 14712cbb3fabef2b5ad0484d537f69e4e36e5643..e83bd18c12d157416c24bfea642a0487d550c949 100644 (file)
@@ -297,20 +297,50 @@ impl Notebook {
 
     #[action]
     pub fn new_file_note(&mut self, event: &ActionEvent) {
 
     #[action]
     pub fn new_file_note(&mut self, event: &ActionEvent) {
-        println!("new file note");
-
         let Some(doc) = self.doc_ref.get_doc() else {
             return;
         };
         let Some(doc) = self.doc_ref.get_doc() else {
             return;
         };
-        let proxy = doc.doc_proxy();
 
         if let Some(obj) = &event.obj {
 
         if let Some(obj) = &event.obj {
+            // TODO: toolkit feature request: it would be really nice, if I could specify
+            //       an openfile_dialog action, which is called on the parent (toolkit issue #965)
             obj.openfile_dialog(false, move|event|{
             obj.openfile_dialog(false, move|event|{
-
+                if let EventType::FileList(files) = event.event_type && files.len() == 1 {
+                    let file = files[0].clone();
+                    doc.ctx.call_action_with_parameter("add_file_note", Box::new(file));
+                }
             });
         }
     }
 
             });
         }
     }
 
+    #[action(String)]
+    pub fn add_file_note(&mut self, _event: &ActionEvent, path: &String) {
+        println!("add_file_note: {}", path);
+
+        let Some(doc) = self.doc_ref.get_doc() else {
+            return;
+        };
+        let proxy = doc.doc_proxy();
+
+        let path = Path::new(path);
+
+        self.backend.add_file_note(self.collection_id, path, |result|{
+            proxy.call_mainthread(|_doc, nb|{
+                match result {
+                    Ok(model) => {
+                        let item = NoteItem::from_model(model);
+                        nb.notes.data_mut().insert(0, item);
+                        nb.notes.update();
+                        nb.notes.select_with_event(0, true);
+                    },
+                    Err(e) => {
+                        eprintln!("add_file_note failed: {:?}", e);
+                    }
+                }
+            });
+        });
+    }
+
     #[action(NavigationItem)]
     pub fn navigate_to_note(&mut self, _event: &mut ActionEvent, nav: &NavigationItem) {
         //println!("navigate to note");
     #[action(NavigationItem)]
     pub fn navigate_to_note(&mut self, _event: &mut ActionEvent, nav: &NavigationItem) {
         //println!("navigate to note");