#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndexKind {
Chk,
Revision,
Inventory,
Text,
Signature,
}
impl IndexKind {
pub fn from_name(name: &str) -> Option<Self> {
match name {
"chk" => Some(IndexKind::Chk),
"revision" => Some(IndexKind::Revision),
"inventory" => Some(IndexKind::Inventory),
"text" => Some(IndexKind::Text),
"signature" => Some(IndexKind::Signature),
_ => None,
}
}
pub fn as_name(self) -> &'static str {
match self {
IndexKind::Chk => "chk",
IndexKind::Revision => "revision",
IndexKind::Inventory => "inventory",
IndexKind::Text => "text",
IndexKind::Signature => "signature",
}
}
}
pub fn index_definition(kind: IndexKind) -> (&'static str, usize) {
match kind {
IndexKind::Chk => (".cix", 4),
IndexKind::Revision => (".rix", 0),
IndexKind::Inventory => (".iix", 1),
IndexKind::Text => (".tix", 2),
IndexKind::Signature => (".six", 3),
}
}
pub fn index_extension(kind: IndexKind) -> &'static str {
index_definition(kind).0
}
pub fn index_offset(kind: IndexKind) -> usize {
index_definition(kind).1
}
pub fn index_name(kind: IndexKind, name: &str) -> String {
format!("{}{}", name, index_extension(kind))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RetrievalGroup<I> {
pub index: I,
pub reads: Vec<(u64, u64)>,
}
pub fn group_retrieval_requests<I, T>(memos: T) -> Vec<RetrievalGroup<I>>
where
I: PartialEq,
T: IntoIterator<Item = (I, u64, u64)>,
{
let mut groups: Vec<RetrievalGroup<I>> = Vec::new();
for (index, offset, length) in memos {
match groups.last_mut() {
Some(last) if last.index == index => last.reads.push((offset, length)),
_ => groups.push(RetrievalGroup {
index,
reads: vec![(offset, length)],
}),
}
}
groups
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RawRecordSlice {
pub start: usize,
pub length: usize,
}
pub fn split_raw_records(
sizes: &[usize],
data_len: usize,
) -> Result<Vec<RawRecordSlice>, PackRepoError> {
let mut slices = Vec::with_capacity(sizes.len());
let mut offset = 0usize;
for &size in sizes {
let end = offset
.checked_add(size)
.ok_or(PackRepoError::SizeOverflow)?;
if end > data_len {
return Err(PackRepoError::SizeMismatch {
total: end,
data_len,
});
}
slices.push(RawRecordSlice {
start: offset,
length: size,
});
offset = end;
}
if offset != data_len {
return Err(PackRepoError::SizeMismatch {
total: offset,
data_len,
});
}
Ok(slices)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PackRepoError {
SizeMismatch { total: usize, data_len: usize },
SizeOverflow,
}
impl std::fmt::Display for PackRepoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PackRepoError::SizeMismatch { total, data_len } => write!(
f,
"raw record sizes sum to {} but buffer is {} bytes",
total, data_len
),
PackRepoError::SizeOverflow => write!(f, "raw record sizes overflowed"),
}
}
}
impl std::error::Error for PackRepoError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReloadDecision {
Retry,
Raise,
}
pub fn reload_decision(
has_reload_func: bool,
reload_changed: bool,
reload_occurred: bool,
) -> ReloadDecision {
if !has_reload_func {
return ReloadDecision::Raise;
}
if !reload_changed && !reload_occurred {
return ReloadDecision::Raise;
}
ReloadDecision::Retry
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn index_definitions_match_python() {
assert_eq!(index_definition(IndexKind::Chk), (".cix", 4));
assert_eq!(index_definition(IndexKind::Revision), (".rix", 0));
assert_eq!(index_definition(IndexKind::Inventory), (".iix", 1));
assert_eq!(index_definition(IndexKind::Text), (".tix", 2));
assert_eq!(index_definition(IndexKind::Signature), (".six", 3));
}
#[test]
fn index_name_appends_extension() {
assert_eq!(index_name(IndexKind::Revision, "abc"), "abc.rix");
assert_eq!(index_name(IndexKind::Chk, "abc"), "abc.cix");
}
#[test]
fn kind_name_round_trips() {
for kind in [
IndexKind::Chk,
IndexKind::Revision,
IndexKind::Inventory,
IndexKind::Text,
IndexKind::Signature,
] {
assert_eq!(IndexKind::from_name(kind.as_name()), Some(kind));
}
assert_eq!(IndexKind::from_name("bogus"), None);
}
#[test]
fn grouping_coalesces_consecutive_same_index() {
let memos = vec![(0u32, 0u64, 10u64), (0, 10, 5), (1, 0, 3), (0, 20, 4)];
let groups = group_retrieval_requests(memos);
assert_eq!(
groups,
vec![
RetrievalGroup {
index: 0,
reads: vec![(0, 10), (10, 5)]
},
RetrievalGroup {
index: 1,
reads: vec![(0, 3)]
},
RetrievalGroup {
index: 0,
reads: vec![(20, 4)]
},
]
);
}
#[test]
fn grouping_empty_yields_no_groups() {
let groups = group_retrieval_requests(Vec::<(u32, u64, u64)>::new());
assert!(groups.is_empty());
}
#[test]
fn split_raw_records_exact() {
let slices = split_raw_records(&[10, 2, 5], 17).unwrap();
assert_eq!(
slices,
vec![
RawRecordSlice {
start: 0,
length: 10
},
RawRecordSlice {
start: 10,
length: 2
},
RawRecordSlice {
start: 12,
length: 5
},
]
);
}
#[test]
fn split_raw_records_too_short_is_error() {
assert_eq!(
split_raw_records(&[10, 5], 12),
Err(PackRepoError::SizeMismatch {
total: 15,
data_len: 12
})
);
}
#[test]
fn split_raw_records_trailing_data_is_error() {
assert_eq!(
split_raw_records(&[10], 12),
Err(PackRepoError::SizeMismatch {
total: 10,
data_len: 12
})
);
}
#[test]
fn reload_decision_table() {
assert_eq!(reload_decision(false, false, false), ReloadDecision::Raise);
assert_eq!(reload_decision(false, true, true), ReloadDecision::Raise);
assert_eq!(reload_decision(true, true, false), ReloadDecision::Retry);
assert_eq!(reload_decision(true, true, true), ReloadDecision::Retry);
assert_eq!(reload_decision(true, false, false), ReloadDecision::Raise);
assert_eq!(reload_decision(true, false, true), ReloadDecision::Retry);
}
}