use std::collections::BTreeSet;
use std::path::Path;
use tokio::fs;
use tokio::io::{AsyncReadExt, AsyncSeekExt};
use crate::Result;
use crate::errors::PagedbError;
use crate::pager::page_space::is_reserved;
use crate::vfs::types::OpenMode;
use crate::vfs::{Vfs, VfsFile, write_all_at};
pub(crate) struct DeltaPlan {
pub page_ids: BTreeSet<u64>,
pub record_count: u64,
record_size: u64,
}
pub(crate) async fn plan_delta_stream(
src_path: &Path,
page_size: usize,
base_live_page_ids: &BTreeSet<u64>,
target_next_page_id: u64,
) -> Result<DeltaPlan> {
let page_size_u64 = u64::try_from(page_size)
.map_err(|_| PagedbError::snapshot_artifact_invalid("pages.delta.page_size"))?;
let record_size = 8u64
.checked_add(page_size_u64)
.ok_or_else(|| PagedbError::snapshot_artifact_invalid("pages.delta.record_size"))?;
let mut delta = match fs::File::open(src_path.join("pages.delta")).await {
Ok(file) => file,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
return Ok(DeltaPlan {
page_ids: BTreeSet::new(),
record_count: 0,
record_size,
});
}
Err(error) => return Err(PagedbError::Io(error)),
};
let delta_len = delta.metadata().await.map_err(PagedbError::Io)?.len();
if delta_len % record_size != 0 {
return Err(PagedbError::snapshot_artifact_invalid("pages.delta.length"));
}
let record_count = delta_len / record_size;
let mut page_ids = BTreeSet::new();
let mut id_buf = [0u8; 8];
let page_skip = i64::try_from(page_size)
.map_err(|_| PagedbError::snapshot_artifact_invalid("pages.delta.page_size"))?;
for _ in 0..record_count {
delta
.read_exact(&mut id_buf)
.await
.map_err(PagedbError::Io)?;
let page_id = u64::from_be_bytes(id_buf);
if is_reserved(page_id) || page_id >= target_next_page_id {
return Err(PagedbError::snapshot_artifact_invalid(
"pages.delta.page_id",
));
}
if base_live_page_ids.contains(&page_id) {
return Err(PagedbError::snapshot_base_page_reused(page_id));
}
if !page_ids.insert(page_id) {
return Err(PagedbError::snapshot_artifact_invalid(
"pages.delta.duplicate_page_id",
));
}
delta
.seek(std::io::SeekFrom::Current(page_skip))
.await
.map_err(PagedbError::Io)?;
}
Ok(DeltaPlan {
page_ids,
record_count,
record_size,
})
}
pub(crate) async fn write_delta_into_image<V: Vfs>(
vfs: &V,
image_path: &str,
src_path: &Path,
page_size: usize,
plan: &DeltaPlan,
) -> Result<u64> {
if plan.record_count == 0 {
return Ok(0);
}
let page_size_u64 = u64::try_from(page_size)
.map_err(|_| PagedbError::snapshot_artifact_invalid("pages.delta.page_size"))?;
let mut delta = fs::File::open(src_path.join("pages.delta"))
.await
.map_err(PagedbError::Io)?;
let expected_len = plan
.record_count
.checked_mul(plan.record_size)
.ok_or_else(|| PagedbError::snapshot_artifact_invalid("pages.delta.length"))?;
if delta.metadata().await.map_err(PagedbError::Io)?.len() != expected_len {
return Err(PagedbError::snapshot_artifact_invalid("pages.delta.length"));
}
let mut image = vfs.open(image_path, OpenMode::CreateOrOpen).await?;
let mut id_buf = [0u8; 8];
let mut page_buf = vec![0u8; page_size];
let mut written = 0u64;
for _ in 0..plan.record_count {
delta
.read_exact(&mut id_buf)
.await
.map_err(PagedbError::Io)?;
let page_id = u64::from_be_bytes(id_buf);
delta
.read_exact(&mut page_buf)
.await
.map_err(PagedbError::Io)?;
if !plan.page_ids.contains(&page_id) {
return Err(PagedbError::snapshot_artifact_invalid(
"pages.delta.page_id",
));
}
let offset = page_id
.checked_mul(page_size_u64)
.ok_or_else(|| PagedbError::snapshot_artifact_invalid("pages.delta.page_offset"))?;
write_all_at(&mut image, offset, &page_buf).await?;
written = written.saturating_add(1);
}
image.sync().await?;
Ok(written)
}