rustpm 0.2.2

A fast, friendly APT frontend with kernel, desktop, and sources management
use anyhow::{bail, Result};
use std::fs;
use std::path::Path;
use std::process::Command;

use super::parser::{parse_all_sources, AptSource};

pub fn add_source(uri: &str, suite: &str, components: &[String]) -> Result<()> {
    let sources = parse_all_sources()?;

    // Check for duplicates
    if sources.iter().any(|s| s.uri == uri && s.suite == suite) {
        bail!("Source already exists: {} {}", uri, suite);
    }

    let line = format!("deb {} {} {}", uri, suite, components.join(" "));
    let fname = uri
        .replace("https://", "")
        .replace("http://", "")
        .replace('/', "-")
        .replace('.', "-");
    let path = format!("/etc/apt/sources.list.d/{}.list", fname.trim_matches('-'));

    fs::write(&path, format!("{}\n", line))?;
    println!("Added source: {}", line);
    println!("Written to: {}", path);

    Ok(())
}

pub fn remove_source(uri: &str) -> Result<()> {
    let sources = parse_all_sources()?;
    let matching: Vec<&AptSource> = sources.iter().filter(|s| s.uri == uri).collect();

    if matching.is_empty() {
        bail!("No source found with URI: {}", uri);
    }

    for source in matching {
        remove_line_from_file(&source.file, source.line, &source.raw_line)?;
        println!("Removed: {} from {}", source.uri, source.file.display());
    }

    Ok(())
}

pub fn enable_source(uri: &str) -> Result<()> {
    toggle_source(uri, true)
}

pub fn disable_source(uri: &str) -> Result<()> {
    toggle_source(uri, false)
}

fn toggle_source(uri: &str, enable: bool) -> Result<()> {
    let sources = parse_all_sources()?;
    let matching: Vec<&AptSource> = sources
        .iter()
        .filter(|s| s.uri == uri && s.enabled != enable)
        .collect();

    if matching.is_empty() {
        let wanted_state = if enable { "disabled" } else { "enabled" };
        bail!("No {} sources found with URI: {}", wanted_state, uri);
    }

    for source in matching {
        let new_line = source.to_line(enable);
        replace_line_in_file(&source.file, source.line, &new_line)?;
        let verb = if enable { "Enabled" } else { "Disabled" };
        println!("{}: {}", verb, source.uri);
    }

    Ok(())
}

fn read_lines(path: &Path) -> Result<Vec<String>> {
    Ok(fs::read_to_string(path)?
        .lines()
        .map(|l| l.to_string())
        .collect())
}

fn replace_line_in_file(path: &Path, line_idx: usize, new_line: &str) -> Result<()> {
    let mut lines = read_lines(path)?;
    if line_idx < lines.len() {
        lines[line_idx] = new_line.to_string();
    }
    fs::write(path, lines.join("\n") + "\n")?;
    Ok(())
}

fn remove_line_from_file(path: &Path, line_idx: usize, expected: &str) -> Result<()> {
    let mut lines = read_lines(path)?;
    if line_idx < lines.len() && lines[line_idx] == expected {
        lines.remove(line_idx);
    }
    fs::write(path, lines.join("\n") + "\n")?;
    Ok(())
}

pub fn edit_sources() -> Result<()> {
    let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nano".to_string());
    let status = Command::new(&editor)
        .arg("/etc/apt/sources.list")
        .status()?;

    if !status.success() {
        bail!("Editor exited with error");
    }

    Ok(())
}