Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,799 changes: 1,350 additions & 449 deletions Cargo.lock

Large diffs are not rendered by default.

37 changes: 29 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,41 @@ path = "src/main.rs"
name = "hoc"

[dependencies]
actix-rt = "2.11.0"
actix-web = "4.11.0"
badgers = "1.2.0"
anyhow = "1.0.100"
axum = { version = "0.8.6", features = ["macros"] }
badgers = "2.0.0"
bytes = "1.10.1"
config = { version = "0.15.18", features = ["toml"], default-features = false }
dashmap = "6.1.0"
dotenvy = "0.15.7"
futures = "0.3.31"
git2 = "0.20.2"
gix-glob = "0.22.1"
gix = { version = "0.74.1", features = ["async-network-client"] }
itertools = "0.14.0"
jiff = "0.2.15"
mime = "0.3"
number_prefix = "0.4.0"
openssl-probe = "0.1.6"
reqwest = "0.12.24"
reqwest = { version = "0.12.24", default-features = false, features = [
"charset",
"system-proxy",
"http2",
"rustls-tls",
] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
thiserror = "2.0.17"
tokio = { version = "1.48.0", features = [
"macros",
"rt-multi-thread",
"signal",
] }
tower-http = { version = "0.6.6", features = [
"trace",
"request-id",
"compression-deflate",
"compression-gzip",
] }
tracing = "0.1.41"
tracing-actix-web = "0.7.19"
tracing-bunyan-formatter = "0.3.10"
tracing-futures = "0.2.5"
tracing-log = "0.2.0"
tracing-subscriber = { version = "0.3.20", features = [
Expand All @@ -47,6 +63,11 @@ vergen-gix = "1.0.9"

[dev-dependencies]
awc = "3.8.1"
hyper-util = { version = "0.1.17", features = [
"client",
"http1",
"client-legacy",
] }
ructe = "0.18.2"
tempfile = "3.23.0"
tokio = "1.48.0"
4 changes: 1 addition & 3 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
in {
devShells.default = with pkgs;
mkShell {
buildInputs = [ openssl pkg-config rust-toolchain bunyan-rs ];
};
mkShell { buildInputs = [ rust-toolchain ]; };
});
}
36 changes: 18 additions & 18 deletions src/cache.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
config::Settings,
error::{Error, Result},
service::FormValue,
platform::Platform,
};

