diff options
author | evuez <julien@mulga.net> | 2024-04-03 22:43:16 +0200 |
---|---|---|
committer | evuez <julien@mulga.net> | 2024-04-03 22:43:16 +0200 |
commit | 43e1a12b5bce11b4a28a53acca243e35c2be6d3e (patch) | |
tree | 07d64823718bfee063ab7b3d5721ac1e950ae17c /src/server/index.rs | |
download | carton-43e1a12b5bce11b4a28a53acca243e35c2be6d3e.tar.gz |
Initial commit
Diffstat (limited to 'src/server/index.rs')
-rw-r--r-- | src/server/index.rs | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/src/server/index.rs b/src/server/index.rs new file mode 100644 index 0000000..042c4aa --- /dev/null +++ b/src/server/index.rs @@ -0,0 +1,260 @@ +use super::attrs::{self, Attr}; +use super::blobref::BlobRef; +use super::object::Chunk; +use super::object::{Attrs, Object}; +use crate::common::{json, sqlite}; +use log::error; +use rusqlite::{params, Connection, Row}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use time::OffsetDateTime; + +#[derive(Serialize, Deserialize)] +pub struct Config { + pub root: PathBuf, +} + +const DB_NAME: &str = "index.sqlite3"; +const SQL_INIT: &str = " + create table objects ( + anchor text primary key, + size integer not null, + chunks text not null, -- JSON array + created_at datetime not null, + updated_at datetime not null, + x_name text, + x_group text, + x_mime text, + x_created_at text, + x_updated_at text, + x_deleted_at text, + x_tags text, + x_note text + ); +"; + +#[derive(Debug)] +pub enum Error { + Internal(rusqlite::Error), +} + +type Result<T> = std::result::Result<T, Error>; + +pub struct Index { + db: Connection, +} + +impl Index { + pub fn new(config: &Config) -> Result<Self> { + let db = match Connection::open(config.root.join(DB_NAME)) { + Ok(conn) => conn, + Err(e) => { + error!("Couldn't open connection to the database: {e:?}"); + return Err(Error::Internal(e)); + } + }; + + Ok(Self { db }) + } + + pub fn put(&self, anchor: &BlobRef, chunks: &[Chunk]) -> Result<()> { + // Calculate the overall size + let size: usize = chunks.iter().map(|c| c.size).sum(); + + // Insert or update the object for `anchor` + let query = " + insert into objects (anchor, size, chunks, created_at, updated_at) + values (?, ?, ?, ?, ?) + on conflict do update set + size = excluded.size, + chunks = excluded.chunks, + updated_at = excluded.updated_at + "; + + let now = OffsetDateTime::now_utc(); + let mut stmt = self.db.prepare(query).map_err(Error::Internal)?; + stmt.execute(params![anchor, size, json::serialize(chunks), now, now]) + .map_err(Error::Internal)?; + + Ok(()) + } + + pub fn set_attrs(&mut self, anchor: &BlobRef, attrs: &[Attr]) -> Result<()> { + let now = OffsetDateTime::now_utc(); + let tx = self.db.transaction().map_err(Error::Internal)?; + + for attr in attrs { + let query = format!( + r#"update objects set "x_{}" = ? where anchor = ?"#, + attrs::key(attr) + ); + let mut stmt = tx.prepare(&query).map_err(Error::Internal)?; + + stmt.execute(params![attr, anchor]) + .map_err(Error::Internal)?; + } + + { + let mut stmt = tx + .prepare("update objects set updated_at = ? where anchor = ?") + .map_err(Error::Internal)?; + stmt.execute(params![now, anchor]) + .map_err(Error::Internal)?; + } + + tx.commit().map_err(Error::Internal)?; + + Ok(()) + } + + pub fn get(&self, anchor: &BlobRef) -> Result<Object> { + let query = "select + anchor, + size, + chunks, + created_at, + updated_at, + x_name, + x_group, + x_mime, + x_created_at, + x_updated_at, + x_deleted_at, + x_tags, + x_note + from objects + where anchor = ?"; + + sqlite::get(&self.db, query, (anchor,), |r| Object::try_from(r)).map_err(Error::Internal) + } + + pub fn list(&self, limit: usize, offset: usize, filter: Option<Attr>) -> Result<Vec<Object>> { + let where_clause = if let Some(ref filter) = filter { + format!(r#""x_{}" = ?"#, attrs::key(filter)) + } else { + "1=1".into() + }; + + let query = format!( + r#"select + anchor, + size, + chunks, + created_at, + updated_at, + x_name, + x_group, + x_mime, + x_created_at, + x_updated_at, + x_deleted_at, + x_tags, + x_note + from objects + where {where_clause} + order by rowid + limit {limit} offset {offset} + "# + ); + + if let Some(filter) = filter { + sqlite::list(&self.db, &query, (filter,), |r| Object::try_from(r)) + .map_err(Error::Internal) + } else { + sqlite::list(&self.db, &query, (), |r| Object::try_from(r)).map_err(Error::Internal) + } + } +} +// +// impl Object { +// fn new(blobref: BlobRef) -> Self { +// Object { +// blobref, +// group: None, +// is_group: false, +// name: None, +// kind: None, +// size: None, +// created_at: None, +// updated_at: None, +// deleted_at: None, +// tags: Vec::new(), +// } +// } +// +// pub fn get_name(&self) -> String { +// self.name.clone().unwrap_or(self.blobref.to_string()) +// } +// +// pub fn get_size(&self) -> u64 { +// self.size.unwrap_or(0) +// } +// +// pub fn is_group(&self) -> bool { +// self.is_group +// } +// +// fn apply_patch(&mut self, patch: Patch) { +// for change in patch.changes { +// self.apply_change(change); +// } +// } +// +// fn apply_change(&mut self, change: Change) { +// match change { +// Change::MarkAsGroup => self.is_group = true, +// Change::SetName { name } => self.name = Some(name), +// Change::SetGroup { group } => self.group = Some(group), +// Change::SetSize { size } => self.size = Some(size), +// Change::SetCreatedAt { created_at } => self.created_at = Some(created_at), +// Change::SetUpdatedAt { updated_at } => self.updated_at = Some(updated_at), +// Change::SetDeletedAt { deleted_at } => self.deleted_at = Some(deleted_at), +// Change::SetTags { tags } => self.tags = tags, +// }; +// } +// } +// +// fn row_to_object(row: &Row) -> rusqlite::Result<Object> { +// Ok(Object { +// blobref: row.get(0)?, +// group: row.get(1)?, +// is_group: row.get(2)?, +// name: row.get(3)?, +// size: row.get(4)?, +// kind: row.get(5)?, +// created_at: row.get(6)?, +// updated_at: row.get(7)?, +// deleted_at: row.get(8)?, +// tags: vec![], +// }) +// } + +impl TryFrom<&Row<'_>> for Object { + type Error = rusqlite::Error; + + fn try_from(row: &Row) -> std::result::Result<Object, Self::Error> { + let chunks_string: Vec<u8> = row.get(2)?; + let chunks = serde_json::from_slice::<Vec<Chunk>>(&chunks_string) + .map_err(|_| rusqlite::Error::InvalidQuery)?; + + let attrs = Attrs { + name: row.get(5)?, + group: row.get(6)?, + mime: row.get(7)?, + created_at: row.get(8)?, + updated_at: row.get(9)?, + deleted_at: row.get(10)?, + tags: Vec::new(), + note: row.get(12)?, + }; + + Ok(Object { + blobref: row.get(0)?, + size: row.get(1)?, + chunks, + attrs, + created_at: row.get(3)?, + updated_at: row.get(4)?, + }) + } +} |