use serde::{Deserialize, Serialize};
use std::{
fs::File,
io::BufReader,
path::Path,
};
use anyhow::Result;
use ron::de::from_reader;
use std::collections::HashMap;
use std::sync::Arc;
use holger_traits::RepositoryBackendTrait;
use crate::exposed::ExposedEndpoint;
use crate::exposed::fast_routes::FastRoutes;
pub use crate::repository::Repository;
pub use crate::storage::StorageEndpoint;
pub mod auth;
pub mod exposed;
pub mod grpc;
pub mod repository;
pub mod storage;
pub fn wire_holger(holger: &mut Holger) -> Result<()> {
let mut repo_map = HashMap::new();
let mut exposed_map = HashMap::new();
let mut storage_map = HashMap::new();
for repo in &*holger.repositories {
repo_map.insert(repo.ron_name.clone(), repo as *const Repository);
}
for exp in &*holger.exposed_endpoints {
exposed_map.insert(exp.ron_name.clone(), exp as *const ExposedEndpoint);
}
for st in &*holger.storage_endpoints {
storage_map.insert(st.ron_name.clone(), st as *const StorageEndpoint);
}
for repo in &mut holger.repositories {
for name in &repo.ron_upstreams {
if let Some(ptr) = repo_map.get(name) {
repo.wired_upstreams.push(*ptr);
} else {
return Err(anyhow::anyhow!("Missing upstream repo: {}", name));
}
}
if let Some(io) = &mut repo.ron_in {
io.wired_storage = *storage_map
.get(&io.ron_storage_endpoint)
.ok_or_else(|| anyhow::anyhow!("Missing storage endpoint: {}", io.ron_storage_endpoint))?;
io.wired_exposed = *exposed_map
.get(&io.ron_exposed_endpoint)
.ok_or_else(|| anyhow::anyhow!("Missing exposed endpoint: {}", io.ron_exposed_endpoint))?;
}
if let Some(io) = &mut repo.ron_out {
io.wired_storage = *storage_map
.get(&io.ron_storage_endpoint)
.ok_or_else(|| anyhow::anyhow!("Missing storage endpoint: {}", io.ron_storage_endpoint))?;
io.wired_exposed = *exposed_map
.get(&io.ron_exposed_endpoint)
.ok_or_else(|| anyhow::anyhow!("Missing exposed endpoint: {}", io.ron_exposed_endpoint))?;
}
}
for exp in &mut holger.exposed_endpoints {
for repo in &holger.repositories {
if let Some(io) = &repo.ron_out {
if io.ron_exposed_endpoint == exp.ron_name {
exp.wired_out_repositories.push(repo as *const Repository);
}
}
}
}
for st in &mut holger.storage_endpoints {
for repo in &holger.repositories {
if let Some(io) = &repo.ron_in {
if io.ron_storage_endpoint == st.ron_name {
st.wired_in_repositories.push(repo as *const Repository);
}
}
if let Some(io) = &repo.ron_out {
if io.ron_storage_endpoint == st.ron_name {
st.wired_out_repositories.push(repo as *const Repository);
}
}
}
}
for exp in &mut holger.exposed_endpoints {
let mut routes: Vec<(String, Arc<dyn RepositoryBackendTrait>)> = Vec::new();
for &repo_ptr in &exp.wired_out_repositories {
if repo_ptr.is_null() { continue; }
let repo: &Repository = unsafe { &*repo_ptr };
if let Some(backend_arc) = &repo.backend_repository {
routes.push((repo.ron_name.clone(), backend_arc.clone()));
}
}
exp.aggregated_routes = if routes.is_empty() {
None
} else {
Some(FastRoutes::new(routes))
};
}
Ok(())
}
#[derive(Serialize, Deserialize)]
pub struct Holger {
pub repositories: Vec<Repository>,
pub exposed_endpoints: Vec<ExposedEndpoint>,
pub storage_endpoints: Vec<StorageEndpoint>,
}
pub fn read_ron_config<P: AsRef<Path>>(path: P) -> Result<Holger> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let holger: Holger = from_reader(reader)?;
Ok(holger)
}
impl Holger {
pub fn fetch(&self, repository: &str, id: &holger_traits::ArtifactId) -> Result<Option<Vec<u8>>> {
let repo = self.get_repo(repository)?;
repo.fetch(id).map_err(Into::into)
}
pub fn put(&self, repository: &str, id: &holger_traits::ArtifactId, data: &[u8]) -> Result<()> {
let repo = self.get_repo(repository)?;
if !repo.is_writable() {
anyhow::bail!("Repository '{}' is read-only", repository);
}
repo.put(id, data).map_err(Into::into)
}
pub fn list_repositories(&self) -> Vec<&str> {
self.repositories.iter().map(|r| r.ron_name.as_str()).collect()
}
fn get_repo(&self, name: &str) -> Result<&Arc<dyn RepositoryBackendTrait>> {
for repo in &self.repositories {
if repo.ron_name == name {
return repo.backend_repository.as_ref()
.ok_or_else(|| anyhow::anyhow!("Repository '{}' has no backend", name));
}
}
anyhow::bail!("Repository '{}' not found", name)
}
pub fn start(&self) -> Result<()> {
for ep in &self.exposed_endpoints {
if let Some(routes) = &ep.aggregated_routes {
self.start_grpc_endpoint(&ep.ron_url, routes.clone())?;
}
}
Ok(())
}
fn start_grpc_endpoint(&self, addr: &str, routes: FastRoutes) -> Result<()> {
use crate::grpc::holger_proto::{
repository_service_server::RepositoryServiceServer,
archive_service_server::ArchiveServiceServer,
admin_service_server::AdminServiceServer,
};
use crate::grpc::HolgerGrpc;
let grpc_state = Arc::new(HolgerGrpc::new(routes));
let listen_addr: std::net::SocketAddr = addr.parse()
.map_err(|e| anyhow::anyhow!("Invalid gRPC address '{}': {}", addr, e))?;
tokio::spawn(async move {
println!("gRPC server listening on {}", listen_addr);
if let Err(e) = tonic::transport::Server::builder()
.add_service(RepositoryServiceServer::new(grpc_state.clone()))
.add_service(ArchiveServiceServer::new(grpc_state.clone()))
.add_service(AdminServiceServer::new(grpc_state.clone()))
.serve(listen_addr)
.await
{
eprintln!("gRPC server error: {}", e);
}
});
Ok(())
}
pub fn instantiate_backends(&mut self) -> Result<()> {
for se in &mut self.storage_endpoints {
se.backend_from_config()?;
}
for repo in &mut self.repositories {
repo.backend_from_config()?;
}
Ok(())
}
}