Files
lazymc/src/mc/server_properties.rs

182 lines
4.7 KiB
Rust

use std::collections::HashMap;
use std::fs;
use std::path::Path;
/// File name.
pub const FILE: &str = "server.properties";
/// EOL in server.properties file.
const EOL: &str = "\r\n";
/// Try to rewrite changes in server.properties file in dir.
///
/// Prints an error and stops on failure.
pub fn rewrite_dir<P: AsRef<Path>>(dir: P, changes: HashMap<&str, String>) {
if changes.is_empty() {
return;
}
// Ensure directory exists
if !dir.as_ref().is_dir() {
warn!(target: "lazymc",
"Not rewriting {} file, configured server directory doesn't exist: {}",
FILE,
dir.as_ref().to_str().unwrap_or("?")
);
return;
}
// Rewrite file
rewrite_file(dir.as_ref().join(FILE), changes)
}
/// Try to rewrite changes in server.properties file.
///
/// Prints an error and stops on failure.
pub fn rewrite_file<P: AsRef<Path>>(file: P, changes: HashMap<&str, String>) {
if changes.is_empty() {
return;
}
// File must exist
if !file.as_ref().is_file() {
warn!(target: "lazymc",
"Not writing {} file, not found at: {}",
FILE,
file.as_ref().to_str().unwrap_or("?"),
);
return;
}
// Read contents
let contents = match fs::read_to_string(&file) {
Ok(contents) => contents,
Err(err) => {
error!(target: "lazymc",
"Failed to rewrite {} file, could not load: {}",
FILE,
err,
);
return;
}
};
// Rewrite file contents, return if nothing changed
let contents = match rewrite_contents(contents, changes) {
Some(contents) => contents,
None => {
debug!(target: "lazymc",
"Not rewriting {} file, no changes to apply",
FILE,
);
return;
}
};
// Write changes
match fs::write(file, contents) {
Ok(_) => {
info!(target: "lazymc",
"Rewritten {} file with updated values",
FILE,
);
}
Err(err) => {
error!(target: "lazymc",
"Failed to rewrite {} file, could not save changes: {}",
FILE,
err,
);
}
};
}
/// Rewrite file contents with new properties.
///
/// Returns new file contents if anything has changed.
fn rewrite_contents(contents: String, mut changes: HashMap<&str, String>) -> Option<String> {
if changes.is_empty() {
return None;
}
let mut changed = false;
// Build new file
let mut new_contents: String = contents
.lines()
.map(|line| {
let mut line = line.to_owned();
// Skip comments or empty lines
let trim = line.trim();
if trim.starts_with('#') || trim.is_empty() {
return line;
}
// Try to split property
let (key, value) = match line.split_once("=") {
Some(result) => result,
None => return line,
};
// Take any new value, and update it
if let Some((_, new)) = changes.remove_entry(key.trim().to_lowercase().as_str()) {
if value != new {
line = format!("{}={}", key, new);
changed = true;
}
}
line
})
.collect::<Vec<_>>()
.join(EOL);
// Append any missed changes
for (key, value) in changes {
new_contents += &format!("{}{}={}", EOL, key, value);
changed = true;
}
// Return new contents if changed
if changed {
Some(new_contents)
} else {
None
}
}
/// Read the given property from the given server.properties file.o
///
/// Returns `None` if file does not contain the property.
pub fn read_property<P: AsRef<Path>>(file: P, property: &str) -> Option<String> {
// File must exist
if !file.as_ref().is_file() {
warn!(target: "lazymc",
"Failed to read property from {} file, it does not exist",
FILE,
);
return None;
}
// Read contents
let contents = match fs::read_to_string(&file) {
Ok(contents) => contents,
Err(err) => {
error!(target: "lazymc",
"Failed to read property from {} file, could not load: {}",
FILE,
err,
);
return None;
}
};
// Find property, return value
contents
.lines()
.filter_map(|line| line.split_once('='))
.find(|(p, _)| p.trim().to_lowercase() == property.to_lowercase())
.map(|(_, v)| v.trim().to_string())
}