use std::time::Duration;
use bask::prelude::*;
struct Link {
url: String,
}
struct Fetch {
url: String,
}
struct SeenUrls;
impl Dedup for SeenUrls {
type Key = String;
}
struct Dedupe;
#[async_trait]
impl Worker for Dedupe {
type Task = Link;
async fn process(&self, link: &Link, ctx: &Context) -> anyhow::Result<()> {
if ctx.first_seen::<SeenUrls>(link.url.clone()) {
ctx.emit(Fetch {
url: link.url.clone(),
})
.await?;
}
Ok(())
}
}
struct Proxy {
name: &'static str,
blocks: &'static [&'static str],
}
#[async_trait]
impl Worker for Proxy {
type Task = Fetch;
async fn process(&self, fetch: &Fetch, ctx: &Context) -> anyhow::Result<()> {
if self
.blocks
.iter()
.any(|suffix| fetch.url.ends_with(*suffix))
{
anyhow::bail!("{} cannot reach {}", self.name, fetch.url);
}
ctx.aggregate::<Served>((fetch.url.clone(), self.name.to_string()));
Ok(())
}
}
struct Served;
impl Aggregator for Served {
type Input = (String, String);
type State = Vec<(String, String)>;
type Output = Vec<(String, String)>;
fn fold(state: &mut Self::State, hit: (String, String)) {
state.push(hit);
}
fn merge(left: &mut Self::State, right: Self::State) {
left.extend(right);
}
fn finalize(mut state: Self::State) -> Self::Output {
state.sort();
state
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let backoff = Backoff::Exponential {
base: Duration::from_millis(10),
factor: 2.0,
max: Duration::from_millis(100),
};
let links = ["a.com", "b.ru", "a.com", "c.cn", "d.onion", "b.ru"];
let mut builder = Engine::builder()
.worker(Dedupe)
.worker_cfg(
Proxy {
name: "eu",
blocks: &[".ru", ".onion"],
},
WorkerCfg::new().label("eu"),
)
.worker_cfg(
Proxy {
name: "us",
blocks: &[".cn", ".onion"],
},
WorkerCfg::new().label("us"),
)
.aggregator::<Served>()
.dedup::<SeenUrls>()
.retry(
RetryPolicy::new()
.max_attempts(3)
.avoid_failed()
.backoff(backoff),
)
.concurrency(1);
for url in links {
builder = builder.seed(Link {
url: url.to_string(),
});
}
let report = builder.run().await?;
println!(
"seeded {} links, {} unique urls",
links.len(),
report.unique::<SeenUrls>()
);
println!("served:");
for (url, proxy) in report.output::<Served>().unwrap() {
println!(" {url:8} via {proxy}");
}
println!("\nfailed:");
for failure in &report.failures {
println!(" after {} attempts: {}", failure.attempts, failure.error);
}
println!("\nstats: {:?}", report.stats);
Ok(())
}