Serve files
This commit is contained in:
parent
145e3c592e
commit
ca52201880
4 changed files with 125 additions and 41 deletions
|
|
@ -188,6 +188,7 @@ impl<'a> Iterator for RepoMetadataIter<'a> {
|
||||||
return Some(Err(ReadRepoMetadataError::InvalidFormat));
|
return Some(Err(ReadRepoMetadataError::InvalidFormat));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.offset += 48 + file_path_len;
|
||||||
Some(Ok(RepoMetadataEntry { file_path, hash }))
|
Some(Ok(RepoMetadataEntry { file_path, hash }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
117
src/server.rs
117
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 askama::Template;
|
||||||
use async_lock::Mutex;
|
use async_lock::Mutex;
|
||||||
use log::error;
|
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::{Conn, Handler};
|
||||||
use trillium_router::{Router, RouterConnExt};
|
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::<
|
let metadata_cache: &'static _ = Box::leak(Box::new(Mutex::new(cache::Cache::<
|
||||||
String,
|
String,
|
||||||
HashMap<String, String>,
|
templates::Directory,
|
||||||
|
>::default())));
|
||||||
|
let file_cache: &'static _ = Box::leak(Box::new(Mutex::new(cache::Cache::<
|
||||||
|
(String, String),
|
||||||
|
String,
|
||||||
>::default())));
|
>::default())));
|
||||||
let client: &'static _ = Box::leak(Box::new(async_lock::Mutex::new(
|
let client: &'static _ = Box::leak(Box::new(async_lock::Mutex::new(
|
||||||
crate::api_client::make_client(),
|
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 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,
|
&mut client,
|
||||||
&repo_url,
|
&repo_index,
|
||||||
&commit_hash,
|
&mut repo_metadata,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("todo handle error");
|
.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();
|
//let planet = conn.param("planet").unwrap();
|
||||||
conn.ok(crate::templates::Home {}.render().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);
|
return conn.with_status(401);
|
||||||
};
|
};
|
||||||
|
|
||||||
let cache_fetch = |key| {
|
let fetch_metadata = |key| {
|
||||||
let mut repo_hash = [0; 32];
|
//let mut repo_hash = [0; 24];
|
||||||
if base64_turbo::URL_SAFE.decode_into(key, &mut repo_hash)
|
//if dbg!(base64_turbo::URL_SAFE.decode_into(key, &mut repo_hash))
|
||||||
!= Ok(32)
|
// != Ok(16)
|
||||||
{
|
//{
|
||||||
return None;
|
// return None;
|
||||||
}
|
//}
|
||||||
|
//let repo_hash = &repo_hash[0..16];
|
||||||
let repo_dir = PathBuf::from(&config.data_dir)
|
let repo_dir = PathBuf::from(&config.data_dir)
|
||||||
.join(crate::SUBDIR_REPOS)
|
.join(crate::SUBDIR_REPOS)
|
||||||
.join(repo_hash_str);
|
.join(key);
|
||||||
let repo_metadata =
|
let repo_metadata =
|
||||||
match crate::repo::RepoMetadata::read_from_file(config, &repo_dir) {
|
match crate::repo::RepoMetadata::read_from_file(config, &repo_dir) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
|
|
@ -95,7 +116,7 @@ pub fn make_router(config: &'static Config) -> impl Handler {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut entries = templates::Directory::default();
|
let mut root = templates::Directory::default();
|
||||||
for entry in repo_metadata.iter_files() {
|
for entry in repo_metadata.iter_files() {
|
||||||
match entry {
|
match entry {
|
||||||
Ok(entry) => {
|
Ok(entry) => {
|
||||||
|
|
@ -104,33 +125,71 @@ pub fn make_router(config: &'static Config) -> impl Handler {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let path = entry.file_path.split('/').peekable();
|
let path = entry.file_path.split('/').peekable();
|
||||||
let template_entry = match entry {
|
|
||||||
|
|
||||||
}
|
root.insert(
|
||||||
entries.insert(, path);
|
templates::Entry::File(templates::File {
|
||||||
|
name: name.into(),
|
||||||
|
path: dbg!(entry.file_path).into(),
|
||||||
|
hash: entry.hash.into(),
|
||||||
|
}),
|
||||||
|
path,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Reading repo metadata file index: {e:?}")
|
error!("Reading repo metadata file index: {e:?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(files)
|
Some(root)
|
||||||
};
|
};
|
||||||
// TODO replace mutex with better thing (less contention or async mutex)
|
// TODO replace mutex with better thing (less contention or async mutex)
|
||||||
let Some(metadata) = metadata_cache
|
let Some(root) = metadata_cache
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.fetch(repo_hash_str.to_string(), cache_fetch) else {
|
.fetch(repo_hash_str.to_string(), fetch_metadata)
|
||||||
return conn.with_status(404);
|
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(
|
let hl_options = giallo::HighlightOptions::new(
|
||||||
"py",
|
"py",
|
||||||
giallo::ThemeVariant::Single("catppuccin-frappe"),
|
giallo::ThemeVariant::Single("catppuccin-frappe"),
|
||||||
);
|
);
|
||||||
let highlighted = hl_registry
|
let highlighted = hl_registry.highlight(&file_content, &hl_options).unwrap();
|
||||||
.highlight("def foo():\n\tpass", &hl_options)
|
|
||||||
.unwrap();
|
|
||||||
let html = giallo::HtmlRenderer::default().render(
|
let html = giallo::HtmlRenderer::default().render(
|
||||||
&highlighted,
|
&highlighted,
|
||||||
&giallo::RenderOptions {
|
&giallo::RenderOptions {
|
||||||
|
|
@ -140,7 +199,7 @@ pub fn make_router(config: &'static Config) -> impl Handler {
|
||||||
);
|
);
|
||||||
conn.ok(crate::templates::Repo {
|
conn.ok(crate::templates::Repo {
|
||||||
content: html.clone(),
|
content: html.clone(),
|
||||||
entries: Vec::new(),
|
root,
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
.unwrap())
|
.unwrap())
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ pub struct Home {}
|
||||||
#[template(path = "repo.html")]
|
#[template(path = "repo.html")]
|
||||||
pub struct Repo {
|
pub struct Repo {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub entries: BTreeSet<Entry>,
|
pub root: Directory,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Clone, Template)]
|
||||||
#[template(
|
#[template(
|
||||||
source = r#"
|
source = r#"
|
||||||
{% match self %}
|
{% match self %}
|
||||||
|
|
@ -44,16 +44,18 @@ pub enum Entry {
|
||||||
File(File),
|
File(File),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct File {
|
pub struct File {
|
||||||
name: String,
|
pub name: String,
|
||||||
path: String,
|
pub path: String,
|
||||||
|
pub hash: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct Directory {
|
pub struct Directory {
|
||||||
name: String,
|
pub name: String,
|
||||||
path: String,
|
pub path: String,
|
||||||
entries: BTreeMap<String, Entry>,
|
pub entries: BTreeMap<String, Entry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
|
|
@ -78,13 +80,16 @@ impl PartialOrd<Entry> for Entry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Directory {
|
impl Directory {
|
||||||
|
/// Insert
|
||||||
pub fn insert<'a>(&mut self, entry: Entry, mut path: Peekable<impl Iterator<Item = &'a str>>) {
|
pub fn insert<'a>(&mut self, entry: Entry, mut path: Peekable<impl Iterator<Item = &'a str>>) {
|
||||||
let Some(next_path_level) = path.next() else {
|
let Some(next_path_level) = path.next() else {
|
||||||
warn!("Cannot insert entry: no more path");
|
warn!("Cannot insert entry: no more path");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if path.peek().is_some() {
|
if path.peek().is_some() {
|
||||||
|
// Directory
|
||||||
if let Some(parent) = self.entries.get_mut(next_path_level) {
|
if let Some(parent) = self.entries.get_mut(next_path_level) {
|
||||||
|
// Directory exists
|
||||||
match parent {
|
match parent {
|
||||||
Entry::Directory(dir) => dir.insert(entry, path),
|
Entry::Directory(dir) => dir.insert(entry, path),
|
||||||
Entry::File(_file) => {
|
Entry::File(_file) => {
|
||||||
|
|
@ -92,10 +97,29 @@ impl Directory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
|
// File
|
||||||
self.entries.insert(entry.name().to_string(), entry);
|
self.entries.insert(entry.name().to_string(), entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find<'a>(&self, mut path: impl Iterator<Item = &'a str>) -> Option<&str> {
|
||||||
|
match self.entries.get(path.next()?)? {
|
||||||
|
Entry::Directory(directory) => directory.find(path),
|
||||||
|
Entry::File(file) => Some(&file.hash),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ html, body {
|
||||||
</header>
|
</header>
|
||||||
<div id="tree">
|
<div id="tree">
|
||||||
<ul class="tree">
|
<ul class="tree">
|
||||||
{% for entry in entries %}
|
{% for (_, entry) in root.entries %}
|
||||||
{{ entry.render()? }}
|
{{ entry.render()?|safe }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue