use std::collections::HashSet;
use crate::pager::Pager;
use crate::pager::format::data_page::body_capacity;
use crate::pager::format::page_kind::PageKind;
use crate::vfs::Vfs;
use crate::{PagedbError, RealmId, Result};
const ENTRY_LEN: usize = 16;
const PAGE_HEADER_LEN: usize = 12;
#[must_use]
pub const fn chain_capacity(page_size: usize) -> usize {
(body_capacity(page_size) - PAGE_HEADER_LEN) / ENTRY_LEN
}
pub async fn read_chain<V: Vfs + Clone>(
pager: &Pager<V>,
realm_id: RealmId,
head: u64,
) -> Result<(Vec<(u64, u64)>, Vec<u64>)> {
let mut entries = Vec::new();
let mut chain_pages = Vec::new();
let mut page = head;
while page != 0 {
let guard = pager.read_main_page(page, realm_id, PageKind::Free).await?;
let body = guard.body_ref();
let mut next_b = [0u8; 8];
next_b.copy_from_slice(&body[0..8]);
let next = u64::from_le_bytes(next_b);
let mut cnt_b = [0u8; 4];
cnt_b.copy_from_slice(&body[8..12]);
let count = u32::from_le_bytes(cnt_b) as usize;
for i in 0..count {
let off = PAGE_HEADER_LEN + i * ENTRY_LEN;
let mut cid_b = [0u8; 8];
cid_b.copy_from_slice(&body[off..off + 8]);
let mut pid_b = [0u8; 8];
pid_b.copy_from_slice(&body[off + 8..off + 16]);
entries.push((u64::from_le_bytes(cid_b), u64::from_le_bytes(pid_b)));
}
chain_pages.push(page);
page = next;
}
Ok((entries, chain_pages))
}
pub async fn rewrite_chain<V: Vfs + Clone>(
pager: &Pager<V>,
realm_id: RealmId,
page_size: usize,
mut entries: Vec<(u64, u64)>,
host_candidates: Vec<u64>,
next_page: u64,
) -> Result<(u64, u64)> {
let cap = chain_capacity(page_size);
let total = entries.len();
let mut next = next_page;
let mut carved: HashSet<u64> = HashSet::new();
let mut chain_pages: Vec<u64> = Vec::new();
let mut hosts = host_candidates.into_iter();
loop {
let remaining = total - carved.len();
let need = if remaining == 0 {
0
} else {
remaining.div_ceil(cap)
};
if chain_pages.len() >= need {
break;
}
if let Some(h) = hosts.next() {
carved.insert(h);
chain_pages.push(h);
} else {
chain_pages.push(next);
next += 1;
}
}
entries.retain(|(_, pid)| !carved.contains(pid));
let head = write_chain(pager, realm_id, page_size, &chain_pages, &entries).await?;
Ok((head, next))
}
pub async fn write_chain<V: Vfs + Clone>(
pager: &Pager<V>,
realm_id: RealmId,
page_size: usize,
chain_pages: &[u64],
entries: &[(u64, u64)],
) -> Result<u64> {
if entries.is_empty() {
return Ok(0);
}
let cap = chain_capacity(page_size);
let body_len = body_capacity(page_size);
debug_assert!(chain_pages.len() * cap >= entries.len());
let mut written = 0usize;
for (i, &page_id) in chain_pages.iter().enumerate() {
let chunk = &entries[written..(written + cap).min(entries.len())];
let next = chain_pages.get(i + 1).copied().unwrap_or(0);
let next = if chunk.is_empty() { 0 } else { next };
let mut body = vec![0u8; body_len];
body[0..8].copy_from_slice(&next.to_le_bytes());
let chunk_len = u32::try_from(chunk.len())
.map_err(|_| PagedbError::Io(std::io::Error::other("free-list chunk_len overflow")))?;
body[8..12].copy_from_slice(&chunk_len.to_le_bytes());
for (j, (cid, pid)) in chunk.iter().enumerate() {
let off = PAGE_HEADER_LEN + j * ENTRY_LEN;
body[off..off + 8].copy_from_slice(&cid.to_le_bytes());
body[off + 8..off + 16].copy_from_slice(&pid.to_le_bytes());
}
pager
.write_main_page(page_id, realm_id, PageKind::Free, &body)
.await?;
written += chunk.len();
if written >= entries.len() {
break;
}
}
Ok(chain_pages[0])
}