fix: make number conversions safe

This commit is contained in:
monosans
2025-05-05 14:37:27 +03:00
parent 3c78d22f94
commit 1e7fb96dfc
5 changed files with 70 additions and 54 deletions
Generated
+7
View File
@@ -236,6 +236,12 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "castaway"
version = "0.2.3"
@@ -1421,6 +1427,7 @@ dependencies = [
name = "proxy-scraper-checker"
version = "0.1.0"
dependencies = [
"cast",
"color-eyre",
"crossterm",
"derivative",
+1
View File
@@ -6,6 +6,7 @@ license = "MIT"
publish = false
[dependencies]
cast = { version = "0.3.0", features = ["std"] }
color-eyre = "=0.6.4"
crossterm = { version = "=0.28.1", features = [
"event-stream",
+3 -2
View File
@@ -41,8 +41,9 @@ pub async fn check_all(
storage: ProxyStorage,
#[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender<Event>,
) -> color_eyre::Result<ProxyStorage> {
let semaphore =
Arc::new(tokio::sync::Semaphore::new(config.max_concurrent_checks));
let semaphore = Arc::new(tokio::sync::Semaphore::new(
config.max_concurrent_checks.min(tokio::sync::Semaphore::MAX_PERMITS),
));
let mut join_set = tokio::task::JoinSet::new();
for proxy in storage {
let config = Arc::clone(&config);
+3 -4
View File
@@ -173,15 +173,14 @@ impl Config {
let max_concurrent_checks =
match rlimit::increase_nofile_limit(u64::MAX) {
Ok(lim) => {
#[expect(clippy::as_conversions)]
#[expect(clippy::cast_possible_truncation)]
if raw_config.max_concurrent_checks > (lim as usize) {
let lim = cast::usize(lim);
if raw_config.max_concurrent_checks > lim {
log::warn!(
"max_concurrent_checks config value is too high \
for your OS. It will be ignored and {lim} will \
be used."
);
lim as usize
lim
} else {
raw_config.max_concurrent_checks
}
+56 -48
View File
@@ -1,9 +1,5 @@
#![expect(
clippy::arithmetic_side_effects,
clippy::as_conversions,
clippy::cast_precision_loss,
clippy::indexing_slicing,
clippy::integer_division_remainder_used,
clippy::missing_asserts_for_indexing,
clippy::wildcard_enum_match_arm
)]
@@ -124,6 +120,7 @@ async fn tick_event_listener(
) -> Result<(), tokio::sync::mpsc::error::SendError<Event>> {
let mut tick =
tokio::time::interval(tokio::time::Duration::from_secs_f64(1.0 / FPS));
#[expect(clippy::integer_division_remainder_used)]
loop {
tokio::select! {
biased;
@@ -141,6 +138,7 @@ async fn crossterm_event_listener(
tx: tokio::sync::mpsc::UnboundedSender<Event>,
) -> Result<(), tokio::sync::mpsc::error::SendError<Event>> {
let mut reader = crossterm::event::EventStream::new();
#[expect(clippy::integer_division_remainder_used)]
loop {
tokio::select! {
biased;
@@ -198,10 +196,13 @@ fn draw(f: &mut Frame, state: &AppState, logger_state: &TuiWidgetState) {
f.render_widget(
Gauge::default()
.block(Block::bordered().title("GeoDB download"))
.ratio(if state.geodb_total == 0 {
1.0
} else {
(state.geodb_downloaded as f64) / (state.geodb_total as f64)
.ratio({
let total = cast::f64(state.geodb_total);
if total == 0.0 {
1.0
} else {
cast::f64(state.geodb_downloaded) / total
}
}),
outer_layout[1],
);
@@ -231,63 +232,57 @@ fn draw(f: &mut Frame, state: &AppState, logger_state: &TuiWidgetState) {
f.render_widget(
Gauge::default()
.ratio(if sources_total == 0 {
0.0
} else {
sources_scraped as f64 / sources_total as f64
.ratio({
let total = cast::f64(sources_total);
if total == 0.0 {
1.0
} else {
cast::f64(sources_scraped) / total
}
})
.block(Block::bordered().title("Scraping sources"))
.label(format!("{sources_scraped}/{sources_total}")),
layout[0],
);
let proxies_checked =
state.proxies_checked.get(proxy_type).copied().unwrap_or_default();
let proxies_working =
state.proxies_working.get(proxy_type).copied().unwrap_or_default();
let proxies_total =
state.proxies_total.get(proxy_type).copied().unwrap_or_default();
let proxies_checked =
state.proxies_checked.get(proxy_type).copied().unwrap_or_default();
f.render_widget(
Gauge::default()
.ratio(if proxies_total == 0 {
0.0
} else {
proxies_checked as f64 / proxies_total as f64
.ratio({
let total = cast::f64(proxies_total);
if total == 0.0 {
1.0
} else {
cast::f64(proxies_checked) / total
}
})
.block(Block::bordered().title("Checking proxies"))
.label(format!("{proxies_checked}/{proxies_total}")),
layout[1],
);
let working_proxies_block = Block::bordered().title("Working proxies");
f.render_widget(working_proxies_block.clone(), layout[2]);
let proxies_working =
state.proxies_working.get(proxy_type).copied().unwrap_or_default();
f.render_widget(
Gauge::default()
.ratio(if proxies_total == 0 {
0.0
} else {
proxies_checked as f64 / proxies_total as f64
})
.block(
Block::bordered()
.title("Working proxies / checked proxies"),
)
.label(format!(
"{}/{} ({:.1}%)",
proxies_working,
proxies_checked,
if proxies_working != 0 {
(proxies_working as f64 / proxies_checked as f64)
* 100.0_f64
} else {
0.0_f64
}
)),
layout[2],
Line::from(format!(
"{} ({:.1}%)",
proxies_working,
(cast::f64(proxies_working) / cast::f64(proxies_checked))
* 100.0_f64
))
.alignment(Alignment::Center),
working_proxies_block.inner(layout[2]),
);
}
let done = matches!(state.mode, AppMode::Done);
let mut lines = Vec::with_capacity(2 + usize::from(done));
let mut lines = Vec::with_capacity(usize::from(done).saturating_add(2));
lines.push(Line::from("Up/PageUp/k - scroll logs up"));
lines.push(Line::from("Down/PageDown/j - scroll logs down"));
if done {
@@ -353,22 +348,35 @@ async fn handle_event(
state.geodb_total = bytes.unwrap_or_default();
}
AppEvent::GeoDbDownloaded(bytes) => {
state.geodb_downloaded += bytes;
state.geodb_downloaded =
state.geodb_downloaded.saturating_add(bytes);
}
AppEvent::SourcesTotal(proxy_type, amount) => {
state.sources_total.insert(proxy_type, amount);
}
AppEvent::SourceScraped(proxy_type) => {
*state.sources_scraped.entry(proxy_type).or_default() += 1;
state
.sources_scraped
.entry(proxy_type)
.and_modify(|c| *c = c.saturating_add(1))
.or_insert(1);
}
AppEvent::TotalProxies(proxy_type, amount) => {
state.proxies_total.insert(proxy_type, amount);
}
AppEvent::ProxyChecked(proxy_type) => {
*state.proxies_checked.entry(proxy_type).or_default() += 1;
state
.proxies_checked
.entry(proxy_type)
.and_modify(|c| *c = c.saturating_add(1))
.or_insert(1);
}
AppEvent::ProxyWorking(proxy_type) => {
*state.proxies_working.entry(proxy_type).or_default() += 1;
state
.proxies_working
.entry(proxy_type)
.and_modify(|c| *c = c.saturating_add(1))
.or_insert(1);
}
AppEvent::Done => {
state.mode = if is_interactive().await {