Compare commits
6 changed files with 675 additions and 787 deletions
1375
Cargo.lock
generated
1375
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
34
Cargo.toml
34
Cargo.toml
|
|
@ -5,33 +5,31 @@ edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# CLI options
|
# CLI options
|
||||||
argp = "0.4.1"
|
argp = "0.4.0"
|
||||||
# Templates
|
# Templates
|
||||||
askama = "0.16.0"
|
askama = "0.12.1"
|
||||||
async-lock = "3.4.2"
|
async-lock = "3.4.2"
|
||||||
base64-turbo = "0.2.0"
|
base64-turbo = "0.1.3"
|
||||||
# TOML parser
|
# TOML parser
|
||||||
boml = "2.0.0"
|
boml = "1.0.2"
|
||||||
# Concurrent hash map for caching
|
|
||||||
dashmap = "6.2.1"
|
|
||||||
#flate2 = "1.1.8"
|
#flate2 = "1.1.8"
|
||||||
# request parsing
|
# request parsing
|
||||||
form_urlencoded = "1.2.2"
|
form_urlencoded = "1.2.2"
|
||||||
# Code highlight
|
# Code highlight
|
||||||
giallo = { version = "0.5.0", features = ["dump"] }
|
giallo = { version = "0.3.1", features = ["dump"] }
|
||||||
log = "0.4.33"
|
log = "0.4.29"
|
||||||
rand = "0.10.2"
|
rand = "0.9.2"
|
||||||
serde = { version = "1.0.229", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
sha2 = "0.11.0"
|
sha2 = "0.10.9"
|
||||||
simplelog = "0.12.2"
|
simplelog = "0.12.2"
|
||||||
#tar = "0.4.44"
|
#tar = "0.4.44"
|
||||||
# Web server
|
# Web server
|
||||||
trillium = "1.3.0"
|
trillium = "0.2.20"
|
||||||
trillium-askama = "0.5.0"
|
trillium-askama = "0.3.2"
|
||||||
trillium-caching-headers = "0.4.2"
|
trillium-caching-headers = "0.2.3"
|
||||||
trillium-client = { version = "0.9.13", features = ["serde_json"] }
|
trillium-client = { version = "0.6.2", features = ["json"] }
|
||||||
trillium-native-tls = "0.6.3"
|
trillium-native-tls = "0.4.0"
|
||||||
trillium-router = "0.5.2"
|
trillium-router = "0.4.1"
|
||||||
trillium-smol = "0.7.0"
|
trillium-smol = "0.4.2"
|
||||||
#trillium-static-compiled = "0.5.2"
|
#trillium-static-compiled = "0.5.2"
|
||||||
url = "2.5.8"
|
url = "2.5.8"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
repo::{RepoMetadata, WriteRepoMetadataError},
|
repo::{RepoMetadata, WriteRepoMetadataError},
|
||||||
};
|
};
|
||||||
|
|
||||||
use rand::RngExt;
|
use rand::Rng;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
use trillium_client::Client;
|
use trillium_client::Client;
|
||||||
|
|
|
||||||
37
src/cache.rs
37
src/cache.rs
|
|
@ -1,50 +1,47 @@
|
||||||
use dashmap::DashMap;
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Borrow,
|
borrow::Borrow,
|
||||||
|
collections::{HashMap, VecDeque},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
sync::atomic::{AtomicU64, Ordering::Relaxed},
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct CacheEntry<T> {
|
pub struct CacheEntry<T> {
|
||||||
inner: T,
|
inner: T,
|
||||||
time: AtomicU64,
|
time: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Cache<K: Eq + Hash, V> {
|
pub struct Cache<K, V> {
|
||||||
ttl: u64,
|
ttl: u64,
|
||||||
size: usize,
|
size: usize,
|
||||||
content: DashMap<K, CacheEntry<V>>,
|
content: HashMap<K, CacheEntry<V>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Clone + Eq + Hash, V: Clone> Cache<K, V> {
|
impl<K: Clone + Eq + Hash, V: Clone> Cache<K, V> {
|
||||||
pub fn sweep(&self) {
|
pub fn sweep(&mut self) {
|
||||||
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
||||||
self.content
|
self.content.retain(|_k, entry| entry.time + self.ttl < now);
|
||||||
.retain(|_k, entry| entry.time.load(Relaxed) + self.ttl < now);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sweep_oldest(&self) {
|
fn sweep_oldest(&mut self) {
|
||||||
// TODO sweep the N oldest
|
// TODO sweep the N oldest
|
||||||
let mut oldest = None;
|
let mut oldest = None;
|
||||||
let mut time = u64::MAX;
|
let mut time = u64::MAX;
|
||||||
for item in self.content.iter() {
|
for (key, entry) in self.content.iter() {
|
||||||
let (key, entry) = item.pair();
|
if entry.time < time {
|
||||||
let entry_time = entry.time.load(Relaxed);
|
time = entry.time;
|
||||||
if entry_time < time {
|
oldest = Some(key);
|
||||||
time = entry_time;
|
|
||||||
oldest = Some(key.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(oldest) = oldest {
|
if let Some(oldest) = oldest.cloned() {
|
||||||
self.content.remove(&oldest);
|
self.content.remove(&oldest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch(&self, key: impl Borrow<K>, f: impl Fn(K) -> Option<V>) -> Option<V> {
|
pub fn fetch(&mut self, key: impl Borrow<K>, f: impl Fn(K) -> Option<V>) -> Option<V> {
|
||||||
if let Some(entry) = self.content.get(key.borrow()) {
|
if let Some(entry) = self.content.get_mut(key.borrow()) {
|
||||||
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
||||||
entry.time.store(now, Relaxed);
|
entry.time = now;
|
||||||
Some(entry.inner.clone())
|
Some(entry.inner.clone())
|
||||||
} else if let Some(inner) = (f)(key.borrow().clone()) {
|
} else if let Some(inner) = (f)(key.borrow().clone()) {
|
||||||
if self.content.len() >= self.size {
|
if self.content.len() >= self.size {
|
||||||
|
|
@ -52,7 +49,7 @@ impl<K: Clone + Eq + Hash, V: Clone> Cache<K, V> {
|
||||||
}
|
}
|
||||||
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
||||||
let entry = CacheEntry {
|
let entry = CacheEntry {
|
||||||
time: AtomicU64::new(now),
|
time: now,
|
||||||
inner: inner.clone(),
|
inner: inner.clone(),
|
||||||
};
|
};
|
||||||
self.content.insert(key.borrow().clone(), entry);
|
self.content.insert(key.borrow().clone(), entry);
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,38 @@
|
||||||
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 log::error;
|
use log::error;
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::{BTreeMap, BTreeSet, HashMap},
|
||||||
io::{ErrorKind, Read},
|
io::{ErrorKind, Read},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
use trillium::{Conn, Handler};
|
use trillium::{Conn, Handler};
|
||||||
use trillium_router::{Router, RouterConnExt};
|
use trillium_router::{Router, RouterConnExt};
|
||||||
|
|
||||||
|
pub async fn hello_world(conn: Conn) -> Conn {
|
||||||
|
conn.ok("hello world!")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn make_router(config: &'static Config) -> impl Handler {
|
pub fn make_router(config: &'static Config) -> impl Handler {
|
||||||
let mut hl_registry = giallo::Registry::builtin().unwrap();
|
let mut hl_registry = giallo::Registry::builtin().unwrap();
|
||||||
hl_registry.link_grammars();
|
hl_registry.link_grammars();
|
||||||
let hl_registry: &'static _ = Box::leak(Box::new(hl_registry));
|
let hl_registry: &'static _ = Box::leak(Box::new(hl_registry));
|
||||||
|
|
||||||
let metadata_cache: &'static _ = Box::leak(Box::new(cache::Cache::<
|
let metadata_cache: &'static _ = Box::leak(Box::new(Mutex::new(cache::Cache::<
|
||||||
String,
|
String,
|
||||||
templates::Directory,
|
templates::Directory,
|
||||||
>::default()));
|
>::default())));
|
||||||
let file_cache: &'static _ =
|
let file_cache: &'static _ = Box::leak(Box::new(Mutex::new(cache::Cache::<
|
||||||
Box::leak(Box::new(cache::Cache::<(String, String), String>::default()));
|
(String, String),
|
||||||
|
String,
|
||||||
|
>::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(),
|
||||||
)));
|
)));
|
||||||
|
|
@ -32,11 +45,10 @@ pub fn make_router(config: &'static Config) -> impl Handler {
|
||||||
conn.ok(crate::templates::Home {}.render().unwrap())
|
conn.ok(crate::templates::Home {}.render().unwrap())
|
||||||
})
|
})
|
||||||
.post("/fetch", move |mut conn: Conn| async move {
|
.post("/fetch", move |mut conn: Conn| async move {
|
||||||
if let Ok(request_body) = conn.request_body().with_max_len(8192).read_bytes().await
|
if let Ok(request_body) = conn.request_body().await.with_max_len(8192).await {
|
||||||
{
|
|
||||||
let mut repo_url = None;
|
let mut repo_url = None;
|
||||||
let mut commit_hash = None;
|
let mut commit_hash = None;
|
||||||
for (key, val) in form_urlencoded::parse(&request_body) {
|
for (key, val) in form_urlencoded::parse(request_body.as_bytes()) {
|
||||||
match key.as_ref() {
|
match key.as_ref() {
|
||||||
"repo-url" => {
|
"repo-url" => {
|
||||||
repo_url = Some(val);
|
repo_url = Some(val);
|
||||||
|
|
@ -130,8 +142,11 @@ pub fn make_router(config: &'static Config) -> impl Handler {
|
||||||
}
|
}
|
||||||
Some(root)
|
Some(root)
|
||||||
};
|
};
|
||||||
let Some(root) =
|
// TODO replace mutex with better thing (less contention or async mutex)
|
||||||
metadata_cache.fetch(repo_hash_str.to_string(), fetch_metadata)
|
let Some(root) = metadata_cache
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.fetch(repo_hash_str.to_string(), fetch_metadata)
|
||||||
else {
|
else {
|
||||||
return conn.with_status(404);
|
return conn.with_status(404);
|
||||||
};
|
};
|
||||||
|
|
@ -163,7 +178,7 @@ pub fn make_router(config: &'static Config) -> impl Handler {
|
||||||
|
|
||||||
Some(buf)
|
Some(buf)
|
||||||
};
|
};
|
||||||
let Some(file_content) = file_cache.fetch(
|
let Some(file_content) = file_cache.lock().await.fetch(
|
||||||
(repo_hash_str.to_string(), file_hash.to_string()),
|
(repo_hash_str.to_string(), file_hash.to_string()),
|
||||||
fetch_file,
|
fetch_file,
|
||||||
) else {
|
) else {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
use std::{collections::BTreeMap, iter::Peekable};
|
use std::{
|
||||||
|
collections::{BTreeMap, BTreeSet, HashMap, btree_map, btree_set},
|
||||||
|
iter::Peekable,
|
||||||
|
};
|
||||||
|
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use trillium_askama::Template;
|
use trillium_askama::Template;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue