mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2025-01-08 19:06:38 +03:00
init bookmarks package
This commit is contained in:
parent
0e89f15a62
commit
612261cc0a
270
src-tauri/src/app/bookmarks/database.rs
Normal file
270
src-tauri/src/app/bookmarks/database.rs
Normal file
@ -0,0 +1,270 @@
|
||||
use std::ops;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::database;
|
||||
|
||||
use super::Bookmark;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Database {
|
||||
database: database::Database,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn new(database: database::Database) -> Self {
|
||||
Self { database }
|
||||
}
|
||||
|
||||
pub fn insert(&self, bookmark: &Bookmark) -> Result<()> {
|
||||
self.database.transaction(|tx| -> Result<()> {
|
||||
let mut stmt = insert_stmt(tx).context("Failed to prepare insert statement")?;
|
||||
let timestamp_ms = bookmark.timestamp_ms.to_string();
|
||||
stmt.execute(rusqlite::named_params! {
|
||||
":id": &bookmark.id,
|
||||
":project_id": &bookmark.project_id,
|
||||
":timestamp_ms": ×tamp_ms,
|
||||
":note": &bookmark.note,
|
||||
})
|
||||
.context("Failed to execute insert statement")?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn list_by_project_id_range(
|
||||
&self,
|
||||
project_id: &str,
|
||||
range: ops::Range<u128>,
|
||||
) -> Result<Vec<Bookmark>> {
|
||||
self.database.transaction(|tx| {
|
||||
let mut stmt = list_by_project_id_range_stmt(tx)
|
||||
.context("Failed to prepare list_by_project_id statement")?;
|
||||
let mut rows = stmt
|
||||
.query(rusqlite::named_params! {
|
||||
":project_id": project_id,
|
||||
":start": range.start.to_string(),
|
||||
":end": range.end.to_string(),
|
||||
})
|
||||
.context("Failed to execute list_by_project_id statement")?;
|
||||
let mut bookmarks = Vec::new();
|
||||
while let Some(row) = rows.next()? {
|
||||
bookmarks.push(parse_row(row)?);
|
||||
}
|
||||
Ok(bookmarks)
|
||||
})
|
||||
}
|
||||
|
||||
fn list_by_project_id_all(&self, project_id: &str) -> Result<Vec<Bookmark>> {
|
||||
self.database.transaction(|tx| {
|
||||
let mut stmt = list_by_project_id_stmt(tx)
|
||||
.context("Failed to prepare list_by_project_id statement")?;
|
||||
let mut rows = stmt
|
||||
.query(rusqlite::named_params! { ":project_id": project_id })
|
||||
.context("Failed to execute list_by_project_id statement")?;
|
||||
let mut bookmarks = Vec::new();
|
||||
while let Some(row) = rows.next()? {
|
||||
bookmarks.push(parse_row(row)?);
|
||||
}
|
||||
Ok(bookmarks)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list_by_project_id(
|
||||
&self,
|
||||
project_id: &str,
|
||||
range: Option<ops::Range<u128>>,
|
||||
) -> Result<Vec<Bookmark>> {
|
||||
if range.is_some() {
|
||||
self.list_by_project_id_range(project_id, range.unwrap())
|
||||
} else {
|
||||
self.list_by_project_id_all(project_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_by_row_id(&self, rowid: &i64) -> Result<Option<Bookmark>> {
|
||||
self.database.transaction(|tx| {
|
||||
let mut stmt =
|
||||
get_by_rowid_stmt(tx).context("Failed to prepare get_by_rowid statement")?;
|
||||
let mut rows = stmt
|
||||
.query(rusqlite::named_params! { ":rowid": rowid })
|
||||
.context("Failed to execute get_by_rowid statement")?;
|
||||
if let Some(row) = rows.next()? {
|
||||
Ok(Some(parse_row(row)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn on<F>(&self, callback: F) -> Result<()>
|
||||
where
|
||||
F: Fn(&Bookmark) + Send + 'static,
|
||||
{
|
||||
let boxed_self = Box::new(self.clone());
|
||||
self.database.on_update(
|
||||
move |action, _database_name, table_name, rowid| match action {
|
||||
rusqlite::hooks::Action::SQLITE_INSERT | rusqlite::hooks::Action::SQLITE_UPDATE => {
|
||||
match table_name {
|
||||
"bookmarks" => match boxed_self.get_by_row_id(&rowid) {
|
||||
Ok(Some(bookmark)) => callback(&bookmark),
|
||||
Ok(None) => {}
|
||||
Err(e) => log::error!("Failed to get bookmark: {}", e),
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_stmt<'conn>(
|
||||
tx: &'conn rusqlite::Transaction,
|
||||
) -> Result<rusqlite::CachedStatement<'conn>> {
|
||||
Ok(tx.prepare_cached(
|
||||
"
|
||||
INSERT INTO `bookmarks` (`id`, `project_id`, `timestamp_ms`, `note`)
|
||||
VALUES (:id, :project_id, :timestamp_ms, :note)
|
||||
",
|
||||
)?)
|
||||
}
|
||||
|
||||
fn list_by_project_id_range_stmt<'conn>(
|
||||
tx: &'conn rusqlite::Transaction,
|
||||
) -> Result<rusqlite::CachedStatement<'conn>> {
|
||||
Ok(tx.prepare_cached(
|
||||
"
|
||||
SELECT `id`, `project_id`, `timestamp_ms`, `note`
|
||||
FROM `bookmarks`
|
||||
WHERE `project_id` = :project_id
|
||||
AND `timestamp_ms` >= :start
|
||||
AND `timestamp_ms` < :end
|
||||
ORDER BY `timestamp_ms` DESC
|
||||
",
|
||||
)?)
|
||||
}
|
||||
|
||||
fn list_by_project_id_stmt<'conn>(
|
||||
tx: &'conn rusqlite::Transaction,
|
||||
) -> Result<rusqlite::CachedStatement<'conn>> {
|
||||
Ok(tx.prepare_cached(
|
||||
"
|
||||
SELECT `id`, `project_id`, `timestamp_ms`, `note`
|
||||
FROM `bookmarks`
|
||||
WHERE `project_id` = :project_id
|
||||
ORDER BY `timestamp_ms` DESC
|
||||
",
|
||||
)?)
|
||||
}
|
||||
|
||||
fn get_by_rowid_stmt<'conn>(
|
||||
tx: &'conn rusqlite::Transaction,
|
||||
) -> Result<rusqlite::CachedStatement<'conn>> {
|
||||
Ok(tx.prepare_cached(
|
||||
"
|
||||
SELECT `id`, `project_id`, `timestamp_ms`, `note`
|
||||
FROM `bookmarks`
|
||||
WHERE `rowid` = :rowid
|
||||
",
|
||||
)?)
|
||||
}
|
||||
|
||||
fn parse_row(row: &rusqlite::Row) -> Result<Bookmark> {
|
||||
Ok(Bookmark {
|
||||
id: row.get(0).context("Failed to get id")?,
|
||||
project_id: row.get(1).context("Failed to get project_id")?,
|
||||
timestamp_ms: row
|
||||
.get::<usize, String>(2)
|
||||
.context("Failed to get timestamp_ms")?
|
||||
.parse()
|
||||
.context("Failed to parse timestamp_ms")?,
|
||||
note: row.get(3).context("Failed to get note")?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_by_rowid() -> Result<()> {
|
||||
let db = database::Database::memory()?;
|
||||
let database = Database::new(db);
|
||||
|
||||
let bookmark = Bookmark {
|
||||
id: "id".to_string(),
|
||||
project_id: "project_id".to_string(),
|
||||
timestamp_ms: 123,
|
||||
note: "note".to_string(),
|
||||
};
|
||||
|
||||
database.insert(&bookmark)?;
|
||||
|
||||
let rowid = database
|
||||
.database
|
||||
.transaction(|tx| {
|
||||
Ok(tx
|
||||
.prepare_cached("SELECT rowid FROM bookmarks LIMIT 1")?
|
||||
.query_row([], |row| row.get(0))?)
|
||||
})
|
||||
.context("Failed to get rowid")?;
|
||||
|
||||
let result = database.get_by_row_id(&rowid)?;
|
||||
|
||||
assert_eq!(result, Some(bookmark));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_by_project_id_all() -> Result<()> {
|
||||
let db = database::Database::memory()?;
|
||||
let database = Database::new(db);
|
||||
|
||||
let bookmark = Bookmark {
|
||||
id: "id".to_string(),
|
||||
project_id: "project_id".to_string(),
|
||||
timestamp_ms: 123,
|
||||
note: "note".to_string(),
|
||||
};
|
||||
|
||||
database.insert(&bookmark)?;
|
||||
|
||||
let result = database.list_by_project_id_all(&bookmark.project_id)?;
|
||||
|
||||
assert_eq!(result, vec![bookmark]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_by_project_id_range() -> Result<()> {
|
||||
let db = database::Database::memory()?;
|
||||
let database = Database::new(db);
|
||||
|
||||
let bookmark_one = Bookmark {
|
||||
id: "id".to_string(),
|
||||
project_id: "project_id".to_string(),
|
||||
timestamp_ms: 123,
|
||||
note: "note".to_string(),
|
||||
};
|
||||
database.insert(&bookmark_one)?;
|
||||
|
||||
let bookmark_two = Bookmark {
|
||||
id: "id2".to_string(),
|
||||
project_id: "project_id".to_string(),
|
||||
timestamp_ms: 456,
|
||||
note: "note".to_string(),
|
||||
};
|
||||
database.insert(&bookmark_two)?;
|
||||
|
||||
let result = database.list_by_project_id_range(
|
||||
&bookmark_one.project_id,
|
||||
ops::Range { start: 0, end: 250 },
|
||||
)?;
|
||||
assert_eq!(result, vec![bookmark_one]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
14
src-tauri/src/app/bookmarks/mod.rs
Normal file
14
src-tauri/src/app/bookmarks/mod.rs
Normal file
@ -0,0 +1,14 @@
|
||||
mod database;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Bookmark {
|
||||
pub id: String,
|
||||
pub project_id: String,
|
||||
pub timestamp_ms: u128,
|
||||
pub note: String,
|
||||
}
|
||||
|
||||
pub use database::Database;
|
@ -1,4 +1,5 @@
|
||||
mod app;
|
||||
pub mod bookmarks;
|
||||
pub mod deltas;
|
||||
pub mod files;
|
||||
pub mod gb_repository;
|
||||
|
8
src-tauri/src/database/migrations/V3__bookmarks.sql
Normal file
8
src-tauri/src/database/migrations/V3__bookmarks.sql
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE TABLE `bookmarks` (
|
||||
`id` text NOT NULL PRIMARY KEY,
|
||||
`project_id` text NOT NULL,
|
||||
`timestamp_ms` text NOT NULL,
|
||||
`note` text NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX bookmarks_project_id_idx ON `bookmarks` (`project_id`);
|
Loading…
Reference in New Issue
Block a user