refractr/src/config.rs

257 lines
6.3 KiB
Rust

/*
* Copyright 2025 Bryson Steck <me@brysonsteck.xyz>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
use crate::common;
use crate::refractr::Refractr;
use core::fmt;
use serde_derive::Deserialize;
use std::env;
use std::fs::{self, File, Metadata};
use std::io::Read;
use std::path::PathBuf;
use toml;
pub struct ConfigFile {
pub path: String,
pub file: Metadata,
pub config: Config,
}
impl fmt::Display for ConfigFile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut branches_list = String::from("[");
for i in 0..self.config.branches.len() {
branches_list = format!("{}{}", branches_list, &self.config.branches[i]);
if i < self.config.branches.len() - 1 {
branches_list.push_str(", ");
}
}
branches_list.push(']');
let mut to_list = String::from("[");
for i in 0..self.config.to.len() {
to_list.push_str(&self.config.to[i]);
if i < self.config.to.len() - 1 {
to_list.push_str(", ");
}
}
to_list.push(']');
let work_dir_path = match &self.config.work_dir {
None => {
if cfg!(windows) {
format!(
"Using default \"{}\\refractr\"",
match env::var("TEMP") {
Ok(val) => val,
Err(_) => format!("This shouldn't happen!"),
}
)
} else {
format!("Using default: /tmp/refractr")
}
},
Some(path) => format!("{}", path),
};
let schedule_interval = match self.config.schedule.interval {
None => {
if !self.config.schedule.enabled {
String::from("")
} else {
String::from("This shouldn't happen!\n")
}
},
Some(int) => format!(
"\n Scheduled interval in seconds: {}",
int.to_string()
),
};
write!(
f,
"Config file: \"{}\"\n \
Is a file: {}\n \
Read only: {}\n \
Configuration:\n \
Git repo to clone: {}\n \
Git repos to push clone to: {}\n \
Branches of clone to push: {}\n \
Working directory: {}\n \
SSH key for pushing clone: {}\n \
Schedule enabled: {}\
{}",
self.path,
self.file.is_file(),
self.file.permissions().readonly(),
self.config.from,
to_list,
branches_list,
work_dir_path,
self.config.git.ssh_identity_file,
self.config.schedule.enabled,
schedule_interval
)
}
}
#[derive(PartialEq, Deserialize)]
pub struct Config {
pub from: String,
pub to: Vec<String>,
pub branches: Vec<String>,
pub work_dir: Option<String>,
pub git: Git,
pub schedule: Schedule,
}
#[derive(PartialEq, Deserialize)]
pub struct Git {
pub ssh_identity_file: String,
}
#[derive(PartialEq, Deserialize)]
pub struct Schedule {
pub enabled: bool,
pub interval: Option<i32>,
}
pub fn read_config(paths: Vec<PathBuf>, refractr: &Refractr) -> Result<Vec<ConfigFile>, String> {
let mut config_files: Vec<ConfigFile> = vec![];
for path in paths {
common::verbose(
refractr.verbose,
1,
format!(
"Reading config file: \"{}\"",
String::from(path.to_string_lossy())
),
);
let mut data = String::new();
let mut file = match File::open(path.as_path()) {
Err(e) => {
return Err(format!(
"unable to open {}: {}",
path.as_path().display(),
e
))
},
Ok(file) => file,
};
if let Err(e) = file.read_to_string(&mut data) {
return Err(format!(
"unable to read {}: {}",
path.as_path().display(),
e
));
}
let config: Config = match toml::from_str(&data) {
Ok(c) => c,
Err(e) => {
return Err(format!(
"issues parsing toml file {}: {}",
path.as_path().display(),
e
))
},
};
let config_file = ConfigFile {
path: match fs::canonicalize(&path) {
Err(_) => {
return Err(format!(
"cannot get absolute path of config file: {}",
path.as_path().display()
))
},
Ok(abs) => abs.to_string_lossy().to_string(),
},
file: match fs::metadata(&path) {
Err(_) => {
return Err(format!(
"cannot obtain metadata for config file: {}",
path.as_path().display()
))
},
Ok(metadata) => metadata,
},
config: match verify_config(&config) {
Err(e) => {
return Err(format!(
"invalid config {}: {}",
path.as_path().display(),
e
))
},
Ok(_) => config,
},
};
let mut dup = false;
for i in &config_files {
if i.path == config_file.path {
common::warning(format!(
"skipping config file \"{}\" as it was already read",
path.as_path().display()
));
dup = true;
break;
} else if i.config == config_file.config {
common::warning(format!(
"config files \"{}\" and \"{}\" appear to have the same config",
i.path, config_file.path
));
}
}
if !dup {
config_files.push(config_file);
}
}
return Ok(config_files);
}
fn verify_config(config: &Config) -> Result<(), String> {
if config.schedule.enabled {
match config.schedule.interval {
Some(i) => {
if i < 60 {
return Err(format!("schedule is enabled, but less than 60"));
}
},
None => return Err(format!("schedule is enabled, but no interval was defined")),
}
}
match &config.work_dir {
Some(path) => format!("{}", path),
None => {
if cfg!(windows) {
match env::var("TEMP") {
Ok(val) => val,
Err(_) => return Err(format!("cannot determine the default temp dir")),
}
} else {
format!("/tmp/refractr")
}
},
};
if !&config.from.starts_with("ssh://")
&& !&config.from.starts_with("https://")
&& !&config.from.starts_with("http://")
{
return Err(format!("'from' value does not use a supported protocol"));
}
Ok(())
}