use crate::{
Arguments, Desktop, ENVIRON, Monitor, Orientation, U8Extension, WallSwitchError,
WallSwitchResult, get_feh_path, get_magick_path, get_monitors,
};
use serde::{Deserialize, Serialize};
use std::{
fs::{self, File},
io::{BufReader, BufWriter, Write},
path::{Path, PathBuf},
};
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub desktop: Desktop,
pub directories: Vec<PathBuf>,
pub extensions: Vec<String>,
pub interval: u64,
pub min_dimension: u64,
pub max_dimension: u64,
pub min_size: u64,
pub max_size: u64,
pub monitors: Vec<Monitor>,
pub monitor_orientation: Orientation,
pub once: bool,
pub path_feh: PathBuf,
pub path_magick: PathBuf,
pub sort: bool,
pub verbose: bool,
pub wallpaper: PathBuf,
pub dry_run: bool,
pub transition_type: String,
pub transition_duration: u16,
pub transition_fps: u16,
pub transition_angle: u16,
pub transition_pos: String,
}
impl Default for Config {
fn default() -> Self {
let extensions: Vec<String> = ["avif", "jpg", "jpeg", "png", "svg", "tif", "webp"]
.iter()
.map(ToString::to_string)
.collect();
let interval: u64 = 30 * 60;
let min_dimension: u64 = 600;
let max_dimension: u64 = 128_000;
Config {
desktop: Desktop::detect(),
min_dimension,
max_dimension,
min_size: u64::pow(1024, 1), max_size: u64::pow(1024, 3), directories: get_directories(),
extensions,
interval,
monitors: get_monitors(2),
monitor_orientation: Orientation::Horizontal,
once: false,
path_feh: PathBuf::from("/usr/bin/feh"),
path_magick: PathBuf::from("/usr/bin/magick"),
sort: false,
verbose: false,
wallpaper: get_wallpaper_path(),
dry_run: false,
transition_type: "random".to_string(),
transition_duration: 3,
transition_fps: 30,
transition_angle: 45,
transition_pos: "center".to_string(),
}
}
}
fn config_boundary() -> Config {
Config {
interval: 5,
min_dimension: 10,
min_size: 1,
monitors: vec![Monitor::default()],
..Config::default()
}
}
impl Config {
pub fn new(args: &Arguments) -> WallSwitchResult<Self> {
let mut read_default_config = false;
let config_path: PathBuf = get_config_path()?;
let config: Config = match read_config_file(&config_path) {
Ok(configuration) => configuration,
Err(_) => {
read_default_config = true;
Self::default()
}
}
.set_command_line_arguments(args)?
.validate_config()?
.write_config_file(&config_path, read_default_config)?;
Ok(config)
}
pub fn in_range(&self, value: u64) -> bool {
self.min_dimension <= value && value <= self.max_dimension
}
pub fn print(&self) -> WallSwitchResult<()> {
let json: String = serde_json::to_string_pretty(self)?;
println!("Config:\n{json}\n");
Ok(())
}
fn set_command_line_arguments(mut self, args: &Arguments) -> WallSwitchResult<Self> {
if let Some(min_dimension) = args.min_dimension {
self.min_dimension = min_dimension;
}
if let Some(max_dimension) = args.max_dimension {
self.max_dimension = max_dimension;
}
if let Some(min_size) = args.min_size {
self.min_size = min_size;
}
if let Some(max_size) = args.max_size {
self.max_size = max_size;
}
if let Some(interval) = args.interval {
self.interval = interval;
}
if let Some(monitor) = args.monitor {
self.monitors = get_monitors(monitor.into())
}
if let Some(orientation) = &args.monitor_orientation {
self.monitor_orientation = orientation.clone();
}
if let Some(pictures_per_monitor) = args.pictures_per_monitor {
for monitor in &mut self.monitors {
monitor.pictures_per_monitor = pictures_per_monitor;
}
}
self.once = args.once;
if args.sort {
self.sort = !self.sort;
}
if args.dry_run {
self.dry_run = true;
}
if let Some(ref t) = args.transition_type {
self.transition_type = t.clone();
}
if let Some(d) = args.transition_duration {
self.transition_duration = d;
}
if let Some(f) = args.transition_fps {
self.transition_fps = f;
}
if let Some(a) = args.transition_angle {
self.transition_angle = a;
}
if let Some(ref p) = args.transition_pos {
self.transition_pos = p.clone();
}
if args.verbose {
self.verbose = !self.verbose;
}
self.desktop = Desktop::detect();
Ok(self)
}
pub fn validate_config(mut self) -> WallSwitchResult<Self> {
let boundary: Config = config_boundary();
if self.interval < boundary.interval {
let value = self.interval.to_string();
return Err(WallSwitchError::AtLeastValue {
arg: "--interval".to_string(),
value,
num: boundary.interval,
});
}
if !self.path_feh.is_file() {
self.path_feh = get_feh_path(true)?;
}
if !self.path_magick.is_file() {
self.path_magick = get_magick_path(true)?;
}
if self.min_dimension < boundary.min_dimension {
let value = self.min_dimension.to_string();
return Err(WallSwitchError::AtLeastValue {
arg: "--min_dimension".to_string(),
value,
num: boundary.min_dimension,
});
}
if self.min_size < boundary.min_size {
let value = self.min_size.to_string();
return Err(WallSwitchError::AtLeastValue {
arg: "--min_size".to_string(),
value,
num: boundary.min_size,
});
}
if self.monitors.is_empty() {
let value = self.monitors.len().to_string();
return Err(WallSwitchError::AtLeastValue {
arg: "--interval".to_string(), value,
num: 1,
});
}
for monitor in &self.monitors {
if monitor.pictures_per_monitor < 1 {
let value = monitor.pictures_per_monitor.to_string();
return Err(WallSwitchError::AtLeastValue {
arg: "--picture".to_string(),
value,
num: 1,
});
}
}
if let Some(parent) = self.wallpaper.parent()
&& !parent.exists()
{
fs::create_dir_all(parent)?; }
for (min, max) in [
(self.min_dimension, self.max_dimension),
(self.min_size, self.max_size),
] {
if min > max {
return Err(WallSwitchError::MinMax { min, max });
}
}
Ok(self)
}
pub fn write_config_file(
self,
path: &PathBuf,
read_default_config: bool,
) -> WallSwitchResult<Self> {
if read_default_config {
eprintln!("Create the configuration file: {path:?}\n");
}
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?
};
let file: File = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.map_err(|io_error| {
WallSwitchError::IOError {
path: path.to_path_buf(),
io_error,
}
})?;
let mut writer = BufWriter::new(file);
serde_json::to_writer_pretty(&mut writer, &self)?;
writer.flush()?;
Ok(self)
}
pub fn get_number_of_images(&self) -> usize {
self.monitors
.iter()
.map(|monitor| monitor.pictures_per_monitor.to_usize())
.sum()
}
}
pub fn get_wallpaper_path() -> PathBuf {
let home = ENVIRON.get_home();
let pkg_name = ENVIRON.get_pkg_name();
let mut wallpaper_path: PathBuf = [home, pkg_name].iter().collect();
wallpaper_path.set_extension("jpg");
wallpaper_path
}
pub fn get_directories() -> Vec<PathBuf> {
let home = ENVIRON.get_home(); let images = ["Figures", "Images", "Pictures", "Wallpapers", "Imagens"];
let directories_home: Vec<PathBuf> = images
.into_iter()
.map(|image| Path::new(home).join(image)) .collect();
let sep: &str = std::path::MAIN_SEPARATOR_STR;
let path1: PathBuf = [sep, "usr", "share", "wallpapers"].iter().collect();
let path2: PathBuf = [sep, "usr", "share", "backgrounds"].iter().collect();
let path3: PathBuf = [sep, "tmp", "teste"].iter().collect();
let directories_others: Vec<PathBuf> = vec![path1, path2, path3];
directories_home
.into_iter()
.chain(directories_others)
.collect()
}
pub fn get_config_path() -> WallSwitchResult<PathBuf> {
let home = ENVIRON.get_home();
let hidden_dir = ".config";
let pkg_name = ENVIRON.get_pkg_name();
let mut config_path: PathBuf = [home, hidden_dir, pkg_name, pkg_name].iter().collect();
config_path.set_extension("json");
Ok(config_path)
}
pub fn read_config_file<P>(path: P) -> WallSwitchResult<Config>
where
P: AsRef<Path>,
{
let file = File::open(path)?;
let reader = BufReader::new(file);
let config: Config = serde_json::from_reader(reader)?;
Ok(config)
}