simplify database backup interface related

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk 2025-04-10 20:55:41 +00:00
parent 54fb48a983
commit 5b322561ce
3 changed files with 69 additions and 52 deletions

View file

@ -1,6 +1,11 @@
use std::{fmt::Write, path::PathBuf, sync::Arc}; use std::{fmt::Write, path::PathBuf, sync::Arc};
use conduwuit::{Err, Result, info, utils::time, warn}; use conduwuit::{
Err, Result, info,
utils::{stream::IterStream, time},
warn,
};
use futures::TryStreamExt;
use crate::admin_command; use crate::admin_command;
@ -81,33 +86,31 @@ pub(super) async fn clear_caches(&self) -> Result {
#[admin_command] #[admin_command]
pub(super) async fn list_backups(&self) -> Result { pub(super) async fn list_backups(&self) -> Result {
let result = self.services.db.db.backup_list()?; self.services
.db
if result.is_empty() { .db
return Err!("No backups found."); .backup_list()?
} .try_stream()
.try_for_each(|result| write!(self, "{result}"))
self.write_str(&result).await .await
} }
#[admin_command] #[admin_command]
pub(super) async fn backup_database(&self) -> Result { pub(super) async fn backup_database(&self) -> Result {
let db = Arc::clone(&self.services.db); let db = Arc::clone(&self.services.db);
let mut result = self let result = self
.services .services
.server .server
.runtime() .runtime()
.spawn_blocking(move || match db.db.backup() { .spawn_blocking(move || match db.db.backup() {
| Ok(()) => String::new(), | Ok(()) => "Done".to_owned(),
| Err(e) => e.to_string(), | Err(e) => format!("Failed: {e}"),
}) })
.await?; .await?;
if result.is_empty() { let count = self.services.db.db.backup_count()?;
result = self.services.db.db.backup_list()?; self.write_str(&format!("{result}. Currently have {count} backups."))
} .await
self.write_str(&result).await
} }
#[admin_command] #[admin_command]

View file

@ -1,24 +1,16 @@
use std::fmt::Write; use std::{ffi::OsString, path::PathBuf};
use conduwuit::{Result, error, implement, info, utils::time::rfc2822_from_seconds, warn}; use conduwuit::{Err, Result, error, implement, info, utils::time::rfc2822_from_seconds, warn};
use rocksdb::backup::{BackupEngine, BackupEngineOptions}; use rocksdb::backup::{BackupEngine, BackupEngineOptions};
use super::Engine; use super::Engine;
use crate::{or_else, util::map_err}; use crate::util::map_err;
#[implement(Engine)] #[implement(Engine)]
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn backup(&self) -> Result { pub fn backup(&self) -> Result {
let server = &self.ctx.server; let mut engine = self.backup_engine()?;
let config = &server.config; let config = &self.ctx.server.config;
let path = config.database_backup_path.as_ref();
if path.is_none() || path.is_some_and(|path| path.as_os_str().is_empty()) {
return Ok(());
}
let options =
BackupEngineOptions::new(path.expect("valid database backup path")).map_err(map_err)?;
let mut engine = BackupEngine::open(&options, &*self.ctx.env.lock()?).map_err(map_err)?;
if config.database_backups_to_keep > 0 { if config.database_backups_to_keep > 0 {
let flush = !self.is_read_only(); let flush = !self.is_read_only();
engine engine
@ -40,34 +32,62 @@ pub fn backup(&self) -> Result {
} }
} }
if config.database_backups_to_keep == 0 {
warn!("Configuration item `database_backups_to_keep` is set to 0.");
}
Ok(()) Ok(())
} }
#[implement(Engine)] #[implement(Engine)]
pub fn backup_list(&self) -> Result<String> { pub fn backup_list(&self) -> Result<impl Iterator<Item = String> + Send> {
let server = &self.ctx.server; let info = self.backup_engine()?.get_backup_info();
let config = &server.config;
let path = config.database_backup_path.as_ref(); if info.is_empty() {
if path.is_none() || path.is_some_and(|path| path.as_os_str().is_empty()) { return Err!("No backups found.");
return Ok("Configure database_backup_path to enable backups, or the path specified is \
not valid"
.to_owned());
} }
let mut res = String::new(); let list = info.into_iter().map(|info| {
let options = format!(
BackupEngineOptions::new(path.expect("valid database backup path")).or_else(or_else)?;
let engine = BackupEngine::open(&options, &*self.ctx.env.lock()?).or_else(or_else)?;
for info in engine.get_backup_info() {
writeln!(
res,
"#{} {}: {} bytes, {} files", "#{} {}: {} bytes, {} files",
info.backup_id, info.backup_id,
rfc2822_from_seconds(info.timestamp), rfc2822_from_seconds(info.timestamp),
info.size, info.size,
info.num_files, info.num_files,
)?; )
});
Ok(list)
} }
Ok(res) #[implement(Engine)]
pub fn backup_count(&self) -> Result<usize> {
let info = self.backup_engine()?.get_backup_info();
Ok(info.len())
}
#[implement(Engine)]
fn backup_engine(&self) -> Result<BackupEngine> {
let path = self.backup_path()?;
let options = BackupEngineOptions::new(path).map_err(map_err)?;
BackupEngine::open(&options, &*self.ctx.env.lock()?).map_err(map_err)
}
#[implement(Engine)]
fn backup_path(&self) -> Result<OsString> {
let path = self
.ctx
.server
.config
.database_backup_path
.clone()
.map(PathBuf::into_os_string)
.unwrap_or_default();
if path.is_empty() {
return Err!(Config("database_backup_path", "Configure path to enable backups"));
}
Ok(path)
} }

View file

@ -72,10 +72,4 @@ impl Data {
pub fn bump_database_version(&self, new_version: u64) { pub fn bump_database_version(&self, new_version: u64) {
self.global.raw_put(b"version", new_version); self.global.raw_put(b"version", new_version);
} }
#[inline]
pub fn backup(&self) -> Result { self.db.db.backup() }
#[inline]
pub fn backup_list(&self) -> Result<String> { self.db.db.backup_list() }
} }