Skip to content

Commit 49f8a17

Browse files
committed
Add --exclude-countries option and fix manjaro mirror parsing
1 parent 9b281e3 commit 49f8a17

File tree

10 files changed

+221
-208
lines changed

10 files changed

+221
-208
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# 0.24.0 (2026-01-16)
2+
3+
- added base option `--exclude-countries` to skip mirrors from certain
4+
countries (comma separated 2-letter ISO country codes)
5+
- fixed manjaro mirror delay calculation (hours were incorrectly treated as minutes)
6+
- fixed manjaro URL handling in mirror parsing
7+
18
# 0.23.0 (2025-12-16)
29

310
- update artix mirrorlist, handling comments, preserving country labels [#83 by Sachin-Bhat](https://github.com/westandskif/rate-mirrors/pull/83/files)

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rate_mirrors"
3-
version = "0.23.0"
3+
version = "0.24.0"
44
authors = ["Nikita Almakov <nikita.almakov@gmail.com>"]
55
edition = "2024"
66
description = "Everyday-use client-side map-aware mirror ranking tool (Arch Linux; Manjaro; custom ones)"

README.md

Lines changed: 145 additions & 184 deletions
Large diffs are not rendered by default.

src/config.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::target_configs::stdin::StdinTarget;
1717
use ambassador::{delegatable_trait, Delegate};
1818
use clap::{Parser, Subcommand};
1919
use itertools::Itertools;
20+
use std::collections::HashSet;
2021
use std::fmt;
2122
use std::str::FromStr;
2223
use std::sync::{mpsc, Arc};
@@ -156,8 +157,7 @@ pub struct Config {
156157
#[arg(env = "RATE_MIRRORS_PROTOCOL", long = "protocol", name = "protocol")]
157158
pub protocols: Vec<Protocol>,
158159

159-
/// Per-mirror speed test timeout in milliseconds. It is doubled in cases where slow connection
160-
/// times are detected
160+
/// Per-mirror speed test timeout in milliseconds
161161
#[arg(env = "RATE_MIRRORS_PER_MIRROR_TIMEOUT", long, default_value = "8000")]
162162
pub per_mirror_timeout: u64,
163163

@@ -227,6 +227,15 @@ pub struct Config {
227227
)]
228228
pub entry_country: String,
229229

230+
/// Exclude countries from mirror selection (comma-separated 2-letter ISO country codes).
231+
#[arg(
232+
env = "RATE_MIRRORS_EXCLUDE_COUNTRIES",
233+
long = "exclude-countries",
234+
name = "country-codes",
235+
verbatim_doc_comment
236+
)]
237+
pub exclude_countries: Option<String>,
238+
230239
/// Neighbor country to test per country
231240
#[arg(
232241
env = "RATE_MIRRORS_COUNTRY_NEIGHBORS_PER_COUNTRY",
@@ -270,13 +279,37 @@ pub struct Config {
270279
/// Disable printing comments to output file
271280
#[arg(env = "RATE_MIRRORS_DISABLE_COMMENTS_IN_FILE", long)]
272281
pub disable_comments_in_file: bool,
282+
283+
/// Pre-parsed set of excluded country codes (lowercase)
284+
#[arg(skip)]
285+
pub excluded_countries_set: HashSet<String>,
273286
}
274287

