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()?;
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 state = if enable { "enabled" } else { "disabled" };
bail!("No {} sources found with URI: {}", if enable { "disabled" } else { "enabled" }, uri);
let _ = state;
}
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(())
}