Initial commit

This commit is contained in:
Pascal Engélibert 2026-01-25 18:27:23 +01:00
commit 88fd274d0c
11 changed files with 2403 additions and 0 deletions

134
src/repo.rs Normal file
View file

@ -0,0 +1,134 @@
use crate::{
api_client::{MAX_PATH_SIZE, MAX_URL_SIZE},
config::Config,
};
use std::{
io::{ErrorKind, Read},
path::Path,
};
const REPO_METADATA_FILE_NAME: &str = "meta.bin";
#[derive(Debug)]
pub struct RepoMetadata {
pub date: u64,
pub commit_url: String,
content: Vec<u8>,
}
#[derive(Debug)]
pub enum ReadRepoMetadataError {
CannotOpenFile(std::io::Error),
CannotReadFile(std::io::Error),
InvalidFormat,
UnsupportedVersion,
}
impl From<std::io::Error> for ReadRepoMetadataError {
fn from(value: std::io::Error) -> Self {
match value.kind() {
ErrorKind::UnexpectedEof => Self::InvalidFormat,
_ => Self::CannotReadFile(value),
}
}
}
impl From<std::string::FromUtf8Error> for ReadRepoMetadataError {
fn from(value: std::string::FromUtf8Error) -> Self {
Self::InvalidFormat
}
}
impl RepoMetadata {
pub fn new(config: &Config, repo_dir: &Path) -> Result<Self, ReadRepoMetadataError> {
let mut file = std::fs::OpenOptions::new()
.read(true)
.open(repo_dir.join(""))
.map_err(ReadRepoMetadataError::CannotOpenFile)?;
let mut version = [0u8; 1];
file.read_exact(&mut version)?;
if version != [0] {
return Err(ReadRepoMetadataError::UnsupportedVersion);
}
let mut date = [0u8; 8];
file.read_exact(&mut date)?;
let date = u64::from_be_bytes(date);
let mut commit_url_len = [0u8; 4];
file.read_exact(&mut commit_url_len)?;
let commit_url_len = u32::from_be_bytes(commit_url_len) as usize;
if commit_url_len > MAX_URL_SIZE {
return Err(ReadRepoMetadataError::InvalidFormat);
}
let mut commit_url = vec![0; commit_url_len];
file.read_exact(&mut commit_url)?;
let commit_url = String::from_utf8(commit_url)?;
let mut content = Vec::new();
file.read_to_end(&mut content)?;
Ok(Self {
date,
commit_url,
content,
})
}
pub fn iter_files<'a>(&'a self) -> RepoMetadataIter<'a> {
RepoMetadataIter {
repo_metadata: self,
offset: 0,
}
}
}
pub struct RepoMetadataIter<'a> {
repo_metadata: &'a RepoMetadata,
offset: usize,
}
pub struct RepoMetadataEntry<'a> {
file_path: &'a str,
hash: &'a str,
}
impl<'a> Iterator for RepoMetadataIter<'a> {
type Item = Result<RepoMetadataEntry<'a>, ReadRepoMetadataError>;
fn next(&mut self) -> Option<Self::Item> {
if self.offset == self.repo_metadata.content.len() {
return None;
}
if self.offset + 4 > self.repo_metadata.content.len() {
return Some(Err(ReadRepoMetadataError::InvalidFormat));
}
let file_path_len = u32::from_be_bytes(
self.repo_metadata.content[self.offset..self.offset + 4]
.try_into()
.expect("unreachable"),
) as usize;
if file_path_len > MAX_PATH_SIZE {
return Some(Err(ReadRepoMetadataError::InvalidFormat));
}
if self.offset + file_path_len + 48 > self.repo_metadata.content.len() {
return Some(Err(ReadRepoMetadataError::InvalidFormat));
}
let Ok(file_path) = str::from_utf8(
&self.repo_metadata.content[self.offset + 4..self.offset + 4 + file_path_len],
) else {
return Some(Err(ReadRepoMetadataError::InvalidFormat));
};
let Ok(hash) = str::from_utf8(
&self.repo_metadata.content
[self.offset + 4 + file_path_len..self.offset + 48 + file_path_len],
) else {
return Some(Err(ReadRepoMetadataError::InvalidFormat));
};
Some(Ok(RepoMetadataEntry { file_path, hash }))
}
}