use std::{
Expand All @@ -19,7 +19,7 @@ pub(crate) trait Cache<K, V> {
fn load(&self, key: &K) -> Result<Option<V>>;
fn store(&self, key: K, value: V) -> Result<()>;

fn clear(&self, service: FormValue, owner: &str, repo: &str) -> Result<()>;
fn clear(&self, platform: Platform, owner: &str, repo: &str) -> Result<()>;
}

pub(crate) trait ToQuery {
Expand All @@ -39,7 +39,7 @@ impl ToQuery for Excludes {

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub(crate) struct CacheKey {
service: FormValue,
platform: Platform,
owner: String,
repo: String,
branch: String,
Expand All @@ -48,14 +48,14 @@ pub(crate) struct CacheKey {

impl CacheKey {
pub(crate) fn new(
service: FormValue,
platform: Platform,
owner: String,
repo: String,
branch: String,
excludes: Excludes,
) -> Self {
Self {
service,
platform,
owner,
repo,
branch,
Expand All @@ -68,7 +68,7 @@ impl CacheKey {

settings
.cachedir
.join(self.service.url())
.join(self.platform.domain())
.join(self.owner.as_str())
.join(self.repo.as_str())
.join(self.branch.as_str())
Expand Down Expand Up @@ -96,7 +96,7 @@ impl Drop for Persist {
fn drop(&mut self) {
info!("persisting cache");
for r in &self.in_memory.cache {
let service = *r.key();
let platform = *r.key();
for r in r.value() {
let owner = r.key();
for r in r.value() {
Expand All @@ -106,7 +106,7 @@ impl Drop for Persist {
for r in r.value() {
let excludes = r.key().clone();
let key = CacheKey::new(
service,
platform,
owner.clone(),
repo.clone(),
branch.clone(),
Expand Down Expand Up @@ -141,9 +141,9 @@ impl Cache<CacheKey, CacheEntry> for Persist {
self.in_memory.store(key, value)
}

fn clear(&self, service: FormValue, owner: &str, repo: &str) -> Result<()> {
let im_res = self.in_memory.clear(service, owner, repo);
let disk_res = self.disk.clear(service, owner, repo);
fn clear(&self, platform: Platform, owner: &str, repo: &str) -> Result<()> {
let im_res = self.in_memory.clear(platform, owner, repo);
let disk_res = self.disk.clear(platform, owner, repo);
if let Err(e) = im_res {
Err(e)?
} else if let Err(e) = disk_res {
Expand All @@ -157,7 +157,7 @@ impl Cache<CacheKey, CacheEntry> for Persist {
struct InMemoryCache {
#[allow(clippy::type_complexity)]
cache: DashMap<
FormValue,
Platform,
DashMap<String, DashMap<String, DashMap<String, DashMap<Excludes, CacheEntry>>>>,
>,
}
Expand All @@ -173,7 +173,7 @@ impl InMemoryCache {
impl Cache<CacheKey, CacheEntry> for InMemoryCache {
fn store(&self, key: CacheKey, value: CacheEntry) -> Result<()> {
self.cache
.entry(key.service)
.entry(key.platform)
.or_default()
.entry(key.owner)
.or_default()
Expand All @@ -186,7 +186,7 @@ impl Cache<CacheKey, CacheEntry> for InMemoryCache {
}

fn load(&self, key: &CacheKey) -> Result<Option<CacheEntry>> {
Ok(self.cache.get(&key.service).and_then(|c| {
Ok(self.cache.get(&key.platform).and_then(|c| {
c.get(&key.owner).and_then(|c| {
c.get(&key.repo).and_then(|c| {
c.get(&key.branch)
Expand All @@ -196,8 +196,8 @@ impl Cache<CacheKey, CacheEntry> for InMemoryCache {
}))
}

fn clear(&self, service: FormValue, owner: &str, repo: &str) -> Result<()> {
if let Some(c) = self.cache.get(&service)
fn clear(&self, platform: Platform, owner: &str, repo: &str) -> Result<()> {
if let Some(c) = self.cache.get(&platform)
&& let Some(c) = c.value().get(owner)
{
c.value().remove(repo);
Expand Down Expand Up @@ -244,11 +244,11 @@ impl Cache<CacheKey, CacheEntry> for DiskCache {
Ok(())
}

fn clear(&self, service: FormValue, owner: &str, repo: &str) -> Result<()> {
fn clear(&self, platform: Platform, owner: &str, repo: &str) -> Result<()> {
let cache_dir = self
.settings
.cachedir
.join(service.service())
.join(platform.url_path())
.join(owner)
.join(repo);
remove_dir_all(cache_dir).or_else(|e| {
Expand Down
15 changes: 15 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::path::PathBuf;

use anyhow::Result;
use config::{Config, ConfigError, Environment, File};
use serde::Deserialize;
use tokio::net::TcpListener;

#[derive(Debug, Deserialize, Clone)]
pub struct Settings {
Expand Down Expand Up @@ -38,4 +40,17 @@ impl Settings {
.build()?
.try_deserialize()
}

/// Create a [`TcpListener`] for this config.
///
/// # Errors
///
/// If binding fails
pub async fn listener(&self) -> Result<TcpListener> {
Ok(tokio::net::TcpListener::bind(self.listen_addr()).await?)
}

fn listen_addr(&self) -> String {
format!("{}:{}", self.host, self.port)
}
}
2 changes: 1 addition & 1 deletion src/count.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::{

use tracing::{instrument, trace};

/// The on disk layout for served repos is `<service>/<user>/<repo>`
/// The on disk layout for served repos is `<platform>/<user>/<repo>`
/// so to get the amount of repos, we just have to count everything
/// in `*/*/*` to get the count.
///
Expand Down
117 changes: 30 additions & 87 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,94 +1,37 @@
use crate::{statics::VERSION_INFO, templates};

use std::fmt;

use actix_web::{HttpResponse, ResponseError, http::StatusCode};
use thiserror::Error;

pub(crate) type Result<T> = std::result::Result<T, Error>;

#[derive(Debug)]
#[derive(Error, Debug)]
pub enum Error {
Badge(String),
Client(reqwest::Error),
Git(git2::Error),
#[error("Badge({0})")]
Badge(#[from] badgers::Error),
#[error("Client({0})")]
Client(#[from] reqwest::Error),
#[error("Git({0})")]
Git(#[from] git2::Error),
#[error("RepoInit({0})")]
RepoInit(#[from] gix::init::Error),
#[error("RemoteInit({0})")]
RemoteInit(#[from] gix::remote::init::Error),
#[error("RemoteConfig({0})")]
RemoteConfig(#[from] gix::config::file::set_raw_value::Error),
#[error("RepoConfig({0})")]
RepoConfig(#[from] gix::config::Error),
#[error("RemoteMissing({0})")]
RemoteMissing(#[from] gix::remote::find::existing::Error),
#[error("Internal")]
Internal,
Io(std::io::Error),
Parse(std::num::ParseIntError),
Serial(serde_json::Error),
#[error("Io({0})")]
Io(#[from] std::io::Error),
#[error("Parse({0})")]
Parse(#[from] std::num::ParseIntError),
#[error("Serde({0})")]
Serial(#[from] serde_json::Error),
#[error("BranchNotFound")]
BranchNotFound,
}

impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Badge(s) => write!(fmt, "Badge({s})"),
Error::Client(e) => write!(fmt, "Client({e})"),
Error::Git(e) => write!(fmt, "Git({e})"),
Error::Internal => write!(fmt, "Internal Error"),
Error::Io(e) => write!(fmt, "Io({e})"),
Error::Parse(e) => write!(fmt, "Parse({e})"),
Error::Serial(e) => write!(fmt, "Serial({e})"),
Error::BranchNotFound => write!(fmt, "Repo doesn't have master branch"),
}
}
}

impl ResponseError for Error {
fn status_code(&self) -> StatusCode {
match self {
Error::BranchNotFound => StatusCode::NOT_FOUND,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}

fn error_response(&self) -> HttpResponse {
let mut buf = Vec::new();
if let Error::BranchNotFound = self {
templates::p404_no_master_html(&mut buf, VERSION_INFO, 0).unwrap();
HttpResponse::NotFound().content_type("text/html").body(buf)
} else {
templates::p500_html(&mut buf, VERSION_INFO, 0).unwrap();
HttpResponse::InternalServerError()
.content_type("text/html")
.body(buf)
}
}
}

impl std::error::Error for Error {}

impl From<String> for Error {
fn from(s: String) -> Self {
Error::Badge(s)
}
}

impl From<git2::Error> for Error {
fn from(err: git2::Error) -> Self {
Error::Git(err)
}
}

impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Io(err)
}
}

impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::Serial(err)
}
}

impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self {
Error::Client(err)
}
}

impl From<std::num::ParseIntError> for Error {
fn from(err: std::num::ParseIntError) -> Self {
Error::Parse(err)
}
#[error("UnknownPlatform({0})")]
UnknownPlatform(String),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
Loading
Loading