use super::*;
pub struct EmbeddedRdbFile<F: VfsFile> {
file: F,
path: PathBuf,
failed: bool,
}
impl<F: VfsFile> EmbeddedRdbFile<F> {
pub fn create(
mut file: F,
name: impl Into<PathBuf>,
snapshot: &[u8],
created_at_unix_ms: u128,
) -> RdbFileResult<Self> {
validate_snapshot(snapshot)?;
if file.file_len()? != 0 {
return Err(RdbFileError::InvalidOperation(
"creating an embedded file requires an empty handle".into(),
));
}
let path = name.into();
create_file(&mut file, &path, snapshot, created_at_unix_ms)?;
Ok(Self {
file,
path,
failed: false,
})
}
pub fn open(mut file: F, name: impl Into<PathBuf>) -> RdbFileResult<Self> {
let path = name.into();
let open = EmbeddedRdbArtifact::open_inner_with_file(&mut file, &path, true)?;
let scan = scan_wal_with_file(&mut file, &open, open.manifest.wal_region_offset)?;
validate_wal(&open, &scan)?;
Ok(Self {
file,
path,
failed: false,
})
}
pub fn state(&mut self) -> RdbFileResult<EmbeddedRdbOpen> {
self.check_healthy()?;
let result = EmbeddedRdbArtifact::open_inner_with_file(&mut self.file, &self.path, true);
self.finish(result)
}
pub fn read_snapshot(&mut self) -> RdbFileResult<Option<Vec<u8>>> {
let open = self.state()?;
let result = read_snapshot_file(&mut self.file, &open);
self.finish(result)
}
pub fn read_wal_payloads(&mut self) -> RdbFileResult<Vec<Vec<u8>>> {
let open = self.state()?;
let result = (|| {
let scan = scan_wal_with_file(&mut self.file, &open, open.manifest.wal_region_offset)?;
validate_wal(&open, &scan)?;
Ok(scan.payloads)
})();
self.finish(result)
}
pub fn append_wal_payloads(&mut self, payloads: &[Vec<u8>]) -> RdbFileResult<EmbeddedRdbOpen> {
let open = self.state()?;
if payloads.is_empty() {
return Ok(open);
}
let result = (|| {
let scan = scan_wal_with_file(&mut self.file, &open, open.manifest.wal_region_offset)?;
append_file(&mut self.file, &self.path, payloads, open, scan, true)
.map(|published| published.open)
})();
self.finish(result)
}
pub fn write_snapshot(
&mut self,
snapshot: &[u8],
min_wal_bytes: u64,
) -> RdbFileResult<EmbeddedRdbOpen> {
self.check_healthy()?;
validate_snapshot(snapshot)?;
let result = checkpoint_file(&mut self.file, &self.path, snapshot, min_wal_bytes);
self.finish(result)
}
pub fn into_inner(self) -> F {
self.file
}
fn check_healthy(&self) -> RdbFileResult<()> {
if self.failed {
return Err(RdbFileError::InvalidOperation(
"embedded file must be reopened after a failed operation".into(),
));
}
Ok(())
}
fn finish<T>(&mut self, result: RdbFileResult<T>) -> RdbFileResult<T> {
if result.is_err() {
self.failed = true;
}
result
}
}
fn validate_snapshot(snapshot: &[u8]) -> RdbFileResult<()> {
if !snapshot.is_empty() && !snapshot.starts_with(SNAPSHOT_MAGIC) {
return Err(RdbFileError::InvalidOperation(
"a nonempty embedded snapshot must start with the native RDST magic".into(),
));
}
Ok(())
}
fn validate_wal(open: &EmbeddedRdbOpen, scan: &WalScan) -> RdbFileResult<()> {
if scan.valid_bytes != open.manifest.wal_live_bytes {
return Err(RdbFileError::ZoneUnrecoverable {
zone: "wal",
path: open.path.clone(),
});
}
Ok(())
}