apt-mirror-check 1.0.0

Check errors for apt mirror
Documentation
use crate::bad_action::BadAction;
use crate::distribution::{check_distribution};
use regex::Regex;
use std::env::current_dir;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use walkdir::WalkDir;

mod bad_action;
mod distribution;
mod file_attr;
mod packages;
mod release;

pub mod log_config;

pub fn check_repository(repository_dir: &Path, delete: bool) {
    log::info!(repository_dir:?, delete; "start checking repository");

    let bad_action = BadAction::new(delete);

    let dists_dir = repository_dir.join("dists");
    let pool_dir = repository_dir.join("pool");
    if dists_dir.is_dir() && pool_dir.is_dir() {
        // normal repo
        if let Ok(reader) = dists_dir.read_dir() {
            for entry in reader {
                if let Ok(entry) = entry
                    && let Ok(file_type) = entry.file_type()
                    && file_type.is_dir()
                    && !file_type.is_symlink()
                {
                    check_distribution(repository_dir, &entry.path(), &bad_action);
                }
            }
        }
    } else {
        // flat repo, layout files like a distribution
        check_distribution(repository_dir, repository_dir, &bad_action);
    }
}

fn check_site(site_dir: &Path, delete: bool) {
    log::info!(site_dir:?, delete; "start checking site");

    // subdirectories of site may not be a repository
    // for example https://mirrors.tsinghua.edu.cn/raspbian/raspbian/
    //
    // NOTE: flat repository is not supported, please use check_repository directly

    WalkDir::new(site_dir)
        .into_iter()
        .filter_map(|entry| entry.ok())
        .filter(|entry| entry.file_type().is_dir() && entry.file_name().to_str() == Some("dists"))
        .filter_map(|entry| entry.path().parent().map(|p| p.to_path_buf()))
        .for_each(|repository_dir| {
        check_repository(&repository_dir, delete);
    });
}

/// check mirror
///
/// # Arguments
///
/// * `base_dir`: base directory of apt-mirror where mirror/skel/var lives
pub fn check_mirror<P>(base_dir: P, delete: bool)
where
    P: AsRef<Path>,
{
    let base_dir = base_dir.as_ref();
    let mirror_dir = base_dir.join("mirror");

    log::info!(base_dir:?, delete; "start checking mirror");

    match mirror_dir.read_dir() {
        Ok(reader) => {
            for entry in reader {
                if let Ok(entry) = entry
                    && let Ok(file_type) = entry.file_type()
                {
                    if file_type.is_dir() {
                        check_site(&entry.path(), delete);
                    }
                }
            }
        }
        Err(e) => {
            log::error!(mirror_dir:?, e:err; "mirror directory error, maybe wrong base_dir specified");
        }
    }
}

fn find_base_dir_in_apt_mirror_config() -> Option<PathBuf> {
    if let Ok(file) = File::open("/etc/apt/mirror.list") {
        let re = Regex::new(r"set\s+base_path\s+([\w/-]+)").unwrap();

        let reader = BufReader::new(file);

        for line_result in reader.lines() {
            if let Ok(line) = line_result {
                if let Some(m) = re.captures(&line) {
                    return Some(PathBuf::from(m.get(1).unwrap().as_str()));
                }
            }
        }
    }

    None
}

/// user specified -> /etc/apt/mirror.list -> current dir
fn guess_base_dir(base_dir: Option<PathBuf>) -> PathBuf {
    if let Some(base_dir) = base_dir {
        log::info!(base_dir:?; "use user specified base_dir");
        base_dir
    } else if let Some(base_dir) = find_base_dir_in_apt_mirror_config() {
        log::info!(base_dir:?; "use base_dir from /etc/apt/mirror.list");
        base_dir
    } else {
        let base_dir = current_dir().unwrap(); // ignore rare case
        log::info!(base_dir:?; "use current directory as base_dir");
        base_dir
    }
}

pub fn check_mirror_guessed(base_dir: Option<PathBuf>, delete: bool) {
    check_mirror(guess_base_dir(base_dir), delete);
}