1mod fetch;
2mod ui;
3mod welcome;
4mod widgets;
5
6use reqwest::Client;
7use std::{env, time::Duration};
8use tokio::time::{interval, MissedTickBehavior};
9
10use ui::draw_ui;
11
12use fetch::{
13 fetch_filters::{fetch_adguard_filter_list, AdGuardFilteringStatus},
14 fetch_query_log::{fetch_adguard_query_log, Query},
15 fetch_stats::{fetch_adguard_stats, StatsResponse},
16 fetch_status::{fetch_adguard_status, StatusResponse},
17};
18
19async fn fetch_all(
22 client: &Client,
23 hostname: &str,
24 username: &str,
25 password: &str,
26 query_log_limit: u32,
27) -> anyhow::Result<(Vec<Query>, StatsResponse, StatusResponse)> {
28 let queries =
29 fetch_adguard_query_log(client, hostname, username, password, query_log_limit).await?;
30 let stats = fetch_adguard_stats(client, hostname, username, password).await?;
31 let status = fetch_adguard_status(client, hostname, username, password).await?;
32 Ok((queries.data, stats, status))
33}
34
35async fn run() -> anyhow::Result<()> {
36 let timeout_secs: u64 = env::var("ADGUARD_TIMEOUT")
38 .unwrap_or_else(|_| "5".into())
39 .parse::<u64>()?
40 .max(1);
41 let client = Client::builder()
42 .timeout(Duration::from_secs(timeout_secs))
43 .build()?;
44
45 let ip = env::var("ADGUARD_IP")?;
47 let port = env::var("ADGUARD_PORT")?;
48 let protocol = env::var("ADGUARD_PROTOCOL").unwrap_or("http".to_string());
49 let hostname = format!("{}://{}:{}", protocol, ip, port);
50 let username = env::var("ADGUARD_USERNAME")?;
51 let password = env::var("ADGUARD_PASSWORD")?;
52
53 let filters = welcome::with_retries(
55 3,
56 Duration::from_secs(5),
57 "Fetching AdGuard filters",
58 || fetch_adguard_filter_list(&client, &hostname, &username, &password),
59 )
60 .await
61 .unwrap_or_else(|e| {
62 eprintln!("Could not fetch filter list, starting without it: {}", e);
63 AdGuardFilteringStatus { filters: None }
64 });
65
66 let (queries_tx, queries_rx) = tokio::sync::mpsc::channel(1);
68 let (stats_tx, stats_rx) = tokio::sync::mpsc::channel(1);
69 let (status_tx, status_rx) = tokio::sync::mpsc::channel(1);
70
71 let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false);
73
74 let draw_ui_task = tokio::spawn(draw_ui(
76 queries_rx,
77 stats_rx,
78 status_rx,
79 filters,
80 shutdown_tx,
81 ));
82
83 let interval_secs: u64 = env::var("ADGUARD_UPDATE_INTERVAL")
85 .unwrap_or_else(|_| "2".into())
86 .parse::<u64>()?
87 .max(1);
88 let mut interval = interval(Duration::from_secs(interval_secs));
89 interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
90
91 let query_log_limit: u32 = env::var("ADGUARD_QUERYLOG_LIMIT")
93 .unwrap_or_else(|_| "100".into())
94 .parse()?;
95
96 loop {
98 tokio::select! {
99 _ = interval.tick() => {
100 if let Ok((queries, stats, status)) =
102 fetch_all(&client, &hostname, &username, &password, query_log_limit).await
103 {
104 if queries_tx.send(queries).await.is_err()
106 || stats_tx.send(stats).await.is_err()
107 || status_tx.send(status).await.is_err()
108 {
109 break;
110 }
111 }
112 }
113 _ = shutdown_rx.changed() => {
115 break;
116 }
117 }
118 }
119
120 draw_ui_task.await??;
121
122 Ok(())
123}
124
125fn main() {
126 let rt = tokio::runtime::Runtime::new().expect("failed to start async runtime");
127 rt.block_on(async {
128 welcome::welcome().await.unwrap_or_else(|e| {
129 eprintln!("Failed to initialize: {}", e);
130 std::process::exit(1);
131 });
132
133 run()
134 .await
135 .map_err(|e| {
136 eprintln!("Failed to run: {}", e);
137 std::io::Error::other(format!("Failed to run: {}", e))
138 })
139 .unwrap_or_else(|e| {
140 eprintln!("Error: {}", e);
141 std::process::exit(1);
142 });
143 });
144}