godwit_daemon/config/
mod.rs

1mod internal;
2
3use crate::errors::{PatchError, PurgeBaseError, RevisionError};
4use chrono::prelude::*;
5use getter_derive::Getter;
6use glob::glob;
7use log::info;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::fmt::Display;
11use std::fs::{self, File};
12use std::io::{self, prelude::*};
13use std::path::{Path, PathBuf};
14
15/// Define godwit daemon config.
16#[derive(Debug, Deserialize, Serialize, Getter)]
17#[serde(rename_all = "snake_case")]
18pub struct Config {
19	pub daemon_directory: PathBuf,
20	pub max_threads: usize,
21	pub daemon_url: String,
22	pub client_url: String,
23}
24
25impl Config {
26	/// Returns new settings instance.
27	pub fn init(
28		daemon_directory: Option<PathBuf>,
29		max_threads: Option<usize>,
30		tcp_port: Option<String>,
31	) -> Self {
32		Config {
33			daemon_directory: daemon_directory
34				.unwrap_or_else(|| Config::default().daemon_directory),
35			max_threads: max_threads.unwrap_or_else(|| Config::default().max_threads),
36			daemon_url: tcp_port
37				.clone()
38				.unwrap_or_else(|| Config::default().daemon_url),
39			client_url: tcp_port
40				.clone()
41				.unwrap_or_else(|| Config::default().client_url),
42		}
43	}
44
45	pub fn get_base_path(&self, application: &str, file_name: &str) -> Option<PathBuf> {
46		let base_path = self
47			.daemon_directory
48			.as_path()
49			.join(application)
50			.join(format!("{}.base", &file_name));
51
52		return if base_path.exists() {
53			Some(base_path)
54		} else {
55			None
56		};
57	}
58
59	/// Get patched revision for file of an application
60	pub fn get_patched_rev(
61		&self,
62		application: &str,
63		file_name: &str,
64	) -> Result<Value, RevisionError> {
65		let base_path = self
66			.daemon_directory
67			.as_path()
68			.join(application)
69			.join(format!("{}.base", &file_name));
70
71		if !base_path.exists() {
72			return Err(RevisionError::BaseNotFound {
73				base_path: file_name.to_string(),
74			});
75		}
76
77		let base = fs::read_to_string(&base_path)?;
78
79		let patched = glob(&format!(
80			"{}/*.patch",
81			&self
82				.daemon_directory
83				.as_path()
84				.join(application.to_string())
85				.join("patches")
86				.to_string_lossy(),
87		))?
88		.into_iter()
89		.try_fold(base, |acc, patch_path| {
90			let patch_file = fs::read_to_string(patch_path?)?;
91
92			internal::patch(acc, patch_file)
93		})?;
94
95		Ok(serde_json::from_str(&patched)?)
96	}
97
98	// Save new patch files
99	pub fn save_patch_file<T>(
100		&self,
101		application: &str,
102		file_name: &str,
103		patch_content: T,
104	) -> Result<(), PatchError>
105	where
106		T: Display + Serialize,
107	{
108		if self.get_base_path(application, file_name).is_none() {
109			let patch_path = self
110				.daemon_directory
111				.as_path()
112				.join(application.to_string())
113				.join(format!("{}.base", &file_name));
114
115			fs::create_dir_all(patch_path.parent().unwrap())?;
116			let patch_file = File::create(patch_path)?;
117
118			serde_json::to_writer_pretty(patch_file, &patch_content)?;
119		} else {
120			let patch_path = self
121				.daemon_directory
122				.as_path()
123				.join(application.to_string())
124				.join("patches")
125				.join(&file_name)
126				.join(format!("{}-{}.patch", &file_name, Utc::now()));
127
128			fs::create_dir_all(patch_path.parent().unwrap())?;
129			let mut patch_file = File::create(patch_path)?;
130
131			patch_file.write_all(patch_content.to_string().as_bytes())?;
132		};
133		Ok(())
134	}
135}
136
137impl Default for Config {
138	fn default() -> Self {
139		Config {
140			daemon_directory: dirs::home_dir()
141				.expect("Home couldn't be located in current $PATH variables.")
142				.join(".godwit")
143				.join("daemon"),
144			max_threads: 23,
145			daemon_url: "tcp://*:5555".to_string(),
146			client_url: "tcp://127.0.0.1:5555".to_string(),
147		}
148	}
149}
150
151/// Get settings instance from settings source file.
152pub fn get_config() -> Result<Config, io::Error> {
153	let config_rc_path: PathBuf = dirs::home_dir()
154		.expect("Home couldn't be located in current $PATH variables.")
155		.join(".gdrc")
156		.iter()
157		.collect();
158
159	let config_path: PathBuf = dirs::home_dir()
160		.expect("Home couldn't be located in current $PATH variables.")
161		.join(".godwit")
162		.join("daemon.gwcore")
163		.iter()
164		.collect();
165
166	let config = if config_rc_path.exists() {
167		info!("Daemon config found at {}", config_rc_path.display());
168		File::open(config_rc_path).and_then(|config_file| {
169			let config: Config = serde_json::from_reader(config_file)?;
170			Ok(config)
171		})?
172	} else if config_path.exists() {
173		info!("Daemon config found at {}", config_path.display());
174		File::open(config_path).and_then(|config_file| {
175			let config: Config = serde_json::from_reader(config_file)?;
176			Ok(config)
177		})?
178	} else {
179		Config::default()
180	};
181
182	Ok(config)
183}
184
185pub fn update_patches(
186	application: &str,
187	file_path: &Path,
188	curr_instance: Value,
189) -> Result<(), RevisionError> {
190	let config = get_config()?;
191
192	let file_name = file_path
193		.file_name()
194		.unwrap()
195		.to_string_lossy()
196		.into_owned();
197
198	if config.get_base_path(application, &file_name).is_none() {
199		return config
200			.save_patch_file(application, &file_name, curr_instance)
201			.map_err(Into::into);
202	}
203
204	let prev_instance = config.get_patched_rev(application, &file_name)?;
205
206	let new_patch = internal::diff(prev_instance, curr_instance)?;
207
208	config.save_patch_file(application, &file_name, new_patch)?;
209	Ok(())
210}
211
212pub fn purge_base_file(application: &str, file_path: &Path) -> Result<(), PurgeBaseError> {
213	let file_name = file_path
214		.file_name()
215		.unwrap_or_default()
216		.to_string_lossy()
217		.into_owned();
218
219	if let Some(base_path) = get_config()?.get_base_path(application, &file_name) {
220		fs::remove_dir_all(base_path)?;
221	}
222	Ok(())
223}