core, desktop: Db::conn everywhere, Change::default, notes_where, and shared row mappings
Android / Build, or is the channel already serving this? (push) Successful in 4s
CI & Build / Build now, or wait for Android? (push) Successful in 4s
CI & Build / Web typecheck and unit tests (push) Successful in 16s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Python tests (push) Successful in 15s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / integration (push) Successful in 1m22s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 4m6s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 4m20s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m21s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Android / Kotlin + Rust (APK) (push) Canceled after 12m20s
Android / Build, or is the channel already serving this? (push) Successful in 4s
CI & Build / Build now, or wait for Android? (push) Successful in 4s
CI & Build / Web typecheck and unit tests (push) Successful in 16s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Python tests (push) Successful in 15s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / integration (push) Successful in 1m22s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 4m6s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 4m20s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m21s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Android / Kotlin + Rust (APK) (push) Canceled after 12m20s
From the audit (#5179, core and desktop half). - Every `db.0.lock().map_err(|e| e.to_string())?` (about 50 sites in core and the desktop) is now `db.conn()?`. The few sites that deliberately handle a poisoned lock differently, and the tests, keep their own spelling. - push::Change derives Default, so its four constructors name only the fields they set. - store: list_notes, reminders, titles and search share notes_where (ids from a query, each loaded through load_note). Labels share LABEL_SELECT/label_row, and saved filters share SAVED_FILTER_SELECT/saved_filter_row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,12 +26,9 @@ pub struct Db(pub Mutex<Connection>);
|
||||
impl Db {
|
||||
/// Lock the store, reporting a poisoned lock as a message rather than a panic.
|
||||
///
|
||||
/// Every consumer was writing `db.0.lock().map_err(|e| e.to_string())?` at each
|
||||
/// call site. Beyond the repetition, that spelling forces the caller to NAME
|
||||
/// `rusqlite::Connection` in any helper that returns the guard — which would make
|
||||
/// rusqlite a dependency of a layer whose whole point is not to know what the
|
||||
/// store is made of. Returning it from here means callers can bind the guard by
|
||||
/// inference and never name the type.
|
||||
/// The one way every caller takes the lock. Returning the guard from here lets a
|
||||
/// caller bind it by inference without naming `rusqlite::Connection`, so layers
|
||||
/// above the store need not depend on what it is made of.
|
||||
///
|
||||
/// A poisoned lock means some earlier call panicked while holding it. The store
|
||||
/// is not necessarily corrupt, but this connection can't be trusted blind, so it
|
||||
|
||||
+54
-74
@@ -8,7 +8,7 @@
|
||||
//! up with the values the frontend sends.
|
||||
|
||||
use chrono::{DateTime, Duration, SecondsFormat, Utc};
|
||||
use rusqlite::{params, params_from_iter, Connection, OptionalExtension};
|
||||
use rusqlite::{params, params_from_iter, Connection, OptionalExtension, Params};
|
||||
use serde_json::{json, Value};
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -439,9 +439,16 @@ pub fn list_notes(conn: &Connection, q: &ListQuery) -> rusqlite::Result<Vec<Note
|
||||
" ORDER BY pinned DESC, position DESC, updated_at DESC"
|
||||
});
|
||||
|
||||
notes_where(conn, &sql, params_from_iter(binds.iter()))
|
||||
}
|
||||
|
||||
/// The notes whose ids `sql` selects, in its order, each loaded in full. Every list
|
||||
/// the UI reads goes through `load_note`, so a list and the note it opens can never
|
||||
/// disagree about what the note is.
|
||||
fn notes_where<P: Params>(conn: &Connection, sql: &str, params: P) -> rusqlite::Result<Vec<Note>> {
|
||||
let ids: Vec<String> = {
|
||||
let mut stmt = conn.prepare(&sql)?;
|
||||
let rows = stmt.query_map(params_from_iter(binds.iter()), |r| r.get::<_, String>(0))?;
|
||||
let mut stmt = conn.prepare(sql)?;
|
||||
let rows = stmt.query_map(params, |r| r.get::<_, String>(0))?;
|
||||
rows.collect::<rusqlite::Result<Vec<String>>>()?
|
||||
};
|
||||
ids.iter().map(|id| load_note(conn, id)).collect()
|
||||
@@ -452,15 +459,14 @@ pub fn get_note(conn: &Connection, id: &str) -> rusqlite::Result<Note> {
|
||||
}
|
||||
|
||||
pub fn reminders(conn: &Connection) -> rusqlite::Result<Vec<Note>> {
|
||||
let ids: Vec<String> = {
|
||||
let mut stmt =
|
||||
// The owner's reminders: a note shared with us neither alerts us nor is ours
|
||||
// to clear, the same as on the server.
|
||||
conn.prepare("SELECT id FROM notes WHERE trashed = 0 AND remind_at IS NOT NULL AND permission = 'owner' ORDER BY remind_at ASC")?;
|
||||
let rows = stmt.query_map([], |r| r.get::<_, String>(0))?;
|
||||
rows.collect::<rusqlite::Result<Vec<String>>>()?
|
||||
};
|
||||
ids.iter().map(|id| load_note(conn, id)).collect()
|
||||
// The owner's reminders: a note shared with us neither alerts us nor is ours to
|
||||
// clear, the same as on the server.
|
||||
notes_where(
|
||||
conn,
|
||||
"SELECT id FROM notes WHERE trashed = 0 AND remind_at IS NOT NULL AND permission = 'owner'
|
||||
ORDER BY remind_at ASC",
|
||||
[],
|
||||
)
|
||||
}
|
||||
|
||||
/// Reminders due at or before `now_ms`, soonest first, trash excluded.
|
||||
@@ -501,35 +507,24 @@ pub fn due_reminders(conn: &Connection, now_ms: i64) -> rusqlite::Result<Vec<Due
|
||||
}
|
||||
|
||||
pub fn titles(conn: &Connection) -> rusqlite::Result<Vec<TitleEntry>> {
|
||||
// Names come from `load_note` so the palette and the card can never disagree
|
||||
// about what a note is called. The command palette reads this; correctness
|
||||
// beats one query per note at personal scale.
|
||||
let ids: Vec<String> = {
|
||||
let mut stmt = conn.prepare("SELECT id FROM notes WHERE trashed = 0")?;
|
||||
let rows = stmt.query_map([], |r| r.get(0))?;
|
||||
rows.collect::<rusqlite::Result<Vec<String>>>()?
|
||||
};
|
||||
ids.iter()
|
||||
.map(|id| {
|
||||
let note = load_note(conn, id)?;
|
||||
Ok(TitleEntry {
|
||||
// The command palette reads this; one query per note is fine at personal scale.
|
||||
let notes = notes_where(conn, "SELECT id FROM notes WHERE trashed = 0", [])?;
|
||||
Ok(notes
|
||||
.into_iter()
|
||||
.map(|note| TitleEntry {
|
||||
id: note.id,
|
||||
title: note.display_title,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn search(conn: &Connection, q: &str) -> rusqlite::Result<Vec<Note>> {
|
||||
let pat = format!("%{}%", escape_like(q));
|
||||
let ids: Vec<String> = {
|
||||
let mut stmt = conn.prepare(
|
||||
notes_where(
|
||||
conn,
|
||||
"SELECT id FROM notes WHERE trashed = 0 AND body LIKE ?1 ESCAPE '\\' ORDER BY updated_at DESC",
|
||||
)?;
|
||||
let rows = stmt.query_map([&pat], |r| r.get::<_, String>(0))?;
|
||||
rows.collect::<rusqlite::Result<Vec<String>>>()?
|
||||
};
|
||||
ids.iter().map(|id| load_note(conn, id)).collect()
|
||||
[&pat],
|
||||
)
|
||||
}
|
||||
|
||||
// ---- notes: write -----------------------------------------------------------
|
||||
@@ -1031,39 +1026,28 @@ pub fn restore_revision(conn: &Connection, id: &str, rev_id: &str) -> rusqlite::
|
||||
|
||||
// ---- labels -----------------------------------------------------------------
|
||||
|
||||
fn load_label(conn: &Connection, id: &str) -> rusqlite::Result<Label> {
|
||||
conn.query_row(
|
||||
"SELECT l.id, l.name, l.color,
|
||||
/// A label with how many live notes carry it; `label_row` reads it.
|
||||
const LABEL_SELECT: &str = "SELECT l.id, l.name, l.color,
|
||||
(SELECT COUNT(*) FROM note_labels nl JOIN notes n ON n.id = nl.note_id
|
||||
WHERE nl.label_id = l.id AND n.trashed = 0)
|
||||
FROM labels l WHERE l.id = ?1",
|
||||
[id],
|
||||
|r| {
|
||||
FROM labels l";
|
||||
|
||||
fn label_row(r: &rusqlite::Row<'_>) -> rusqlite::Result<Label> {
|
||||
Ok(Label {
|
||||
id: r.get(0)?,
|
||||
name: r.get(1)?,
|
||||
color: r.get(2)?,
|
||||
count: Some(r.get(3)?),
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn load_label(conn: &Connection, id: &str) -> rusqlite::Result<Label> {
|
||||
conn.query_row(&format!("{LABEL_SELECT} WHERE l.id = ?1"), [id], label_row)
|
||||
}
|
||||
|
||||
pub fn list_labels(conn: &Connection) -> rusqlite::Result<Vec<Label>> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT l.id, l.name, l.color,
|
||||
(SELECT COUNT(*) FROM note_labels nl JOIN notes n ON n.id = nl.note_id
|
||||
WHERE nl.label_id = l.id AND n.trashed = 0)
|
||||
FROM labels l ORDER BY l.name COLLATE NOCASE",
|
||||
)?;
|
||||
let rows = stmt.query_map([], |r| {
|
||||
Ok(Label {
|
||||
id: r.get(0)?,
|
||||
name: r.get(1)?,
|
||||
color: r.get(2)?,
|
||||
count: Some(r.get(3)?),
|
||||
})
|
||||
})?;
|
||||
let mut stmt = conn.prepare(&format!("{LABEL_SELECT} ORDER BY l.name COLLATE NOCASE"))?;
|
||||
let rows = stmt.query_map([], label_row)?;
|
||||
rows.collect()
|
||||
}
|
||||
|
||||
@@ -1170,19 +1154,25 @@ pub fn merge_labels(
|
||||
|
||||
// ---- saved filters ----------------------------------------------------------
|
||||
|
||||
pub fn list_saved_filters(conn: &Connection) -> rusqlite::Result<Vec<SavedFilter>> {
|
||||
let mut stmt =
|
||||
conn.prepare("SELECT id, name, params, position FROM saved_filters ORDER BY position ASC, name COLLATE NOCASE")?;
|
||||
let rows = stmt.query_map([], |r| {
|
||||
const SAVED_FILTER_SELECT: &str = "SELECT id, name, params, position FROM saved_filters";
|
||||
|
||||
/// A stored filter. Params that no longer parse read as no filter at all rather
|
||||
/// than failing the whole list.
|
||||
fn saved_filter_row(r: &rusqlite::Row<'_>) -> rusqlite::Result<SavedFilter> {
|
||||
let params_str: String = r.get(2)?;
|
||||
let params = serde_json::from_str(¶ms_str).unwrap_or_else(|_| serde_json::json!({}));
|
||||
Ok(SavedFilter {
|
||||
id: r.get(0)?,
|
||||
name: r.get(1)?,
|
||||
params,
|
||||
params: serde_json::from_str(¶ms_str).unwrap_or_else(|_| json!({})),
|
||||
position: r.get(3)?,
|
||||
})
|
||||
})?;
|
||||
}
|
||||
|
||||
pub fn list_saved_filters(conn: &Connection) -> rusqlite::Result<Vec<SavedFilter>> {
|
||||
let mut stmt = conn.prepare(&format!(
|
||||
"{SAVED_FILTER_SELECT} ORDER BY position ASC, name COLLATE NOCASE"
|
||||
))?;
|
||||
let rows = stmt.query_map([], saved_filter_row)?;
|
||||
rows.collect()
|
||||
}
|
||||
|
||||
@@ -1225,19 +1215,9 @@ pub fn rename_saved_filter(
|
||||
params![name, id],
|
||||
)?;
|
||||
conn.query_row(
|
||||
"SELECT id, name, params, position FROM saved_filters WHERE id = ?1",
|
||||
&format!("{SAVED_FILTER_SELECT} WHERE id = ?1"),
|
||||
[id],
|
||||
|r| {
|
||||
let params_str: String = r.get(2)?;
|
||||
let params =
|
||||
serde_json::from_str(¶ms_str).unwrap_or_else(|_| serde_json::json!({}));
|
||||
Ok(SavedFilter {
|
||||
id: r.get(0)?,
|
||||
name: r.get(1)?,
|
||||
params,
|
||||
position: r.get(3)?,
|
||||
})
|
||||
},
|
||||
saved_filter_row,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ pub async fn run_cycle(
|
||||
} else {
|
||||
state::SHARES
|
||||
};
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
if state::begin_shares(&conn, level).map_err(|e| e.to_string())? {
|
||||
log::info!("the server shares more of notes now; pulling everything once");
|
||||
}
|
||||
@@ -91,7 +91,7 @@ pub async fn run_cycle(
|
||||
let retention = server.and_then(|s| s.trash_retention_days);
|
||||
|
||||
let status = {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
if let Some(days) = retention {
|
||||
state::set_server_retention(&conn, days as i64).map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ async fn download_missing_blobs(
|
||||
token: &str,
|
||||
) -> Result<(usize, usize), String> {
|
||||
let wanted = {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
hashed_attachments(&conn).map_err(|e| e.to_string())?
|
||||
};
|
||||
|
||||
@@ -446,7 +446,7 @@ pub async fn run(
|
||||
|
||||
loop {
|
||||
let since = {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
state::read(&conn).map_err(|e| e.to_string())?.last_cursor
|
||||
};
|
||||
|
||||
@@ -463,7 +463,7 @@ pub async fn run(
|
||||
|
||||
let has_more = page.has_more;
|
||||
let applied = {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
apply_page(&conn, &page).map_err(|e| e.to_string())?
|
||||
};
|
||||
total.absorb(applied);
|
||||
|
||||
+9
-38
@@ -64,7 +64,7 @@ impl PushSummary {
|
||||
/// One entry in the `changes` array. Notes and labels share the envelope; serde skips
|
||||
/// the fields that don't apply, so the server sees exactly the shape docs/sync.md
|
||||
/// describes for each entity.
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Change {
|
||||
pub entity: &'static str,
|
||||
pub id: String,
|
||||
@@ -110,18 +110,7 @@ impl Change {
|
||||
id,
|
||||
op: "delete",
|
||||
edited_at,
|
||||
body: None,
|
||||
color: None,
|
||||
pinned: None,
|
||||
archived: None,
|
||||
trashed: None,
|
||||
remind_at: None,
|
||||
recurrence: None,
|
||||
position: None,
|
||||
label_ids: None,
|
||||
created_at: None,
|
||||
name: None,
|
||||
state_at: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,16 +226,7 @@ fn collect_labels(conn: &Connection, out: &mut Vec<Change>, limit: usize) -> rus
|
||||
name: Some(r.get(1)?),
|
||||
color: Some(r.get(2)?),
|
||||
edited_at: r.get(3)?,
|
||||
body: None,
|
||||
pinned: None,
|
||||
archived: None,
|
||||
trashed: None,
|
||||
remind_at: None,
|
||||
recurrence: None,
|
||||
position: None,
|
||||
label_ids: None,
|
||||
created_at: None,
|
||||
state_at: None,
|
||||
..Default::default()
|
||||
})
|
||||
})?;
|
||||
for row in rows {
|
||||
@@ -342,17 +322,11 @@ fn note_change(conn: &Connection, id: &str, shared_state: bool) -> rusqlite::Res
|
||||
op: "upsert",
|
||||
edited_at: row.updated_at,
|
||||
body: (row.permission == "edit").then_some(row.body),
|
||||
color: None,
|
||||
pinned: own.then_some(row.pinned),
|
||||
archived: own.then_some(row.archived),
|
||||
trashed: None,
|
||||
remind_at: None,
|
||||
recurrence: None,
|
||||
position: own.then_some(row.position),
|
||||
label_ids: None,
|
||||
created_at: None,
|
||||
name: None,
|
||||
state_at,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -374,8 +348,6 @@ fn note_change(conn: &Connection, id: &str, shared_state: bool) -> rusqlite::Res
|
||||
// server's last-write-wins comparison runs against.
|
||||
edited_at: row.updated_at,
|
||||
body: Some(row.body),
|
||||
// A note has no colour to send. See the field on `Change`.
|
||||
color: None,
|
||||
pinned: Some(row.pinned),
|
||||
archived: Some(row.archived),
|
||||
trashed: Some(row.trashed),
|
||||
@@ -384,8 +356,7 @@ fn note_change(conn: &Connection, id: &str, shared_state: bool) -> rusqlite::Res
|
||||
position: Some(row.position),
|
||||
label_ids: Some(label_ids),
|
||||
created_at: Some(row.created_at),
|
||||
name: None,
|
||||
state_at: None,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -610,7 +581,7 @@ async fn upload_pending(
|
||||
token: &str,
|
||||
) -> Result<PushSummary, String> {
|
||||
let wanted = {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
pending_uploads(&conn).map_err(|e| e.to_string())?
|
||||
};
|
||||
let mut summary = PushSummary::default();
|
||||
@@ -636,7 +607,7 @@ async fn upload_pending(
|
||||
)),
|
||||
};
|
||||
{
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
settle_upload(&conn, &upload.id, &result).map_err(|e| e.to_string())?;
|
||||
}
|
||||
match result {
|
||||
@@ -669,7 +640,7 @@ pub async fn run(
|
||||
|
||||
loop {
|
||||
let batch = {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
collect(&conn, BATCH, accepts).map_err(|e| e.to_string())?
|
||||
};
|
||||
if batch.is_empty() {
|
||||
@@ -680,7 +651,7 @@ pub async fn run(
|
||||
let results = parse_results(&raw)?;
|
||||
|
||||
let applied = {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
apply_results(&conn, &batch, &results).map_err(|e| e.to_string())?
|
||||
};
|
||||
// Everything rejected clears nothing, so the same batch would be collected
|
||||
|
||||
@@ -18,7 +18,7 @@ pub const NEEDS_SERVER: &str =
|
||||
"Sharing is between people on a server. Link this device to one in Sync to share notes.";
|
||||
|
||||
fn link(db: &Db) -> Result<(String, String), String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
let link = state::read(&conn).map_err(|e| e.to_string())?;
|
||||
match (link.server_url, link.device_token) {
|
||||
(Some(url), Some(token)) => Ok((url, token)),
|
||||
@@ -29,7 +29,7 @@ fn link(db: &Db) -> Result<(String, String), String> {
|
||||
/// Set the local note's `shared` flag from the server's list of its shares. Not a
|
||||
/// local edit, so it leaves `dirty` and `updated_at` alone.
|
||||
fn mark_shared(db: &Db, note_id: &str, shares: &[NoteShare]) -> Result<(), String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
conn.execute(
|
||||
"UPDATE notes SET shared = ?2 WHERE id = ?1 AND permission = 'owner'",
|
||||
params![note_id, !shares.is_empty()],
|
||||
|
||||
@@ -175,7 +175,7 @@ fn backoff(failures: u32) -> Duration {
|
||||
|
||||
/// The server URL and token, or None when this device isn't linked.
|
||||
fn credentials(db: &Db) -> Result<Option<(String, String)>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
let current = state::read(&conn).map_err(|e| e.to_string())?;
|
||||
Ok(current.server_url.zip(current.device_token))
|
||||
}
|
||||
@@ -183,7 +183,7 @@ fn credentials(db: &Db) -> Result<Option<(String, String)>, String> {
|
||||
/// What is waiting to be sent, as [`push::pending_fingerprint`] sees it. A store
|
||||
/// that can't be read reads as nothing pending; the periodic cycle still runs.
|
||||
fn pending(db: &Db) -> Option<String> {
|
||||
let conn = db.0.lock().ok()?;
|
||||
let conn = db.conn().ok()?;
|
||||
push::pending_fingerprint(&conn).ok().flatten()
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ pub struct CaptureShortcut {
|
||||
}
|
||||
|
||||
fn stored(db: &Db) -> Result<String, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
Ok(store::pref(&conn, SHORTCUT_PREF)
|
||||
.map_err(|e| e.to_string())?
|
||||
.unwrap_or_default())
|
||||
@@ -187,7 +187,7 @@ pub fn capture_shortcut_set(
|
||||
) -> Result<CaptureShortcut, String> {
|
||||
let wanted = shortcut.trim().to_string();
|
||||
register(&app, &wanted)?;
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::set_pref(&conn, SHORTCUT_PREF, &wanted).map_err(|e| e.to_string())?;
|
||||
log::info!(
|
||||
"quick capture shortcut {}",
|
||||
|
||||
@@ -54,37 +54,37 @@ pub fn auth_me() -> User {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_list(query: ListQuery, db: State<'_, Db>) -> Result<Vec<Note>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::list_notes(&conn, &query).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_get(id: String, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::get_note(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_create(input: NoteCreateInput, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::create_note(&conn, &input).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_update(id: String, changes: Value, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::update_note(&conn, &id, &changes).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_complete_reminder(id: String, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::complete_reminder(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_snooze_reminder(id: String, minutes: i64, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::snooze_reminder(&conn, &id, minutes).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ pub fn notes_set_labels(
|
||||
label_ids: Vec<String>,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::set_labels(&conn, &id, &label_ids).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ pub fn notes_update_item(
|
||||
changes: Value,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::update_item(&conn, &id, &item_id, &changes).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ pub async fn notes_add_attachment(
|
||||
let note_id = header("x-note-id");
|
||||
let filename = blobs::urldecode(header("x-filename"));
|
||||
let mime = header("x-mime");
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::add_attachment(&conn, &blobs, note_id, &filename, mime, bytes)
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ pub async fn notes_export(
|
||||
) -> Result<String, String> {
|
||||
use tauri::Manager;
|
||||
let bytes = {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
portable::export_zip(&conn, &blobs)?
|
||||
};
|
||||
let dir = app
|
||||
@@ -188,7 +188,7 @@ pub async fn notes_import(
|
||||
let tauri::ipc::InvokeBody::Raw(bytes) = request.body() else {
|
||||
return Err("The file's contents didn't arrive.".to_string());
|
||||
};
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
portable::import_zip(&conn, &blobs, bytes)
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ pub fn notes_delete_attachment(
|
||||
att_id: String,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::delete_attachment(&conn, &id, &att_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -208,37 +208,37 @@ pub fn notes_delete_preview(
|
||||
preview_id: String,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::delete_preview(&conn, &id, &preview_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_reorder(ordered_ids: Vec<String>, db: State<'_, Db>) -> Result<(), String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::reorder(&conn, &ordered_ids).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_trash(id: String, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::trash(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_restore(id: String, db: State<'_, Db>) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::restore(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_delete_forever(id: String, db: State<'_, Db>) -> Result<(), String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::delete_forever(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_revisions(id: String, db: State<'_, Db>) -> Result<Vec<NoteRevision>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::revisions(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -248,61 +248,61 @@ pub fn notes_restore_revision(
|
||||
rev_id: String,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<Note, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::restore_revision(&conn, &id, &rev_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_reminders(db: State<'_, Db>) -> Result<Vec<Note>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::reminders(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn notes_titles(db: State<'_, Db>) -> Result<Vec<TitleEntry>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::titles(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn labels_list(db: State<'_, Db>) -> Result<Vec<Label>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::list_labels(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn labels_create(name: String, db: State<'_, Db>) -> Result<Label, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::create_label(&conn, &name).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn labels_rename(id: String, name: String, db: State<'_, Db>) -> Result<Label, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::rename_label(&conn, &id, &name).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn labels_set_color(id: String, color: String, db: State<'_, Db>) -> Result<Label, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::set_label_color(&conn, &id, &color).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn labels_remove(id: String, db: State<'_, Db>) -> Result<(), String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::remove_label(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn labels_merge(source_id: String, into: String, db: State<'_, Db>) -> Result<Label, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::merge_labels(&conn, &source_id, &into).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn saved_filters_list(db: State<'_, Db>) -> Result<Vec<SavedFilter>, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::list_saved_filters(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -312,13 +312,13 @@ pub fn saved_filters_create(
|
||||
params: Value,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<SavedFilter, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::create_saved_filter(&conn, &name, ¶ms).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn saved_filters_remove(id: String, db: State<'_, Db>) -> Result<(), String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::remove_saved_filter(&conn, &id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -328,6 +328,6 @@ pub fn saved_filters_rename(
|
||||
name: String,
|
||||
db: State<'_, Db>,
|
||||
) -> Result<SavedFilter, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::rename_saved_filter(&conn, &id, &name).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ pub async fn sync_link(input: LinkInput, db: State<'_, Db>) -> Result<LinkResult
|
||||
// isn't Send so it cannot be held across an await, and holding the store
|
||||
// locked for a network round-trip would freeze every note operation in the UI.
|
||||
let status = {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
state::set_link(&conn, &base_url, &token).map_err(|e| e.to_string())?;
|
||||
// Adopt the server's trash-retention window immediately, so the Trash view
|
||||
// stops counting down against this device's offline default the moment it's
|
||||
@@ -135,7 +135,7 @@ pub async fn sync_unlink(db: State<'_, Db>) -> Result<UnlinkResult, String> {
|
||||
// holding the store across a round-trip would freeze every note operation in
|
||||
// the UI.
|
||||
let link = {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
let current = state::read(&conn).map_err(|e| e.to_string())?;
|
||||
current.server_url.zip(current.device_token)
|
||||
};
|
||||
@@ -144,7 +144,7 @@ pub async fn sync_unlink(db: State<'_, Db>) -> Result<UnlinkResult, String> {
|
||||
None => client::RevokeOutcome::Skipped,
|
||||
};
|
||||
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
state::clear_link(&conn).map_err(|e| e.to_string())?;
|
||||
log::info!("unlinked from server (server-side token: {revoked:?})");
|
||||
Ok(UnlinkResult {
|
||||
@@ -155,7 +155,7 @@ pub async fn sync_unlink(db: State<'_, Db>) -> Result<UnlinkResult, String> {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn sync_status(db: State<'_, Db>) -> Result<state::Status, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
state::status(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ pub fn sync_last(autosync: State<'_, AutoSync>) -> Option<LastCycle> {
|
||||
/// changes" state without running a sync to find out.
|
||||
#[tauri::command]
|
||||
pub fn sync_has_pending(db: State<'_, Db>) -> Result<bool, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
push::has_pending(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ fn worker(app: AppHandle) {
|
||||
|
||||
fn due(app: &AppHandle, now: i64) -> Result<Vec<DueReminder>, String> {
|
||||
let db = app.state::<Db>();
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::due_reminders(&conn, now).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ pub fn adopt_installer_channel(db: &Db, data_dir: &Path) {
|
||||
/// Apply `channel` unless we already applied this same marker value. Returns whether
|
||||
/// anything changed.
|
||||
fn adopt(db: &Db, channel: Channel) -> Result<bool, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
let adopted = store::pref(&conn, CHANNEL_SEED_PREF).map_err(|e| e.to_string())?;
|
||||
// Already acted on this marker — whatever the pref says now is the user's own
|
||||
// choice, and re-applying would quietly undo it.
|
||||
@@ -193,7 +193,7 @@ fn adopt(db: &Db, channel: Channel) -> Result<bool, String> {
|
||||
}
|
||||
|
||||
fn read_channel(db: &State<'_, Db>) -> Result<Channel, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
let raw = store::pref(&conn, CHANNEL_PREF).map_err(|e| e.to_string())?;
|
||||
Ok(raw
|
||||
.as_deref()
|
||||
@@ -208,7 +208,7 @@ pub fn update_channel_get(db: State<'_, Db>) -> Result<Channel, String> {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn update_channel_set(channel: Channel, db: State<'_, Db>) -> Result<Channel, String> {
|
||||
let conn = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let conn = db.conn()?;
|
||||
store::set_pref(&conn, CHANNEL_PREF, channel.as_str()).map_err(|e| e.to_string())?;
|
||||
log::info!("update channel set to {}", channel.as_str());
|
||||
Ok(channel)
|
||||
|
||||
Reference in New Issue
Block a user