Expose notes over IPC and drop them with the project
This commit is contained in:
@@ -8,6 +8,7 @@ pub mod help_commands;
|
|||||||
pub mod inspect_commands;
|
pub mod inspect_commands;
|
||||||
pub mod install_helper_commands;
|
pub mod install_helper_commands;
|
||||||
pub mod migration_commands;
|
pub mod migration_commands;
|
||||||
|
pub mod notes_commands;
|
||||||
pub mod project_commands;
|
pub mod project_commands;
|
||||||
pub mod settings_commands;
|
pub mod settings_commands;
|
||||||
pub mod settings_export_commands;
|
pub mod settings_export_commands;
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
use crate::models::Note;
|
||||||
|
use crate::storage::notes_store;
|
||||||
|
|
||||||
|
/// Every project's notes, oldest concept first: pinned notes, then most
|
||||||
|
/// recently edited.
|
||||||
|
///
|
||||||
|
/// Sorted here rather than in the webview so the dock and the tab — two views
|
||||||
|
/// of the same list — cannot drift into two different orders.
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn list_notes(project_id: String) -> Result<Vec<Note>, String> {
|
||||||
|
let mut notes = notes_store::load(&project_id)?;
|
||||||
|
notes.sort_by(|a, b| {
|
||||||
|
b.pinned
|
||||||
|
.cmp(&a.pinned)
|
||||||
|
.then_with(|| b.updated_at.cmp(&a.updated_at))
|
||||||
|
});
|
||||||
|
Ok(notes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert or replace one note.
|
||||||
|
///
|
||||||
|
/// There is deliberately no whole-list setter. A bulk write is exactly the
|
||||||
|
/// clobbering this store's per-project file exists to avoid, and every caller
|
||||||
|
/// here is editing one note.
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn save_note(project_id: String, note: Note) -> Result<Note, String> {
|
||||||
|
notes_store::upsert(&project_id, note)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn delete_note(project_id: String, note_id: String) -> Result<(), String> {
|
||||||
|
notes_store::delete(&project_id, ¬e_id)
|
||||||
|
}
|
||||||
@@ -722,6 +722,15 @@ pub async fn remove_project(
|
|||||||
// holding an entire snapshot image that nothing will ever reference again.
|
// holding an entire snapshot image that nothing will ever reference again.
|
||||||
crate::commands::migration_commands::purge_migration_artifacts(&project_id).await;
|
crate::commands::migration_commands::purge_migration_artifacts(&project_id).await;
|
||||||
|
|
||||||
|
// A project's notes are the one piece of its state that is purely the
|
||||||
|
// user's prose, so removal takes them with it rather than leaving an
|
||||||
|
// orphan file keyed by an id nothing will ever look up again. Logged and
|
||||||
|
// not propagated: an orphaned notes file is harmless, and a project that
|
||||||
|
// cannot be removed is not.
|
||||||
|
if let Err(e) = crate::storage::notes_store::clear(&project_id) {
|
||||||
|
log::warn!("Could not remove notes for project {}: {}", project_id, e);
|
||||||
|
}
|
||||||
|
|
||||||
// Stop and remove container if it exists. Everything named in `report`
|
// Stop and remove container if it exists. Everything named in `report`
|
||||||
// below is what will be unreachable the moment this function drops the
|
// below is what will be unreachable the moment this function drops the
|
||||||
// project record — see [`ProjectRemovalReport`] and
|
// project record — see [`ProjectRemovalReport`] and
|
||||||
|
|||||||
@@ -470,6 +470,10 @@ pub fn run() {
|
|||||||
commands::project_commands::stop_project_container,
|
commands::project_commands::stop_project_container,
|
||||||
commands::project_commands::rebuild_project_container,
|
commands::project_commands::rebuild_project_container,
|
||||||
commands::project_commands::reconcile_project_statuses,
|
commands::project_commands::reconcile_project_statuses,
|
||||||
|
// Notes
|
||||||
|
commands::notes_commands::list_notes,
|
||||||
|
commands::notes_commands::save_note,
|
||||||
|
commands::notes_commands::delete_note,
|
||||||
// Container base-image migration
|
// Container base-image migration
|
||||||
commands::migration_commands::get_container_staleness,
|
commands::migration_commands::get_container_staleness,
|
||||||
commands::migration_commands::migrate_project_to_base,
|
commands::migration_commands::migrate_project_to_base,
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ fn delete_in(dir: &Path, project_id: &str, note_id: &str) -> Result<(), String>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn clear_in(dir: &Path, project_id: &str) -> Result<(), String> {
|
fn clear_in(dir: &Path, project_id: &str) -> Result<(), String> {
|
||||||
|
let _guard = write_lock().lock().unwrap_or_else(|e| e.into_inner());
|
||||||
let path = notes_path_in(dir, project_id);
|
let path = notes_path_in(dir, project_id);
|
||||||
match fs::remove_file(&path) {
|
match fs::remove_file(&path) {
|
||||||
Ok(()) => Ok(()),
|
Ok(()) => Ok(()),
|
||||||
@@ -334,4 +335,14 @@ mod tests {
|
|||||||
clear_in(&dir, "p1").unwrap(); // idempotent
|
clear_in(&dir, "p1").unwrap(); // idempotent
|
||||||
std::fs::remove_dir_all(&dir).ok();
|
std::fs::remove_dir_all(&dir).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clearing_is_what_project_removal_calls_and_it_never_fails_on_absence() {
|
||||||
|
// `remove_project` must not be able to fail because a project simply
|
||||||
|
// never had any notes — an orphaned notes file is harmless, a project
|
||||||
|
// that cannot be removed is not.
|
||||||
|
let dir = temp_dir("removal");
|
||||||
|
assert!(clear_in(&dir, "never-had-notes").is_ok());
|
||||||
|
std::fs::remove_dir_all(&dir).ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user