* 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::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 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;
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(¬e_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);
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 {
}
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
#[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 proxy = doc.doc_proxy();
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|{
-
+ 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");