diff --git a/src/config.rs b/src/config.rs index d3135b58..f20bbfb5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -18,6 +18,8 @@ use crate::error::Error; use std::cell::LazyCell; use std::collections::HashMap; use std::fmt; +use std::fs; +use std::io::{BufRead, BufReader, Read, Write}; use std::path::PathBuf; use std::str::FromStr; @@ -121,10 +123,45 @@ impl<'a> Config<'a> { Ok(()) } - fn write(&self, _key: &str, _value: ConfigValue, _section: &str) -> Result<(), Error> { - // TODO: Copy the file and parse it manually. If the key exists, edit it in place during - // parsing. If it doesn't, add it at the end of the section. Replace the original file - // atomically. + /// Write config file with the least modification to the original file, and replace the + /// original. + fn write(&self, key: &str, value: ConfigValue, _section: &str) -> Result<(), Error> { + // Copy the file + let tempfile = self.filename.clone().with_extension("tmp"); + fs::copy(self.filename.clone(), tempfile.clone())?; + + { + // Parse the file, get seek offset + let mut file = fs::File::open(tempfile.clone())?; + let mut reader = BufReader::new(file); + let mut line = String::new(); + let mut clear_offset: u64 = 0; + + let linestart = format!("{} ", key); + loop { + let len = reader.read_line(&mut line)?; + if line.trim().starts_with(linestart.as_str()) { + // Found offset + break; + } + clear_offset = clear_offset + u64::try_from(len)?; // This can fail? + } + + // Keep remainder of the file in memory + let mut remainder = String::new(); + reader.read_to_string(&mut remainder); + + let mut file = reader.into_inner(); + + // Clear file after the found offset. If not offset was found, clears after EOF, and + // the remainder is empty. + file.set_len(clear_offset); + file.write(format!("{} = {}\n", key, value).as_bytes()); + file.write(remainder.as_bytes()); + } + + fs::rename(tempfile, self.filename.clone()); + Ok(()) } diff --git a/src/error.rs b/src/error.rs index a9baf6fe..84ed56d9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -18,11 +18,13 @@ use crate::config::ConfigValue; use std::error::Error as StdError; use std::fmt; use std::io; +use std::num::TryFromIntError; use std::path::PathBuf; #[derive(Debug)] pub(crate) enum Error { IOError(io::Error), + IntError(TryFromIntError), UnableToCreateConfigDir, InvalidConfigValueType(ConfigValue), InvalidValueType(String), @@ -33,6 +35,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::IOError(e) => write!(f, "io error: {}", e), + Error::IntError(e) => write!(f, "int error: {}", e), Error::UnableToCreateConfigDir => write!(f, "Unable to create config dir"), Error::InvalidConfigValueType(err) => write!(f, "Invalid ConfigValue type: {}", err), Error::InvalidValueType(err) => write!(f, "Invalid value type: {}", err), @@ -50,3 +53,9 @@ impl From for Error { Error::IOError(err) } } + +impl From for Error { + fn from(err: TryFromIntError) -> Error { + Error::IntError(err) + } +}