From ca5220188069147cc0b8432cc396e9e22e84ae2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Eng=C3=A9libert?= Date: Sat, 25 Jul 2026 19:57:43 +0200 Subject: [PATCH] Serve files --- src/repo.rs | 1 + src/server.rs | 119 +++++++++++++++++++++++++++++++++----------- src/templates.rs | 42 ++++++++++++---- templates/repo.html | 4 +- 4 files changed, 125 insertions(+), 41 deletions(-) diff --git a/src/repo.rs b/src/repo.rs index 3f6f7a0..edbaf88 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -188,6 +188,7 @@ impl<'a> Iterator for RepoMetadataIter<'a> { return Some(Err(ReadRepoMetadataError::InvalidFormat)); }; + self.offset += 48 + file_path_len; Some(Ok(RepoMetadataEntry { file_path, hash })) } } diff --git a/src/server.rs b/src/server.rs index 3f4ee41..e84baa7 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,9 +1,18 @@ -use crate::{cache, config::Config, repo::ReadRepoMetadataError, templates}; +use crate::{ + cache, + config::Config, + repo::{ReadRepoMetadataError, RepoMetadataEntry}, + templates, +}; use askama::Template; use async_lock::Mutex; use log::error; -use std::{collections::{BTreeMap, BTreeSet, HashMap}, io::ErrorKind, path::PathBuf}; +use std::{ + collections::{BTreeMap, BTreeSet, HashMap}, + io::{ErrorKind, Read}, + path::PathBuf, +}; use trillium::{Conn, Handler}; use trillium_router::{Router, RouterConnExt}; @@ -18,7 +27,11 @@ pub fn make_router(config: &'static Config) -> impl Handler { let metadata_cache: &'static _ = Box::leak(Box::new(Mutex::new(cache::Cache::< String, - HashMap, + templates::Directory, + >::default()))); + let file_cache: &'static _ = Box::leak(Box::new(Mutex::new(cache::Cache::< + (String, String), + String, >::default()))); let client: &'static _ = Box::leak(Box::new(async_lock::Mutex::new( crate::api_client::make_client(), @@ -51,17 +64,24 @@ pub fn make_router(config: &'static Config) -> impl Handler { }; let mut client = client.lock().await; - let (repo_index, mut repo_metadata) = crate::api_client::fetch_repo_tree_index_at_commit( + let (repo_index, mut repo_metadata) = + crate::api_client::fetch_repo_tree_index_at_commit( + &mut client, + &repo_url, + &commit_hash, + None, + ) + .await + .expect("todo handle error"); + crate::api_client::fetch_repo_files( + config, &mut client, - &repo_url, - &commit_hash, + &repo_index, + &mut repo_metadata, None, ) .await .expect("todo handle error"); - crate::api_client::fetch_repo_files(config, &mut client, &repo_index, &mut repo_metadata, None) - .await - .expect("todo handle error"); } //let planet = conn.param("planet").unwrap(); conn.ok(crate::templates::Home {}.render().unwrap()) @@ -72,16 +92,17 @@ pub fn make_router(config: &'static Config) -> impl Handler { return conn.with_status(401); }; - let cache_fetch = |key| { - let mut repo_hash = [0; 32]; - if base64_turbo::URL_SAFE.decode_into(key, &mut repo_hash) - != Ok(32) - { - return None; - } + let fetch_metadata = |key| { + //let mut repo_hash = [0; 24]; + //if dbg!(base64_turbo::URL_SAFE.decode_into(key, &mut repo_hash)) + // != Ok(16) + //{ + // return None; + //} + //let repo_hash = &repo_hash[0..16]; let repo_dir = PathBuf::from(&config.data_dir) .join(crate::SUBDIR_REPOS) - .join(repo_hash_str); + .join(key); let repo_metadata = match crate::repo::RepoMetadata::read_from_file(config, &repo_dir) { Ok(v) => v, @@ -95,7 +116,7 @@ pub fn make_router(config: &'static Config) -> impl Handler { return None; } }; - let mut entries = templates::Directory::default(); + let mut root = templates::Directory::default(); for entry in repo_metadata.iter_files() { match entry { Ok(entry) => { @@ -104,33 +125,71 @@ pub fn make_router(config: &'static Config) -> impl Handler { continue; }; let path = entry.file_path.split('/').peekable(); - let template_entry = match entry { - - } - entries.insert(, path); + + root.insert( + templates::Entry::File(templates::File { + name: name.into(), + path: dbg!(entry.file_path).into(), + hash: entry.hash.into(), + }), + path, + ); } Err(e) => { error!("Reading repo metadata file index: {e:?}") } } } - Some(files) + Some(root) }; // TODO replace mutex with better thing (less contention or async mutex) - let Some(metadata) = metadata_cache + let Some(root) = metadata_cache .lock() .await - .fetch(repo_hash_str.to_string(), cache_fetch) else { - return conn.with_status(404); + .fetch(repo_hash_str.to_string(), fetch_metadata) + else { + return conn.with_status(404); + }; + + let Some(file_hash) = root.find(conn.path().split('/')) else { + return conn.with_status(404); + }; + + let fetch_file = |(repo_hash, file_hash)| { + let file_path = PathBuf::from(&config.data_dir) + .join(crate::SUBDIR_REPOS) + .join(repo_hash) + .join(file_hash); + + let mut file = match std::fs::OpenOptions::new().read(true).open(&file_path) + { + Ok(file) => file, + Err(e) => { + error!("Cannot open file `{file_path:?}`: {e:?}"); + return None; + } }; + let mut buf = String::new(); + if let Err(e) = file.read_to_string(&mut buf) { + error!("Error reading file `{file_path:?}`: {e:?}"); + return None; + } + + Some(buf) + }; + let Some(file_content) = file_cache.lock().await.fetch( + (repo_hash_str.to_string(), file_hash.to_string()), + fetch_file, + ) else { + return conn.with_status(500); + }; + let hl_options = giallo::HighlightOptions::new( "py", giallo::ThemeVariant::Single("catppuccin-frappe"), ); - let highlighted = hl_registry - .highlight("def foo():\n\tpass", &hl_options) - .unwrap(); + let highlighted = hl_registry.highlight(&file_content, &hl_options).unwrap(); let html = giallo::HtmlRenderer::default().render( &highlighted, &giallo::RenderOptions { @@ -140,7 +199,7 @@ pub fn make_router(config: &'static Config) -> impl Handler { ); conn.ok(crate::templates::Repo { content: html.clone(), - entries: Vec::new(), + root, } .render() .unwrap()) diff --git a/src/templates.rs b/src/templates.rs index 86fdbea..8a4f90b 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -14,10 +14,10 @@ pub struct Home {} #[template(path = "repo.html")] pub struct Repo { pub content: String, - pub entries: BTreeSet, + pub root: Directory, } -#[derive(Template)] +#[derive(Clone, Template)] #[template( source = r#" {% match self %} @@ -44,16 +44,18 @@ pub enum Entry { File(File), } +#[derive(Clone)] pub struct File { - name: String, - path: String, + pub name: String, + pub path: String, + pub hash: String, } -#[derive(Default)] +#[derive(Clone, Default)] pub struct Directory { - name: String, - path: String, - entries: BTreeMap, + pub name: String, + pub path: String, + pub entries: BTreeMap, } impl Entry { @@ -78,13 +80,16 @@ impl PartialOrd for Entry { } impl Directory { + /// Insert pub fn insert<'a>(&mut self, entry: Entry, mut path: Peekable>) { let Some(next_path_level) = path.next() else { warn!("Cannot insert entry: no more path"); return; }; if path.peek().is_some() { + // Directory if let Some(parent) = self.entries.get_mut(next_path_level) { + // Directory exists match parent { Entry::Directory(dir) => dir.insert(entry, path), Entry::File(_file) => { @@ -92,10 +97,29 @@ impl Directory { } } } else { - warn!("Cannot insert entry: not found"); + //warn!("Cannot insert entry: not found"); + // Need to create directory + let mut new_dir_path = self.path.clone(); + new_dir_path.push_str(next_path_level); + let mut parent = Directory { + name: next_path_level.into(), + path: new_dir_path, + entries: BTreeMap::new(), + }; + parent.insert(entry, path); + self.entries + .insert(next_path_level.into(), Entry::Directory(parent)); } } else { + // File self.entries.insert(entry.name().to_string(), entry); } } + + pub fn find<'a>(&self, mut path: impl Iterator) -> Option<&str> { + match self.entries.get(path.next()?)? { + Entry::Directory(directory) => directory.find(path), + Entry::File(file) => Some(&file.hash), + } + } } diff --git a/templates/repo.html b/templates/repo.html index 55a6f4d..bd1f03d 100644 --- a/templates/repo.html +++ b/templates/repo.html @@ -29,8 +29,8 @@ html, body {
    - {% for entry in entries %} - {{ entry.render()? }} + {% for (_, entry) in root.entries %} + {{ entry.render()?|safe }} {% endfor %}