1use std::path::PathBuf;
2use serde::{Deserialize, Serialize};
3use std::fs::File;
4use std::io::{Read, Write};
5use crate::destination::routed_destination::RoutedDestination;
6use crate::destination::kinds::file::FileDestination;
7use crate::destination::{MessageDestination, SerializableDestination};
8use crate::message_router::RoutingInfo;
9
10const CONFIG_FILE_NAME: &str = "rnotify.toml";
11
12#[derive(Serialize, Deserialize, Debug)]
13#[serde(deny_unknown_fields)]
14pub struct Config {
15 destinations: Vec<SerializableRoutedDestination>,
16}
17
18#[derive(Debug, Serialize, Deserialize)]
19pub struct SerializableRoutedDestination {
20 id: String,
21 #[serde(flatten)]
23 destination: Box<dyn SerializableDestination>,
24 #[serde(flatten)]
25 routing_info: RoutingInfo,
26}
27
28impl SerializableRoutedDestination {
29
30 pub fn new(id: String, destination: Box<dyn SerializableDestination>, routing_info: RoutingInfo) -> Self {
31 Self {
32 id,
33 destination,
34 routing_info,
35 }
36 }
37
38 pub fn create<D: SerializableDestination + 'static>(id: String, destination: D, routing_info: RoutingInfo) -> Self {
39 Self {
40 id,
41 destination: Box::new(destination),
42 routing_info
43 }
44 }
45}
46
47impl RoutedDestination for SerializableRoutedDestination {
48 fn get_id(&self) -> &str {
49 &self.id
50 }
51
52 fn get_destination(&self) -> &dyn MessageDestination {
53 self.destination.as_message_destination()
54 }
55
56 fn get_routing_info(&self) -> &RoutingInfo {
57 &self.routing_info
58 }
59}
60
61impl Config {
62 pub fn get_destinations(&self) -> &Vec<SerializableRoutedDestination> {
63 &self.destinations
64 }
65
66 pub fn take_destinations(self) -> Vec<SerializableRoutedDestination> {
67 self.destinations
68 }
69
70 fn try_default() -> Result<Self, String> {
71 let log_path = dirs::state_dir()
72 .or_else(|| dirs::home_dir());
73
74 if log_path.is_none() {
75 return Err("Failed to get state directory - if you're on linux, is $HOME set?".to_owned());
76 }
77 let mut log_path = log_path.unwrap();
78 log_path.push("rnotify.log");
79
80 Ok(Self {
81 destinations: vec![
82 SerializableRoutedDestination::create("file_log".to_owned(), FileDestination::new(log_path), RoutingInfo::root()),
83 ]
84 })
85 }
86}
87
88pub fn read_config_file(mut file: File) -> Config {
89 let mut s = String::new();
90 file.read_to_string(&mut s).expect("Failed to read config file.");
91 match toml::from_str(&s) {
92 Ok(c) => c,
93 Err(err) => panic!("Error parsing config file:{}", err),
94 }
95}
96
97pub fn fetch_config_file(verbose: bool, config_file_path_override: &Option<PathBuf>) -> Result<File, String> {
98 if config_file_path_override.is_some() {
99 return File::options().read(true).open(config_file_path_override.as_ref().unwrap())
100 .map_err(|e| format!("Failed to open config file {:?} provided by argument for reading: {}", config_file_path_override, e));
101 }
102
103 let config_dir = dirs::config_dir();
104 if config_dir.is_none() {
105 return Err("Failed to find config directory - if you're on linux, is $HOME set?".to_owned());
106 }
107 let mut path_buf = config_dir.unwrap();
108 path_buf.push(CONFIG_FILE_NAME);
109
110 if verbose {
111 println!("Using config file path: {}", &path_buf.display());
112 }
113
114 if !path_buf.exists() {
115 println!("Config file doesn't exist, creating it ({})", &path_buf.display());
116 let mut file = File::options()
117 .create_new(true)
118 .write(true)
119 .open(&path_buf)
120 .expect("Failed to create new config file to write default config.");
121
122 let default_config = Config::try_default().expect("Failed to create default config");
123 let string = toml::to_string(&default_config)
124 .expect("Failed to serialize default config.");
125 file.write_all(string.as_bytes()).expect("Failed to write default config file to config file.");
126 println!("Created default config file");
127 }
128
129 File::options()
130 .read(true)
131 .open(&path_buf)
132 .map_err(|e| format!("Failed to open config file for reading: {}", e))
133}
134
135#[cfg(test)]
136mod tests {
137 use std::fs;
138 use crate::destination::kinds::discord::DiscordDestination;
139 use crate::destination::routed_destination::{MessageRoutingBehaviour, RoutedDestinationBase};
140 use super::*;
141
142 #[test]
143 fn test_mixed() {
144 let s = fs::read_to_string("test/mixed.toml").expect("Failed to read file");
145 let config: Config = toml::from_str(&s).expect("Failed to deserialize.");
146
147 let file_dest = FileDestination::new(PathBuf::from("/var/log/rnotify.log"));
148 let dsc_dest = DiscordDestination::new("https://discord.com/api/webhooks/11111111111111/2aaaaaaaaaaaaaaaaa".to_owned());
149
150
151 let log_file = RoutedDestinationBase::create("log_file".to_owned(), file_dest, RoutingInfo::root());
152 let dsc = RoutedDestinationBase::create("discord_destination".to_owned(), dsc_dest, RoutingInfo::of(MessageRoutingBehaviour::Additive));
153
154 assert_eq!(config.destinations[0].get_id(), log_file.get_id());
155 assert_eq!(config.destinations[0].get_routing_info(), log_file.get_routing_info());
156
157 assert_eq!(config.destinations[1].get_id(), dsc.get_id());
158 assert_eq!(config.destinations[1].get_routing_info(), dsc.get_routing_info());
159 }
160}