275288
impl Config {
289+
pub fn new() -> Self {
290+
let mut config = Self::parse();
291+
config.excluded_countries_set = config
292+
.exclude_countries
293+
.as_ref()
294+
.map(|s| {
295+
s.split(',')
296+
.map(|c| c.trim().to_ascii_lowercase())
297+
.filter(|c| !c.is_empty())
298+
.collect()
299+
})
300+
.unwrap_or_default();
301+
config
302+
}
303+
276304
pub fn is_protocol_allowed(&self, protocol: &Protocol) -> bool {
277305
self.protocols.is_empty() || self.protocols.contains(protocol)
278306
}
279307

308+
pub fn is_country_excluded(&self, code: &str) -> bool {
309+
self.excluded_countries_set
310+
.contains(&code.to_ascii_lowercase())
311+
}
312+
280313
pub fn is_protocol_allowed_for_url(&self, url: &Url) -> bool {
281314
self.protocols.is_empty()
282315
|| url

src/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ mod targets;
1111
use crate::config::{AppError, Config, FetchMirrors};
1212
use crate::speed_test::{test_speed_by_countries, SpeedTestResult, SpeedTestResults};
1313
use chrono::prelude::*;
14-
use clap::Parser;
1514
use config::LogFormatter;
1615
use itertools::Itertools;
1716
use mirror::Mirror;
@@ -92,7 +91,7 @@ impl<'a, T: LogFormatter> OutputSink<'a, T> {
9291
}
9392

9493
fn main() -> Result<(), AppError> {
95-
let config = Arc::new(Config::parse());
94+
let config = Arc::new(Config::new());
9695
if !config.allow_root && Uid::effective().is_root() {
9796
return Err(AppError::Root);
9897
}

src/speed_test.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::convert::From;
1313
use std::fmt;
1414
use std::fmt::Debug;
1515
use std::sync::mpsc::Sender;
16-
use std::sync::{mpsc, Arc};
16+
use std::sync::{Arc, mpsc};
1717
use std::time::{Duration, Instant};
1818

1919
use tokio::runtime::Runtime;
@@ -290,9 +290,10 @@ pub fn test_speed_by_countries(
290290
let mut unlabeled_mirrors: Vec<Mirror> = Vec::new();
291291
for mirror in mirrors.into_iter() {
292292
match mirror.country {
293-
Some(country) => {
293+
Some(country) if !config.is_country_excluded(country.code) => {
294294
map.entry(country).or_insert_with(Vec::new).push(mirror);
295295
}
296+
Some(_) => {}
296297
None => {
297298
unlabeled_mirrors.push(mirror);
298299
}
@@ -360,7 +361,11 @@ pub fn test_speed_by_countries(
360361
};
361362

362363
let mut links: Vec<_> = if !explored {
363-
country.links.iter().collect()
364+
country
365+
.links
366+
.iter()
367+
.filter(|link| !config.is_country_excluded(link.code))
368+
.collect()
364369
} else {
365370
Vec::new()
366371
};
@@ -441,7 +446,8 @@ pub fn test_speed_by_countries(
441446
tx_progress
442447
.send(format!(
443448
" TOP NEIGHBOR - CONNECTION TIME: {} - {}",
444-
top_country.code, result.fmt_connection_time(),
449+
top_country.code,
450+
result.fmt_connection_time(),
445451
))
446452
.unwrap();
447453
countries_to_check.push(top_country);
@@ -451,7 +457,8 @@ pub fn test_speed_by_countries(
451457
tx_progress
452458
.send(format!(
453459
" TOP CONNECTION TIME: {} - {}",
454-
top_country.code, result.fmt_connection_time(),
460+
top_country.code,
461+
result.fmt_connection_time(),
455462
))
456463
.unwrap();
457464
latest_top_connection_times.push(result.connection_time);

src/targets/endeavouros.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ impl FetchMirrors for EndeavourOSTarget {
121121
})?
122122
} else {
123123
fs::read_to_string(self.mirror_list_file.as_str())
124-
.expect("failed to read from mirror-list-file")
124+
.map_err(|e| AppError::RequestError(format!("failed to read mirror-list-file: {}", e)))?
125125
};
126126

127127
let mut current_country = None;

src/targets/manjaro.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ where
3737
if let Ok(value) = String::deserialize(deserializer) {
3838
if let Some((h, m)) = value.split_once(":") {
3939
if let (Ok(h), Ok(m)) = (h.parse::<i64>(), m.parse::<i64>()) {
40-
return Ok(Some(h * 60 + m));
40+
return Ok(Some(h * 3600 + m * 60));
4141
}
4242
}
4343
};
@@ -108,7 +108,7 @@ impl FetchMirrors for ManjaroTarget {
108108
.and_then(|u| u.join(&self.path_to_test))
109109
.map(|url_to_test| Mirror {
110110
country: Country::from_str(&m.country),
111-
url: m.url,
111+
url,
112112
url_to_test,
113113
})
114114
.ok()

src/targets/stdin.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,23 @@ impl FetchMirrors for StdinTarget {
3333
.lock()
3434
.lines()
3535
.filter_map(
36-
|line| match MirrorInfo::parse(&line.unwrap(), &self.separator) {
37-
Ok(info) => Some(Mirror {
38-
country: info.country,
39-
url_to_test: info
40-
.url
41-
.join(&self.path_to_test)
42-
.expect("failed to join path-to-test"),
43-
url: info.url,
44-
}),
36+
|line| match line {
37+
Ok(line) => match MirrorInfo::parse(&line, &self.separator) {
38+
Ok(info) => Some(Mirror {
39+
country: info.country,
40+
url_to_test: info
41+
.url
42+
.join(&self.path_to_test)
43+
.expect("failed to join path-to-test"),
44+
url: info.url,
45+
}),
46+
Err(err) => {
47+
eprintln!("{}", err);
48+
None
49+
}
50+
},
4551
Err(err) => {
46-
eprintln!("{}", err);
52+
eprintln!("failed to read line: {}", err);
4753
None
4854
}
4955
},

0 commit comments

Comments
 (0)