diff --git a/src/storage.rs b/src/storage.rs index 525ce77..ede6729 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -17,8 +17,10 @@ use crate::{ protocol::Invite, }; +mod manifests; mod paths; +pub use manifests::ManifestRevision; pub use paths::AppPaths; const DEFAULT_HISTORY_REVISIONS: usize = 100; @@ -51,13 +53,6 @@ pub struct MemberInfo { pub is_active: bool, } -#[derive(Clone, Debug)] -pub struct ManifestRevision { - pub revision: i64, - pub saved_at: OffsetDateTime, - pub manifest: Manifest, -} - impl StateStore { pub fn open(paths: &AppPaths) -> anyhow::Result { let store = Self { @@ -363,93 +358,6 @@ impl StateStore { .collect() } - pub fn load_manifest(&self, folder_id: FolderId) -> anyhow::Result { - let manifest = self - .connection - .query_row( - "SELECT manifest FROM manifests WHERE folder_id = ?1", - params![folder_id.to_string()], - |row| row.get::<_, String>(0), - ) - .optional()?; - manifest - .map(|value| serde_json::from_str::(&value).map_err(anyhow::Error::from)) - .transpose()? - .ok_or_else(|| anyhow::anyhow!("missing manifest for folder {folder_id}")) - } - - pub fn save_manifest(&self, manifest: &Manifest) -> anyhow::Result<()> { - let serialized = serde_json::to_string(manifest)?; - let existing: Option = self - .connection - .query_row( - "SELECT manifest FROM manifests WHERE folder_id = ?1", - params![manifest.folder_id.to_string()], - |row| row.get(0), - ) - .optional()?; - if existing.as_deref() == Some(&serialized) { - return Ok(()); - } - let transaction = self.connection.unchecked_transaction()?; - transaction.execute("INSERT INTO manifests (folder_id, manifest) VALUES (?1, ?2) ON CONFLICT(folder_id) DO UPDATE SET manifest = excluded.manifest", params![manifest.folder_id.to_string(), serialized])?; - transaction.execute( - "INSERT INTO manifest_history (folder_id, saved_at, manifest) VALUES (?1, ?2, ?3)", - params![ - manifest.folder_id.to_string(), - OffsetDateTime::now_utc().format(&Rfc3339)?, - serde_json::to_string(manifest)? - ], - )?; - prune_manifest_history(&transaction, manifest.folder_id, self.history_limit)?; - transaction.commit()?; - Ok(()) - } - - pub fn manifest_history(&self, folder_id: FolderId) -> anyhow::Result> { - let mut statement = self.connection.prepare("SELECT revision, saved_at, manifest FROM manifest_history WHERE folder_id = ?1 ORDER BY revision DESC")?; - statement - .query_map(params![folder_id.to_string()], |row| { - Ok(( - row.get::<_, i64>(0)?, - row.get::<_, String>(1)?, - row.get::<_, String>(2)?, - )) - })? - .collect::, _>>()? - .into_iter() - .map(|(revision, saved_at, manifest)| { - Ok(ManifestRevision { - revision, - saved_at: OffsetDateTime::parse(&saved_at, &Rfc3339)?, - manifest: serde_json::from_str(&manifest)?, - }) - }) - .collect() - } - - pub fn manifest_history_count(&self, folder_id: FolderId) -> anyhow::Result { - let count: i64 = self.connection.query_row( - "SELECT COUNT(*) FROM manifest_history WHERE folder_id = ?1", - params![folder_id.to_string()], - |row| row.get(0), - )?; - u64::try_from(count).map_err(Into::into) - } - - pub fn manifest_revision( - &self, - folder_id: FolderId, - revision: i64, - ) -> anyhow::Result { - let serialized: String = self.connection.query_row( - "SELECT manifest FROM manifest_history WHERE folder_id = ?1 AND revision = ?2", - params![folder_id.to_string(), revision], - |row| row.get(0), - )?; - Ok(serde_json::from_str(&serialized)?) - } - pub fn save_roster(&self, roster: &FolderRoster) -> anyhow::Result<()> { roster.validate()?; self.connection.execute( diff --git a/src/storage/manifests.rs b/src/storage/manifests.rs new file mode 100644 index 0000000..1b062fc --- /dev/null +++ b/src/storage/manifests.rs @@ -0,0 +1,103 @@ +use rusqlite::{OptionalExtension, params}; +use time::{OffsetDateTime, format_description::well_known::Rfc3339}; + +use crate::{ + domain::{FolderId, Manifest}, + storage::{StateStore, prune_manifest_history}, +}; + +#[derive(Clone, Debug)] +pub struct ManifestRevision { + pub revision: i64, + pub saved_at: OffsetDateTime, + pub manifest: Manifest, +} + +impl StateStore { + pub fn load_manifest(&self, folder_id: FolderId) -> anyhow::Result { + let manifest = self + .connection + .query_row( + "SELECT manifest FROM manifests WHERE folder_id = ?1", + params![folder_id.to_string()], + |row| row.get::<_, String>(0), + ) + .optional()?; + manifest + .map(|value| serde_json::from_str::(&value).map_err(anyhow::Error::from)) + .transpose()? + .ok_or_else(|| anyhow::anyhow!("missing manifest for folder {folder_id}")) + } + + pub fn save_manifest(&self, manifest: &Manifest) -> anyhow::Result<()> { + let serialized = serde_json::to_string(manifest)?; + let existing: Option = self + .connection + .query_row( + "SELECT manifest FROM manifests WHERE folder_id = ?1", + params![manifest.folder_id.to_string()], + |row| row.get(0), + ) + .optional()?; + if existing.as_deref() == Some(&serialized) { + return Ok(()); + } + let transaction = self.connection.unchecked_transaction()?; + transaction.execute("INSERT INTO manifests (folder_id, manifest) VALUES (?1, ?2) ON CONFLICT(folder_id) DO UPDATE SET manifest = excluded.manifest", params![manifest.folder_id.to_string(), serialized])?; + transaction.execute( + "INSERT INTO manifest_history (folder_id, saved_at, manifest) VALUES (?1, ?2, ?3)", + params![ + manifest.folder_id.to_string(), + OffsetDateTime::now_utc().format(&Rfc3339)?, + serde_json::to_string(manifest)? + ], + )?; + prune_manifest_history(&transaction, manifest.folder_id, self.history_limit)?; + transaction.commit()?; + Ok(()) + } + + pub fn manifest_history(&self, folder_id: FolderId) -> anyhow::Result> { + let mut statement = self.connection.prepare("SELECT revision, saved_at, manifest FROM manifest_history WHERE folder_id = ?1 ORDER BY revision DESC")?; + statement + .query_map(params![folder_id.to_string()], |row| { + Ok(( + row.get::<_, i64>(0)?, + row.get::<_, String>(1)?, + row.get::<_, String>(2)?, + )) + })? + .collect::, _>>()? + .into_iter() + .map(|(revision, saved_at, manifest)| { + Ok(ManifestRevision { + revision, + saved_at: OffsetDateTime::parse(&saved_at, &Rfc3339)?, + manifest: serde_json::from_str(&manifest)?, + }) + }) + .collect() + } + + pub fn manifest_history_count(&self, folder_id: FolderId) -> anyhow::Result { + let count: i64 = self.connection.query_row( + "SELECT COUNT(*) FROM manifest_history WHERE folder_id = ?1", + params![folder_id.to_string()], + |row| row.get(0), + )?; + u64::try_from(count).map_err(Into::into) + } + + pub fn manifest_revision( + &self, + folder_id: FolderId, + revision: i64, + ) -> anyhow::Result { + let serialized: String = self.connection.query_row( + "SELECT manifest FROM manifest_history WHERE folder_id = ?1 AND revision = ?2", + params![folder_id.to_string(), revision], + |row| row.get(0), + )?; + Ok(serde_json::from_str(&serialized)?) + } +}