stateset_embedded/
maintenance.rs1use stateset_core::{CommerceError, Result};
33use stateset_db::Database;
34use std::path::Path;
35use std::sync::Arc;
36
37#[cfg(feature = "sqlite")]
38use stateset_db::SqliteDatabase;
39
40pub use stateset_db::portability::{
41 ConflictPolicy, ExportOptions, ExportReport, ImportOptions, ImportReport,
42};
43
44#[cfg(feature = "sqlite")]
45pub use stateset_db::maintenance::{BackupManifest, BackupReport, RestoreOptions, RestoreReport};
46
47pub struct Maintenance {
49 db: Arc<dyn Database>,
50 #[cfg(feature = "sqlite")]
51 sqlite: Option<Arc<SqliteDatabase>>,
52 #[cfg(feature = "sqlite")]
53 sqlite_path: Option<String>,
54}
55
56impl std::fmt::Debug for Maintenance {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.debug_struct("Maintenance").finish_non_exhaustive()
59 }
60}
61
62impl Maintenance {
63 #[cfg(feature = "sqlite")]
64 pub(crate) fn new(
65 db: Arc<dyn Database>,
66 sqlite: Option<Arc<SqliteDatabase>>,
67 sqlite_path: Option<String>,
68 ) -> Self {
69 Self { db, sqlite, sqlite_path }
70 }
71
72 #[cfg(not(feature = "sqlite"))]
73 pub(crate) fn new(db: Arc<dyn Database>) -> Self {
74 Self { db }
75 }
76
77 #[must_use]
83 pub const fn supports_backup(&self) -> bool {
84 #[cfg(feature = "sqlite")]
85 {
86 self.sqlite.is_some()
87 }
88 #[cfg(not(feature = "sqlite"))]
89 {
90 false
91 }
92 }
93
94 #[cfg(feature = "sqlite")]
95 fn sqlite_handle(&self) -> Result<&Arc<SqliteDatabase>> {
96 self.sqlite.as_ref().ok_or_else(|| {
97 CommerceError::ValidationError(
98 "backup and restore require a SQLite-backed Commerce instance".to_owned(),
99 )
100 })
101 }
102
103 #[cfg(feature = "sqlite")]
113 pub fn backup_to(&self, backup_path: impl AsRef<Path>) -> Result<BackupReport> {
114 let handle = self.sqlite_handle()?;
115 let conn = handle.conn()?;
116 let source = self.sqlite_path.clone().unwrap_or_else(|| ":memory:".to_owned());
117 stateset_db::maintenance::backup_to(&conn, source, backup_path).map_err(Into::into)
118 }
119
120 #[cfg(feature = "sqlite")]
133 pub fn restore_from(
134 &self,
135 backup_path: impl AsRef<Path>,
136 target_path: impl AsRef<Path>,
137 options: &RestoreOptions,
138 ) -> Result<RestoreReport> {
139 let target = target_path.as_ref();
140 if let Some(open_path) = &self.sqlite_path {
141 let same = Path::new(open_path)
142 .canonicalize()
143 .ok()
144 .zip(target.canonicalize().ok())
145 .is_some_and(|(a, b)| a == b);
146 if same {
147 return Err(CommerceError::ValidationError(format!(
148 "refusing to restore over '{open_path}', which this Commerce instance has \
149 open; restore to a new path and reopen, or close this instance first"
150 )));
151 }
152 }
153 stateset_db::maintenance::restore_from(backup_path, target, options).map_err(Into::into)
154 }
155
156 pub fn export_all<W: std::io::Write>(
162 &self,
163 writer: &mut W,
164 options: &ExportOptions,
165 ) -> Result<ExportReport> {
166 stateset_db::portability::export_all(self.db.as_ref(), writer, options)
167 }
168
169 pub fn export_to_file(&self, path: impl AsRef<Path>) -> Result<ExportReport> {
175 self.export_to_file_with(path, &ExportOptions::default())
176 }
177
178 pub fn export_to_file_with(
184 &self,
185 path: impl AsRef<Path>,
186 options: &ExportOptions,
187 ) -> Result<ExportReport> {
188 let path = path.as_ref();
189 if let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
190 std::fs::create_dir_all(parent).map_err(|e| {
191 CommerceError::DatabaseError(format!(
192 "cannot create export directory {}: {e}",
193 parent.display()
194 ))
195 })?;
196 }
197 let file = std::fs::File::create(path).map_err(|e| {
198 CommerceError::DatabaseError(format!("cannot create {}: {e}", path.display()))
199 })?;
200 let mut writer = std::io::BufWriter::new(file);
201 self.export_all(&mut writer, options)
202 }
203
204 pub fn import_all<R: std::io::Read>(
210 &self,
211 reader: &mut R,
212 options: &ImportOptions,
213 ) -> Result<ImportReport> {
214 stateset_db::portability::import_all(self.db.as_ref(), reader, options)
215 }
216
217 pub fn import_from_file(
223 &self,
224 path: impl AsRef<Path>,
225 options: &ImportOptions,
226 ) -> Result<ImportReport> {
227 let path = path.as_ref();
228 let file = std::fs::File::open(path).map_err(|e| {
229 CommerceError::DatabaseError(format!("cannot open {}: {e}", path.display()))
230 })?;
231 let mut reader = std::io::BufReader::new(file);
232 self.import_all(&mut reader, options)
233 }
234
235 #[must_use]
237 pub fn exportable_domains(&self) -> Vec<&'static str> {
238 stateset_db::portability::exportable_domains()
239 }
240
241 #[must_use]
243 pub fn importable_domains(&self) -> Vec<&'static str> {
244 stateset_db::portability::importable_domains()
245 }
246}