use rustc_hash::FxHashMap as HashMap;
use super::{
ScopeFileSystem,
index::PackIndex,
pack::{PackId, PackIdAlloc},
};
use crate::{Error, Result};
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Meta {
pack_id_alloc: PackIdAlloc,
hot_pack_index: PackIndex,
cold_pack_indexes: HashMap<PackId, PackIndex>,
}
impl Meta {
pub const FILE_NAME: &str = "_meta";
fn parse_index_line(line: &str) -> Option<(PackId, PackIndex)> {
let (pack_id_str, index_str) = line.split_once(' ')?;
let pack_id = pack_id_str.parse().ok()?;
let index = index_str.parse().ok()?;
Some((pack_id, index))
}
pub async fn load(fs: &ScopeFileSystem) -> Result<Self> {
let mut meta = Self::default();
let mut reader = fs.stream_read(&Self::FILE_NAME).await?;
meta.pack_id_alloc = reader.read_line().await?.parse()?;
while let Ok(line) = reader.read_line().await {
if line.is_empty() {
break;
}
let Some((pack_id, pack_index)) = Self::parse_index_line(&line) else {
return Err(Error::InvalidFormat(format!(
"Failed to parse pack index in '{}': invalid line '{}'",
Self::FILE_NAME,
line
)));
};
meta.update_pack_index(pack_id, Some(pack_index));
}
Ok(meta)
}
pub async fn save(&self, fs: &ScopeFileSystem) -> Result<()> {
let mut writer = fs.stream_write(&Self::FILE_NAME).await?;
writer.write_line(&self.pack_id_alloc.to_string()).await?;
writer
.write_line(&format!(
"{} {}",
PackIdAlloc::HOT_PACK_ID,
self.hot_pack_index
))
.await?;
for (pack_id, index) in self.cold_pack_indexes.iter() {
writer.write_line(&format!("{pack_id} {index}")).await?;
}
writer.flush().await?;
Ok(())
}
pub fn next_pack_id(&mut self) -> PackId {
self.pack_id_alloc.next_id()
}
pub fn update_pack_index(&mut self, id: PackId, index: Option<PackIndex>) {
match index {
Some(index) => {
if id == PackIdAlloc::HOT_PACK_ID {
self.hot_pack_index = index;
} else {
self.cold_pack_indexes.insert(id, index);
}
}
None => {
if id == PackIdAlloc::HOT_PACK_ID {
unreachable!("Cannot remove hot pack index (ID 0)")
} else {
self.cold_pack_indexes.remove(&id);
self.pack_id_alloc.add_id(id);
}
}
}
}
pub fn hot_pack_index(&self) -> &PackIndex {
&self.hot_pack_index
}
pub fn cold_pack_indexes(&self) -> &HashMap<PackId, PackIndex> {
&self.cold_pack_indexes
}
}
#[cfg(test)]
mod test {
use super::{super::Pack, Meta, Result, ScopeFileSystem};
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn test_meta() -> Result<()> {
let fs = ScopeFileSystem::new_memory_fs("/bucket1".into());
fs.ensure_exist().await?;
assert!(Meta::load(&fs).await.is_err());
let pack = Pack::new(vec![("key1".into(), "value1".into())]);
let mut meta = Meta::default();
let pack_id_1 = meta.pack_id_alloc.next_id();
let index_1 = pack.save(&fs, pack_id_1).await?;
meta.update_pack_index(pack_id_1, Some(index_1));
let pack_id_2 = meta.pack_id_alloc.next_id();
let index_2 = pack.save(&fs, pack_id_2).await?;
meta.update_pack_index(pack_id_2, Some(index_2));
let temp_id = meta.pack_id_alloc.next_id();
meta.pack_id_alloc.add_id(temp_id);
meta.save(&fs).await?;
let other_meta = Meta::load(&fs).await?;
assert_eq!(meta, other_meta);
Ok(())
}
}