use rsfbclient_core::{ibase, FbError};
#[cfg(feature = "linking")]
use rsfbclient_native::DynLink;
#[cfg(feature = "dynamic_loading")]
use rsfbclient_native::DynLoad;
use rsfbclient_native::NativeServiceManager;
#[derive(Clone, Copy, Default)]
pub struct SvcBackupOptions {
pub metadata_only: bool,
pub ignore_checksums: bool,
pub ignore_limbo: bool,
pub no_garbage_collect: bool,
}
#[derive(Clone, Copy, Default)]
pub struct SvcRestoreOptions {
pub replace: bool,
pub page_size: Option<u32>,
}
#[derive(Clone)]
pub struct ServiceManagerBuilder {
host: String,
port: u16,
user: String,
pass: String,
lib_path: Option<String>,
}
impl Default for ServiceManagerBuilder {
fn default() -> Self {
Self {
host: "localhost".to_string(),
port: 3050,
user: "SYSDBA".to_string(),
pass: "masterkey".to_string(),
lib_path: None,
}
}
}
impl ServiceManagerBuilder {
pub fn host<S: Into<String>>(&mut self, host: S) -> &mut Self {
self.host = host.into();
self
}
pub fn port(&mut self, port: u16) -> &mut Self {
self.port = port;
self
}
pub fn user<S: Into<String>>(&mut self, user: S) -> &mut Self {
self.user = user.into();
self
}
pub fn pass<S: Into<String>>(&mut self, pass: S) -> &mut Self {
self.pass = pass.into();
self
}
pub fn with_dyn_load<S: Into<String>>(&mut self, lib_path: S) -> &mut Self {
self.lib_path = Some(lib_path.into());
self
}
pub fn attach(&self) -> Result<ServiceManager, FbError> {
let spb = build_attach_spb(&self.user, &self.pass);
match &self.lib_path {
None => {
#[cfg(feature = "linking")]
{
let inner = NativeServiceManager::<DynLink>::attach_dyn_link(
&self.host, self.port, &spb,
)?;
Ok(ServiceManager {
inner: SvcContainer::Linking(inner),
})
}
#[cfg(not(feature = "linking"))]
Err(FbError::from(
"The 'linking' feature is disabled; use with_dyn_load() to load a client library by path",
))
}
Some(lib_path) => {
#[cfg(feature = "dynamic_loading")]
{
let inner = NativeServiceManager::<DynLoad>::attach_dyn_load(
lib_path, &self.host, self.port, &spb,
)?;
Ok(ServiceManager {
inner: SvcContainer::DynLoad(inner),
})
}
#[cfg(not(feature = "dynamic_loading"))]
{
let _ = lib_path;
Err(FbError::from(
"with_dyn_load() requires the 'dynamic_loading' feature",
))
}
}
}
}
}
enum SvcContainer {
#[cfg(feature = "linking")]
Linking(NativeServiceManager<DynLink>),
#[cfg(feature = "dynamic_loading")]
DynLoad(NativeServiceManager<DynLoad>),
}
pub struct ServiceManager {
inner: SvcContainer,
}
impl ServiceManager {
pub fn builder() -> ServiceManagerBuilder {
ServiceManagerBuilder::default()
}
pub fn server_version(&mut self) -> Result<String, FbError> {
let receive = [ibase::isc_info_svc_server_version as u8];
let mut buffer = [0u8; 1024];
self.query(&[], &receive, &mut buffer)?;
if buffer[0] != ibase::isc_info_svc_server_version as u8 {
return Err(FbError::from("Unexpected service version reply"));
}
let len = u16::from_le_bytes([buffer[1], buffer[2]]) as usize;
Ok(String::from_utf8_lossy(&buffer[3..3 + len]).to_string())
}
pub fn backup(
&mut self,
db_name: &str,
backup_file: &str,
options: SvcBackupOptions,
) -> Result<(), FbError> {
self.backup_with_output(db_name, backup_file, options, |_| {})
}
pub fn backup_with_output<F: FnMut(&str)>(
&mut self,
db_name: &str,
backup_file: &str,
options: SvcBackupOptions,
on_line: F,
) -> Result<(), FbError> {
let mut flags = 0u32;
if options.metadata_only {
flags |= ibase::isc_spb_bkp_metadata_only;
}
if options.ignore_checksums {
flags |= ibase::isc_spb_bkp_ignore_checksums;
}
if options.ignore_limbo {
flags |= ibase::isc_spb_bkp_ignore_limbo;
}
if options.no_garbage_collect {
flags |= ibase::isc_spb_bkp_no_garbage_collect;
}
let mut req = vec![ibase::isc_action_svc_backup as u8];
push_string_arg(&mut req, ibase::isc_spb_dbname as u8, db_name);
push_string_arg(&mut req, ibase::isc_spb_bkp_file as u8, backup_file);
push_u32_arg(&mut req, ibase::isc_spb_options as u8, flags);
req.push(ibase::isc_spb_verbose as u8);
self.start(&req)?;
self.drain_output(on_line)
}
pub fn restore(
&mut self,
backup_file: &str,
db_name: &str,
options: SvcRestoreOptions,
) -> Result<(), FbError> {
self.restore_with_output(backup_file, db_name, options, |_| {})
}
pub fn restore_with_output<F: FnMut(&str)>(
&mut self,
backup_file: &str,
db_name: &str,
options: SvcRestoreOptions,
on_line: F,
) -> Result<(), FbError> {
let flags = if options.replace {
ibase::isc_spb_res_replace
} else {
ibase::isc_spb_res_create
};
let mut req = vec![ibase::isc_action_svc_restore as u8];
push_string_arg(&mut req, ibase::isc_spb_bkp_file as u8, backup_file);
push_string_arg(&mut req, ibase::isc_spb_dbname as u8, db_name);
push_u32_arg(&mut req, ibase::isc_spb_options as u8, flags);
if let Some(page_size) = options.page_size {
push_u32_arg(&mut req, ibase::isc_spb_res_page_size as u8, page_size);
}
req.push(ibase::isc_spb_verbose as u8);
self.start(&req)?;
self.drain_output(on_line)
}
pub fn detach(&mut self) -> Result<(), FbError> {
match &mut self.inner {
#[cfg(feature = "linking")]
SvcContainer::Linking(s) => s.detach(),
#[cfg(feature = "dynamic_loading")]
SvcContainer::DynLoad(s) => s.detach(),
}
}
fn start(&mut self, request: &[u8]) -> Result<(), FbError> {
match &mut self.inner {
#[cfg(feature = "linking")]
SvcContainer::Linking(s) => s.start(request),
#[cfg(feature = "dynamic_loading")]
SvcContainer::DynLoad(s) => s.start(request),
}
}
fn query(&mut self, send: &[u8], receive: &[u8], buffer: &mut [u8]) -> Result<(), FbError> {
match &mut self.inner {
#[cfg(feature = "linking")]
SvcContainer::Linking(s) => s.query(send, receive, buffer),
#[cfg(feature = "dynamic_loading")]
SvcContainer::DynLoad(s) => s.query(send, receive, buffer),
}
}
fn drain_output<F: FnMut(&str)>(&mut self, mut on_line: F) -> Result<(), FbError> {
let receive = [ibase::isc_info_svc_line as u8];
loop {
let mut buffer = [0u8; 4096];
self.query(&[], &receive, &mut buffer)?;
if buffer[0] != ibase::isc_info_svc_line as u8 {
return Ok(());
}
let len = u16::from_le_bytes([buffer[1], buffer[2]]) as usize;
if len == 0 {
return Ok(());
}
on_line(&String::from_utf8_lossy(&buffer[3..3 + len]));
}
}
}
fn build_attach_spb(user: &str, pass: &str) -> Vec<u8> {
let mut spb = vec![
ibase::isc_spb_version as u8,
ibase::isc_spb_current_version as u8,
];
spb.push(ibase::isc_spb_user_name as u8);
spb.push(user.len() as u8);
spb.extend_from_slice(user.as_bytes());
spb.push(ibase::isc_spb_password as u8);
spb.push(pass.len() as u8);
spb.extend_from_slice(pass.as_bytes());
spb
}
fn push_string_arg(req: &mut Vec<u8>, tag: u8, value: &str) {
req.push(tag);
req.extend_from_slice(&(value.len() as u16).to_le_bytes());
req.extend_from_slice(value.as_bytes());
}
fn push_u32_arg(req: &mut Vec<u8>, tag: u8, value: u32) {
req.push(tag);
req.extend_from_slice(&value.to_le_bytes());
}