use std::collections::{HashMap, HashSet};
use color_eyre::eyre::{Result, bail};
use crate::models::channel_config;
use crate::models::channel_configs;
use crate::mirror::types::{Mirror, Mirrors};
use crate::mirror::selection::dump_mirror_performance_stats;
use crate::mirror::url::url2site;
use crate::mirror::logging::load_performance_logs;
use crate::mirror::filtering::{apply_country_code_filtering, filter_mirrors_by_exploration};
fn merge_single_manual_mirror(
all_mirrors_raw: &mut HashMap<String, Mirror>,
url: String,
mut manual_mirror: Mirror,
) {
if let Some(existing_mirror) = all_mirrors_raw.get_mut(&url) {
if !manual_mirror.country_code.is_none() {
existing_mirror.country_code = manual_mirror.country_code.clone();
}
if manual_mirror.top_os.is_some() {
existing_mirror.top_os = manual_mirror.top_os.clone();
}
if !manual_mirror.distro_dirs.is_empty() {
existing_mirror.distro_dirs = manual_mirror.distro_dirs.clone();
}
log::trace!("Merged manual mirror data for {}", url);
} else {
manual_mirror.url = url.clone();
all_mirrors_raw.insert(url.clone(), manual_mirror);
log::trace!("Added new manual mirror: {}", url);
}
}
fn load_and_merge_manual_mirrors(
all_mirrors_raw: &mut HashMap<String, Mirror>,
manual_mirrors_file_path: &std::path::Path,
) -> Result<()> {
if manual_mirrors_file_path.exists() {
log::debug!("Loading manual mirrors from {}", manual_mirrors_file_path.display());
match crate::io::read_json_file::<HashMap<String, Mirror>>(manual_mirrors_file_path) {
Ok(manual_mirrors) => {
log::debug!("Loaded {} manual mirrors", manual_mirrors.len());
for (url, manual_mirror) in manual_mirrors {
merge_single_manual_mirror(all_mirrors_raw, url, manual_mirror);
}
},
Err(_) => { }
}
} else {
log::debug!("manual-mirrors.json not found, skipping manual mirror loading");
}
Ok(())
}
fn convert_mirror_data_structure(all_mirrors_raw: HashMap<String, Mirror>) -> HashMap<String, Mirror> {
let mut all_mirrors: HashMap<String, Mirror> = HashMap::new();
for (url, mut mirror) in all_mirrors_raw {
if let Some(ref top_os) = mirror.top_os {
mirror.distro_dirs.insert(top_os.clone());
}
mirror.url = url.clone();
let site_key = url2site(&url);
all_mirrors.insert(site_key, mirror);
}
all_mirrors
}
fn apply_channel_config_filtering(
all_mirrors: HashMap<String, Mirror>,
) -> Result<HashMap<String, Mirror>> {
let distro = &channel_config().distro;
let mut all_distro_dirs = HashSet::new();
for config in channel_configs() {
all_distro_dirs.extend(config.distro_dirs.iter().cloned());
}
let distro_dirs: Vec<String> = all_distro_dirs.into_iter().collect();
log::debug!("Filtering mirrors for distro='{}', distro_dirs={:?}, total mirrors={}", distro, distro_dirs, all_mirrors.len());
let original_count = all_mirrors.len();
let filtered_mirrors: HashMap<String, Mirror> = all_mirrors
.into_iter()
.filter(|(_, mirror)| {
is_mirror_suitable_for_channel_config(mirror, distro, &distro_dirs)
})
.collect();
log::debug!(
"Filtered mirrors for distro '{}' OR distro_dirs {:?}: {} out of {} mirrors selected",
distro,
distro_dirs,
filtered_mirrors.len(),
original_count
);
Ok(filtered_mirrors)
}
fn load_mirrors_for_distro() -> Result<HashMap<String, Mirror>> {
let manager_path = crate::dirs::get_epkg_src_path();
let mirrors_file_path = crate::dirs::path_join(&manager_path, &["assets", "mirrors", "mirrors.json"]);
let manual_mirrors_file_path = crate::dirs::path_join(&manager_path, &["assets", "mirrors", "manual-mirrors.json"]);
let mut all_mirrors_raw: HashMap<String, Mirror> = crate::io::read_json_file(&mirrors_file_path)?;
log::debug!("Loaded {} raw mirrors from {}", all_mirrors_raw.len(), mirrors_file_path.display());
load_and_merge_manual_mirrors(&mut all_mirrors_raw, &manual_mirrors_file_path)?;
log::debug!("After merging manual mirrors: {} total raw mirrors", all_mirrors_raw.len());
let all_mirrors = convert_mirror_data_structure(all_mirrors_raw);
log::debug!("After converting to site keys: {} mirrors", all_mirrors.len());
let mut filtered_mirrors = apply_channel_config_filtering(all_mirrors)?;
for mirror in filtered_mirrors.values_mut() {
if mirror.stats.score == 0 {
mirror.calculate_performance_score();
}
}
Ok(filtered_mirrors)
}
fn is_mirror_suitable_for_channel_config(mirror: &Mirror, target_distro: &str, target_distro_dirs: &[String]) -> bool {
let has_distro = mirror.top_os.as_ref() == Some(&target_distro.to_string()) || mirror.distro_dirs.contains(&target_distro.to_string());
let has_dirs = target_distro_dirs.iter().any(|required_dir| {
mirror.distro_dirs.contains(required_dir)
});
if !has_distro && !has_dirs {
log::trace!("Mirror {} rejected: top_os={:?}, distro_dirs={:?}, target_distro={}, target_distro_dirs={:?}",
mirror.url, mirror.top_os, mirror.distro_dirs, target_distro, target_distro_dirs);
return false;
}
let arch = &channel_config().arch;
if target_distro == "fedora" {
if arch != "x86_64" && arch != "aarch64" {
return mirror.distro_dirs.iter().any(|dir| dir.contains("secondary") || dir.contains("altarch"));
}
}
if target_distro == "ubuntu" {
if arch != "x86_64" {
return mirror.distro_dirs.iter().any(|dir| dir.contains("ports"));
}
}
log::trace!("Mirror {} accepted: top_os={:?}, distro_dirs={:?}, target_distro={}, target_distro_dirs={:?}",
mirror.url, mirror.top_os, mirror.distro_dirs, target_distro, target_distro_dirs);
true
}
pub(crate) fn initialize_mirrors() -> Result<Mirrors> {
let mirrors = match load_mirrors_for_distro() {
Ok(m) if !m.is_empty() => apply_country_code_filtering(m),
Ok(_empty_map) => {
log::debug!("load_mirrors_for_distro returned empty map for distro '{}'", channel_config().distro);
bail!("Failed to load mirrors for distro '{}'", channel_config().distro);
}
Err(e) => {
bail!("Failed to load mirrors for distro '{}': {}", channel_config().distro, e);
}
};
let mut loaded_mirrors = mirrors;
load_performance_logs(&mut loaded_mirrors);
for mirror in loaded_mirrors.values_mut() {
mirror.calculate_performance_score();
}
filter_mirrors_by_exploration(&mut loaded_mirrors);
let mirrors = Mirrors {
mirrors: loaded_mirrors,
available_mirrors: Vec::new(),
pget_limit: 1,
};
if log::log_enabled!(log::Level::Debug) {
dump_mirror_performance_stats(&mirrors, true);
}
Ok(mirrors)
}