use std::io::Error;
use std::path::{Path, PathBuf};
use std::pin::Pin;
-use sea_orm::{ActiveModelTrait, Database, DatabaseConnection, EntityTrait, QueryFilter, ColumnTrait, Set, QueryOrder, DbErr, ExprTrait, RelationTrait, QuerySelect};
+use sea_orm::{ActiveModelTrait, Database, DatabaseConnection, EntityTrait, QueryFilter, ColumnTrait, Set, QueryOrder, DbErr, ExprTrait};
use tokio::runtime::Runtime;
use std::sync::{Arc};
use std::thread::JoinHandle;
use rand::distr::Alphanumeric;
use rand::RngExt;
use tokio::fs;
-use tokio::fs::File;
-use tokio::io::AsyncWriteExt;
use tokio::sync::{broadcast, mpsc};
use tokio::sync::broadcast::error::SendError;
-use migration::{Expr, JoinType, Migrator, MigratorTrait};
+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, LocalStorageSetting, Node};
+use entity::collection::{create_notebook_hierarchy, CollectionType, Entity as Collection, Node};
use entity::note::{Column, Entity as Note};
use entity::notecontent::{Entity as NoteContent};
use migration::prelude::Utc;
use crate::lockmanager::LockManager;
+use crate::storage::{get_collection_storage_path, get_note_storage_path, write_file};
pub struct Backend {
rt: Arc<Runtime>,
let backend = self.backend.clone();
let cmd = Box::pin(async move {
// check if the note content is stored in the filesystem or database
- let storage_result = note::Entity::find_by_id(note_id)
- .join(JoinType::InnerJoin, note::Relation::Collection.def())
- .join(JoinType::InnerJoin, collection::Relation::Repository.def())
- .select_only()
- .column(collection::Column::Storage)
- .column(repository::Column::UseLocalStorage)
- .column(repository::Column::LocalPath)
- .column(note::Column::Nodename)
- .into_tuple::<(LocalStorageSetting, bool, Option<String>, String)>()
- .one(&backend.db).await;
- let storage = match storage_result {
- Ok(storage) => {
- if let Some(storage) = storage {
- storage
- } else {
- // Should not happen, a note always has a parent collection, which
- // also always is assigned to a repository
- return;
- }
- },
+ let storage_result = get_note_storage_path(&backend.db, note_id).await;
+ let note_path = match storage_result {
+ Ok(note_path) => note_path,
Err(e) => {
callback(Err(e));
return;
}
};
- if let Some(dir) = storage.2 &&
- (storage.0 == LocalStorageSetting::FileSystem || (storage.0 == LocalStorageSetting::Default && storage.1))
- {
+ if let Some(note_path) = note_path {
// local storage
- let path = Path::new(dir.as_str()).join(storage.3);
- let content = fs::read_to_string(path.as_path()).await;
+ let path = Path::new(note_path.as_str());
+ let content = fs::read_to_string(path).await;
match content {
Ok(content) => {
let model = notecontent::Model {
},
*/
- Err(e) => {
+ Err(_e) => {
let msg = format!("Cannot open file {}", path.display());
callback(Err(DbErr::Custom(msg)));
}
let bhandle = self.clone();
let cmd = Box::pin(async move {
let local_storage_path = {
- let result = collection::Entity::find_by_id(collection_id)
- .join(JoinType::InnerJoin, collection::Relation::Repository.def())
- .select_only()
- .column(collection::Column::Storage)
- .column(repository::Column::UseLocalStorage)
- .column(repository::Column::LocalPath)
- .into_tuple::<(LocalStorageSetting, bool, Option<String>)>()
- .one(&bhandle.backend.db).await;
- let storage = if let Ok(okres) = result && let Some(storage) = okres {
- storage
- } else {
- // should not happen
- callback(SaveNoteResult::Error(DbErr::Custom("Cannot get storage settings".to_string())));
- return;
- };
-
- if storage.0 == LocalStorageSetting::FileSystem || (storage.0 == LocalStorageSetting::Default && storage.1) {
- if storage.2.is_none() {
- println!("Warning: collection is configured to use local storage, but there is no local storage path configured in the repository");
+ let result = get_collection_storage_path(&bhandle.backend.db, collection_id).await;
+ match result {
+ Ok(path) => path,
+ Err(e) => {
+ callback(SaveNoteResult::Error(e));
+ return;
}
- storage.2
- } else {
- None
}
};
}
}
-async fn write_file(path: &Path, content: &str) -> std::io::Result<()> {
- let mut file = File::create(path).await?;
- file.write_all(content.as_bytes()).await?;
- Ok(())
-}
+
pub enum SaveNoteResult {
Ok((entity::note::Model, i32)),
--- /dev/null
+use std::io::ErrorKind;
+use std::path::Path;
+use sea_orm::{DatabaseConnection, DbErr, EntityTrait, QuerySelect, RelationTrait};
+use tokio::fs;
+use tokio::fs::File;
+use tokio::io::AsyncWriteExt;
+use entity::{collection, note, repository};
+use entity::collection::LocalStorageSetting;
+use migration::JoinType;
+
+/// Checks if a collection should use local storage
+fn uses_local_storage(settings: LocalStorageSetting, repo_use_local: bool) -> bool {
+ settings == LocalStorageSetting::FileSystem || (settings == LocalStorageSetting::Default && repo_use_local)
+}
+
+pub async fn get_note_storage_path(db: &DatabaseConnection, note_id: i32) -> Result<Option<String>, DbErr> {
+ // check if the note content is stored in the filesystem or database
+ let result = note::Entity::find_by_id(note_id)
+ .join(JoinType::InnerJoin, note::Relation::Collection.def())
+ .join(JoinType::InnerJoin, collection::Relation::Repository.def())
+ .select_only()
+ .column(collection::Column::Storage)
+ .column(repository::Column::UseLocalStorage)
+ .column(repository::Column::LocalPath)
+ .column(collection::Column::Parent)
+ .column(collection::Column::Name)
+ .column(note::Column::Nodename)
+ .into_tuple::<(LocalStorageSetting, bool, Option<String>, String, String, String)>()
+ .one(db).await?;
+
+ let Some(storage) = result else {
+ return Err(DbErr::RecordNotFound("Cannot find storage settings".to_string()));
+ };
+
+ if let Some(local_path) = storage.2 && uses_local_storage(storage.0, storage.1) {
+ let collection_path = format!("{}/{}/{}/{}", local_path, storage.3, storage.4, storage.5);
+ Ok(Some(collection_path))
+ } else {
+ Ok(None)
+ }
+}
+
+pub async fn get_collection_storage_path(db: &DatabaseConnection, collection_id: i32) -> Result<Option<String>, DbErr> {
+ let result = collection::Entity::find_by_id(collection_id)
+ .join(JoinType::InnerJoin, collection::Relation::Repository.def())
+ .select_only()
+ .column(collection::Column::Storage)
+ .column(repository::Column::UseLocalStorage)
+ .column(repository::Column::LocalPath)
+ .column(collection::Column::Parent)
+ .column(collection::Column::Name)
+ .into_tuple::<(LocalStorageSetting, bool, Option<String>, String, String)>()
+ .one(db).await?;
+
+ let Some(storage) = result else {
+ return Err(DbErr::RecordNotFound("Cannot find storage settings".to_string()));
+ };
+
+ if let Some(local_path) = storage.2 && uses_local_storage(storage.0, storage.1) {
+ let collection_path = format!("{}/{}/{}", local_path, storage.3, storage.4);
+ Ok(Some(collection_path))
+ } else {
+ Ok(None)
+ }
+}
+
+pub async fn write_file(path: &Path, content: &str) -> std::io::Result<()> {
+ let mut file = match File::create(path).await {
+ Ok(f) => f,
+ Err(err) if err.kind() == ErrorKind::NotFound => {
+ if let Some(parent) = path.parent() {
+ fs::create_dir_all(parent).await?;
+ }
+ File::create(path).await?
+ }
+ Err(err) => return Err(err),
+ };
+
+ file.write_all(content.as_bytes()).await?;
+ Ok(())
+}
\ No newline at end of file