use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use anyhow::Result;
use traits::{ArtifactId, ConnectorTrait, RepositoryBackendTrait};
use crate::app::{Tab, ToolsFn};
const CURATED: &[(&str, &str)] = &[
("serde", "1.0.210"),
("serde_json", "1.0.128"),
("tokio", "1.40.0"),
("anyhow", "1.0.89"),
("clap", "4.5.20"),
("regex", "1.11.0"),
("rand", "0.8.5"),
("log", "0.4.22"),
("itertools", "0.13.0"),
("bytes", "1.7.2"),
("once_cell", "1.20.2"),
("thiserror", "1.0.64"),
];
const DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(8);
#[derive(Clone, Debug)]
pub struct DemoPackage {
pub repo: String,
pub name: String,
pub version: String,
pub bytes: usize,
pub real: bool,
}
pub struct DemoFill {
pub repos: Vec<String>,
pub packages: Vec<DemoPackage>,
pub online: bool,
pub routes: Vec<(String, Arc<dyn RepositoryBackendTrait>)>,
}
impl DemoFill {
pub fn real_count(&self) -> usize {
self.packages.iter().filter(|p| p.real).count()
}
pub fn local_holger(&self) -> server_lib::LocalHolger {
server_lib::LocalHolger::new(server_lib::exposed::fast_routes::FastRoutes::new(
self.routes.clone(),
))
}
}
fn synthetic_crate(name: &str, version: &str) -> Vec<u8> {
let header = format!(
"# holger demo offline fixture\n# crate: {name}\n# version: {version}\n# (real download unavailable โ bundled fixture)\n"
);
let mut bytes = header.into_bytes();
let pad = format!("// {name}-{version} padding\n").repeat(64);
bytes.extend_from_slice(pad.as_bytes());
bytes
}
async fn try_download(
conn: &world_connector::WorldConnector,
name: &str,
version: &str,
) -> Option<Vec<u8>> {
let pkg = world_connector::Package {
name: name.to_string(),
version: version.to_string(),
source: Some("registry+https://github.com/rust-lang/crates.io-index".to_string()),
checksum: None,
dependencies: None,
};
let assets = world_connector::packages_to_assets(std::slice::from_ref(&pkg));
let asset = assets.into_iter().next()?;
match tokio::time::timeout(DOWNLOAD_TIMEOUT, conn.download_asset(&asset)).await {
Ok(Ok(bytes)) if !bytes.is_empty() => Some(bytes),
_ => None,
}
}
pub fn fill_demo_store(store_dir: &Path, allow_network: bool) -> Result<DemoFill> {
std::fs::create_dir_all(store_dir)?;
let repo_a = "crates-cache".to_string();
let repo_b = "crates-sparring".to_string();
let backend_a =
znippy_rust_repo::RustRepoZnippy::with_writable_archive(repo_a.clone(), store_dir.join("crates-cache.znippy"));
let backend_b = znippy_rust_repo::RustRepoZnippy::with_writable_archive(
repo_b.clone(),
store_dir.join("crates-sparring.znippy"),
);
let net = if allow_network {
match (
tokio::runtime::Builder::new_current_thread().enable_all().build(),
world_connector::WorldConnector::new(),
) {
(Ok(rt), Ok(conn)) => Some((rt, conn)),
_ => None,
}
} else {
None
};
let mut packages = Vec::new();
let mut online = false;
for (i, (name, version)) in CURATED.iter().enumerate() {
let (repo_name, backend): (&str, &znippy_rust_repo::RustRepoZnippy) =
if i < 6 { (&repo_a, &backend_a) } else { (&repo_b, &backend_b) };
let (bytes, real) = match &net {
Some((rt, conn)) => match rt.block_on(try_download(conn, name, version)) {
Some(b) => {
online = true;
(b, true)
}
None => (synthetic_crate(name, version), false),
},
None => (synthetic_crate(name, version), false),
};
let id = ArtifactId { namespace: None, name: name.to_string(), version: version.to_string() };
backend.put(&id, &bytes)?;
packages.push(DemoPackage {
repo: repo_name.to_string(),
name: name.to_string(),
version: version.to_string(),
bytes: bytes.len(),
real,
});
}
let routes: Vec<(String, Arc<dyn RepositoryBackendTrait>)> = vec![
(repo_a.clone(), Arc::new(backend_a) as Arc<dyn RepositoryBackendTrait>),
(repo_b.clone(), Arc::new(backend_b) as Arc<dyn RepositoryBackendTrait>),
];
Ok(DemoFill { repos: vec![repo_a, repo_b], packages, online, routes })
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum DemoAction {
Goto(Tab),
SelectRepo(usize),
SelectGraphNode(usize),
SelectTool(ToolsFn),
SecurityGraph(bool),
Look(usize),
}
#[derive(Clone, Debug)]
pub(crate) struct DemoStep {
pub at_ms: u64,
pub label: String,
pub action: DemoAction,
}
pub struct DemoWalk {
steps: Vec<DemoStep>,
next: usize,
}
impl DemoWalk {
pub fn tour(beat_ms: u64) -> Self {
let mut b = WalkBuilder::new(beat_ms);
b.go(Tab::Repos, "Repos โ the repository roster");
b.act(DemoAction::SelectRepo(0), "select crates-cache");
b.act(DemoAction::SelectRepo(1), "select crates-sparring");
b.go(Tab::Browse, "Browse โ artifacts in the repo");
b.go(Tab::Archive, "Archive โ raw znippy member files");
b.go(Tab::Artifact, "Artifact โ fetch a package by id");
b.go(Tab::Graph, "Graph โ 3D serverโrepoโartifact graph");
b.act(DemoAction::SelectGraphNode(1), "light a node (neon glow/shockwave)");
b.act(DemoAction::SelectGraphNode(2), "light another subgraph");
b.go(Tab::Helix, "Helix โ time-helix dependency DAG (traverse along time)");
b.act(DemoAction::Look(1), "๐จ re-theme โ light");
b.go(Tab::Tools, "Tools โ operator functions");
b.act(DemoAction::SelectTool(ToolsFn::HolgerDs), "Tool 1 ยท holger-ds");
b.act(DemoAction::SelectTool(ToolsFn::Migrate), "Tool 2 ยท migrate / suck");
b.act(DemoAction::SelectTool(ToolsFn::Security), "Tool 3 ยท security ยท Mรณรฐguรฐr");
b.act(DemoAction::SecurityGraph(true), "Mรณรฐguรฐr โ dependency verdict graph");
b.act(DemoAction::SelectTool(ToolsFn::Deploy), "Tool 4 ยท deploy ยท Skรญรฐblaรฐnir");
b.act(DemoAction::SelectTool(ToolsFn::Airgap), "Tool 5 ยท airgap populate (plan)");
b.act(DemoAction::Look(2), "๐จ re-theme โ device");
b.go(Tab::Upload, "Upload โ writable ingest");
b.go(Tab::Graph, "Graph โ back to the wiring graph");
b.act(DemoAction::SelectGraphNode(0), "light the server anchor");
b.act(DemoAction::Look(0), "๐จ re-theme โ dark");
b.go(Tab::Status, "โฉ Status โ home");
DemoWalk { steps: b.steps, next: 0 }
}
pub fn len(&self) -> usize {
self.steps.len()
}
pub fn is_empty(&self) -> bool {
self.steps.is_empty()
}
pub fn is_done(&self) -> bool {
self.next >= self.steps.len()
}
pub fn duration_ms(&self) -> u64 {
self.steps.last().map(|s| s.at_ms).unwrap_or(0)
}
pub fn rewind(&mut self) {
self.next = 0;
}
#[allow(dead_code)] pub(crate) fn tabs(&self) -> Vec<Tab> {
let mut seen: Vec<Tab> = Vec::new();
for s in &self.steps {
if let DemoAction::Goto(t) = s.action {
if !seen.contains(&t) {
seen.push(t);
}
}
}
seen
}
pub(crate) fn due(&mut self, elapsed_ms: u64) -> Option<DemoStep> {
let step = self.steps.get(self.next)?;
if elapsed_ms >= step.at_ms {
let out = step.clone();
self.next += 1;
Some(out)
} else {
None
}
}
}
struct WalkBuilder {
beat_ms: u64,
t: u64,
steps: Vec<DemoStep>,
}
impl WalkBuilder {
fn new(beat_ms: u64) -> Self {
let beat_ms = beat_ms.max(1);
Self { beat_ms, t: beat_ms, steps: Vec::new() }
}
fn act(&mut self, action: DemoAction, label: &str) {
self.steps.push(DemoStep { at_ms: self.t, label: label.to_string(), action });
self.t += self.beat_ms;
}
fn go(&mut self, tab: Tab, label: &str) {
self.act(DemoAction::Goto(tab), label);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tour_walks_every_tab() {
let w = DemoWalk::tour(1000);
assert!(w.tabs().len() >= 7, "tour visits โฅ7 distinct tabs, got {}", w.tabs().len());
assert!(w.len() >= 15, "the tour is a real script, got {} beats", w.len());
}
#[test]
fn due_is_time_injected_and_fires_once() {
let mut w = DemoWalk::tour(500);
let n = w.len();
assert!(w.due(0).is_none(), "nothing due before the first beat");
let mut fired = 0usize;
for ms in (0..=w.duration_ms() + 10).step_by(25) {
while w.due(ms).is_some() {
fired += 1;
}
}
assert_eq!(fired, n, "every beat fires exactly once across one pass");
assert!(w.is_done());
}
#[test]
fn offline_fill_is_safe_and_populated() {
let dir = std::env::temp_dir().join(format!("holger-demo-fill-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let fill = fill_demo_store(&dir, false).expect("offline fill");
assert_eq!(fill.repos.len(), 2, "two repos");
assert_eq!(fill.packages.len(), CURATED.len(), "every curated pkg landed");
assert!(!fill.online, "offline fill never reaches the network");
assert_eq!(fill.real_count(), 0, "offline โ all synthetic");
let (_, backend) = &fill.routes[0];
let id = ArtifactId { namespace: None, name: "serde".into(), version: "1.0.210".into() };
assert!(backend.fetch(&id).expect("fetch").is_some(), "serde is served from the store");
let _ = std::fs::remove_dir_all(&dir);
}
}