diff --git a/ffi/src/helpers.rs b/ffi/src/helpers.rs index 4295fd5..06e302b 100644 --- a/ffi/src/helpers.rs +++ b/ffi/src/helpers.rs @@ -54,19 +54,19 @@ fn map_error(err: &Error) -> c_uint { use Error::*; match *err { InvalidPath(_) => LIBLO_ERROR_FILE_NOT_FOUND, - IoError(ref x) => map_io_error(x), + IoError(_, ref x) => map_io_error(x), NoFilename => LIBLO_ERROR_FILE_PARSE_FAIL, SystemTimeError(_) => LIBLO_ERROR_TIMESTAMP_WRITE_FAIL, NotUtf8(_) => LIBLO_ERROR_FILE_NOT_UTF8, DecodeError(_) => LIBLO_ERROR_TEXT_DECODE_FAIL, EncodeError(_) => LIBLO_ERROR_TEXT_ENCODE_FAIL, - PluginParsingError => LIBLO_ERROR_FILE_PARSE_FAIL, + PluginParsingError(_) => LIBLO_ERROR_FILE_PARSE_FAIL, PluginNotFound(_) => LIBLO_ERROR_INVALID_ARGS, TooManyActivePlugins => LIBLO_ERROR_INVALID_ARGS, InvalidRegex => LIBLO_ERROR_INTERNAL_LOGIC_ERROR, - DuplicatePlugin => LIBLO_ERROR_INVALID_ARGS, - NonMasterBeforeMaster => LIBLO_ERROR_INVALID_ARGS, - GameMasterMustLoadFirst => LIBLO_ERROR_INVALID_ARGS, + DuplicatePlugin(_) => LIBLO_ERROR_INVALID_ARGS, + NonMasterBeforeMaster { .. } => LIBLO_ERROR_INVALID_ARGS, + GameMasterMustLoadFirst(_) => LIBLO_ERROR_INVALID_ARGS, InvalidEarlyLoadingPluginPosition { .. } => LIBLO_ERROR_INVALID_ARGS, InvalidPlugin(_) => LIBLO_ERROR_INVALID_ARGS, ImplicitlyActivePlugin(_) => LIBLO_ERROR_INVALID_ARGS, @@ -75,7 +75,7 @@ fn map_error(err: &Error) -> c_uint { UnrepresentedHoist { .. } => LIBLO_ERROR_INVALID_ARGS, InstalledPlugin(_) => LIBLO_ERROR_INVALID_ARGS, IniParsingError { .. } => LIBLO_ERROR_FILE_PARSE_FAIL, - VdfParsingError(_) => LIBLO_ERROR_FILE_PARSE_FAIL, + VdfParsingError(_, _) => LIBLO_ERROR_FILE_PARSE_FAIL, SystemError(_, _) => LIBLO_ERROR_SYSTEM_ERROR, } } diff --git a/src/enums.rs b/src/enums.rs index 58bd9e9..106ea1f 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -77,19 +77,22 @@ impl GameId { #[derive(Debug)] pub enum Error { InvalidPath(PathBuf), - IoError(io::Error), + IoError(PathBuf, io::Error), NoFilename, SystemTimeError(time::SystemTimeError), NotUtf8(Vec), DecodeError(Cow<'static, str>), EncodeError(Cow<'static, str>), - PluginParsingError, + PluginParsingError(PathBuf), PluginNotFound(String), TooManyActivePlugins, InvalidRegex, - DuplicatePlugin, - NonMasterBeforeMaster, - GameMasterMustLoadFirst, + DuplicatePlugin(String), + NonMasterBeforeMaster { + master: String, + non_master: String, + }, + GameMasterMustLoadFirst(String), InvalidEarlyLoadingPluginPosition { name: String, pos: usize, @@ -105,20 +108,15 @@ pub enum Error { }, InstalledPlugin(String), IniParsingError { + path: PathBuf, line: usize, column: usize, message: String, }, - VdfParsingError(String), + VdfParsingError(PathBuf, String), SystemError(i32, OsString), } -impl From for Error { - fn from(error: io::Error) -> Self { - Error::IoError(error) - } -} - impl From for Error { fn from(error: time::SystemTimeError) -> Self { Error::SystemTimeError(error) @@ -137,49 +135,6 @@ impl From for Error { } } -impl From for Error { - fn from(error: esplugin::Error) -> Self { - match error { - esplugin::Error::IoError(x) => Error::IoError(x), - esplugin::Error::NoFilename => Error::NoFilename, - esplugin::Error::ParsingIncomplete | esplugin::Error::ParsingError(_, _) => { - Error::PluginParsingError - } - esplugin::Error::DecodeError => Error::DecodeError("invalid sequence".into()), - } - } -} - -impl From for Error { - fn from(error: ini::Error) -> Self { - match error { - ini::Error::Io(x) => Error::IoError(x), - ini::Error::Parse(x) => Error::from(x), - } - } -} - -impl From for Error { - fn from(error: ini::ParseError) -> Self { - Error::IniParsingError { - line: error.line, - column: error.col, - message: error.msg, - } - } -} - -impl From for Error { - fn from(error: keyvalues_parser::error::Error) -> Self { - match error { - keyvalues_parser::error::Error::ParseError(e) => Error::VdfParsingError(e.to_string()), - keyvalues_parser::error::Error::InvalidTokenStream(c) => { - Error::VdfParsingError(c.to_string()) - } - } - } -} - #[cfg(windows)] impl From for Error { fn from(error: windows::core::Error) -> Self { @@ -191,14 +146,14 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::InvalidPath(ref x) => write!(f, "The path \"{:?}\" is invalid", x), - Error::IoError(ref x) => x.fmt(f), + Error::IoError(ref p, ref x) => write!(f, "I/O error involving the path \"{:?}\": {}", p, x), Error::NoFilename => write!(f, "The plugin path has no filename part"), Error::SystemTimeError(ref x) => x.fmt(f), Error::NotUtf8(ref x) => write!(f, "Expected a UTF-8 string, got bytes {:?}", x), Error::DecodeError(_) => write!(f, "Text could not be decoded from Windows-1252"), Error::EncodeError(_) => write!(f, "Text could not be encoded in Windows-1252"), - Error::PluginParsingError => { - write!(f, "An error was encountered while parsing a plugin") + Error::PluginParsingError(ref p) => { + write!(f, "An error was encountered while parsing the plugin at \"{:?}\"", p) } Error::PluginNotFound(ref x) => { write!(f, "The plugin \"{}\" is not in the load order", x) @@ -208,18 +163,18 @@ impl fmt::Display for Error { f, "Internal error: regex is invalid" ), - Error::DuplicatePlugin => write!(f, "The given plugin list contains duplicates"), - Error::NonMasterBeforeMaster => write!( + Error::DuplicatePlugin(ref n) => write!(f, "The given plugin list contains more than one instance of \"{}\"", n), + Error::NonMasterBeforeMaster{ref master, ref non_master} => write!( f, - "Attempted to load a non-master plugin before a master plugin" + "Attempted to load the non-master plugin \"{}\" before the master plugin \"{}\"", non_master, master ), - Error::GameMasterMustLoadFirst => write!( + Error::GameMasterMustLoadFirst(ref n) => write!( f, - "The game's master file must load first" + "The game's master file \"{}\" must load first", n ), Error::InvalidEarlyLoadingPluginPosition{ ref name, pos, expected_pos } => write!( f, - "Attempted to load the early-loading plugin {} at position {}, its expected position is {}", name, pos, expected_pos + "Attempted to load the early-loading plugin \"{}\" at position {}, its expected position is {}", name, pos, expected_pos ), Error::InvalidPlugin(ref x) => write!(f, "The plugin file \"{}\" is invalid", x), Error::ImplicitlyActivePlugin(ref x) => write!( @@ -244,15 +199,16 @@ impl fmt::Display for Error { plugin ), Error::IniParsingError { + ref path, line, column, ref message, } => write!( f, - "Failed to parse ini file, error at line {}, column {}: {}", - line, column, message + "Failed to parse ini file at \"{:?}\", error at line {}, column {}: {}", + path, line, column, message ), - Error::VdfParsingError(ref message) => write!(f, "Failed to parse VDF file: {}", message), + Error::VdfParsingError(ref path, ref message) => write!(f, "Failed to parse VDF file at \"{:?}\": {}", path, message), Error::SystemError(code, ref message) => write!(f, "Error returned by the operating system, code {}: {:?}", code, message), } } @@ -261,7 +217,7 @@ impl fmt::Display for Error { impl error::Error for Error { fn cause(&self) -> Option<&dyn error::Error> { match *self { - Error::IoError(ref x) => Some(x), + Error::IoError(_, ref x) => Some(x), Error::SystemTimeError(ref x) => Some(x), _ => None, } diff --git a/src/game_settings.rs b/src/game_settings.rs index c020339..a8efd00 100644 --- a/src/game_settings.rs +++ b/src/game_settings.rs @@ -509,7 +509,8 @@ fn find_nam_plugins(plugins_path: &Path) -> Result, Error> { } let dir_iter = plugins_path - .read_dir()? + .read_dir() + .map_err(|e| Error::IoError(plugins_path.to_path_buf(), e))? .filter_map(Result::ok) .filter(|e| e.file_type().map(|f| f.is_file()).unwrap_or(false)) .filter(|e| { @@ -554,7 +555,8 @@ fn early_loading_plugins( if let Some(file_path) = ccc_file_path(game_id, game_path) { if file_path.exists() { - let reader = BufReader::new(File::open(file_path)?); + let reader = + BufReader::new(File::open(&file_path).map_err(|e| Error::IoError(file_path, e))?); let lines = reader .lines() diff --git a/src/ghostable_path.rs b/src/ghostable_path.rs index ede948a..9a15f7a 100644 --- a/src/ghostable_path.rs +++ b/src/ghostable_path.rs @@ -41,7 +41,7 @@ impl GhostablePath for Path { Ok(self.to_path_buf()) } else { let new_path = self.as_ghosted_path()?; - rename(self, &new_path)?; + rename(self, &new_path).map_err(|e| Error::IoError(self.to_path_buf(), e))?; Ok(new_path) } } @@ -51,7 +51,7 @@ impl GhostablePath for Path { Ok(self.to_path_buf()) } else { let new_path = self.as_unghosted_path()?; - rename(self, &new_path)?; + rename(self, &new_path).map_err(|e| Error::IoError(self.to_path_buf(), e))?; Ok(new_path) } } diff --git a/src/ini.rs b/src/ini.rs index c9d5f48..9c649da 100644 --- a/src/ini.rs +++ b/src/ini.rs @@ -28,12 +28,18 @@ type TestFiles = [Option; 10]; fn read_ini(ini_path: &Path) -> Result { // Read ini as Windows-1252 bytes and then convert to UTF-8 before parsing, // as the ini crate expects the content to be valid UTF-8. - let contents = std::fs::read(ini_path)?; + let contents = + std::fs::read(ini_path).map_err(|e| Error::IoError(ini_path.to_path_buf(), e))?; // My Games is used if bUseMyGamesDirectory is not present or set to 1. let contents = WINDOWS_1252.decode_without_bom_handling(&contents).0; - ini::Ini::load_from_str(&contents).map_err(Error::from) + ini::Ini::load_from_str(&contents).map_err(|e| Error::IniParsingError { + path: ini_path.to_path_buf(), + line: e.line, + column: e.col, + message: e.msg, + }) } pub fn use_my_games_directory(ini_path: &Path) -> Result { @@ -189,9 +195,18 @@ fn starfield_language(game_path: &Path) -> Result<&'static str, Error> { } fn read_steam_language_config(appmanifest_acf_path: &Path) -> Result, Error> { - let content = std::fs::read_to_string(appmanifest_acf_path)?; + let content = std::fs::read_to_string(appmanifest_acf_path) + .map_err(|e| Error::IoError(appmanifest_acf_path.to_path_buf(), e))?; + + let language = keyvalues_parser::Vdf::parse(&content) + .map_err(|e| { + let detail = match e { + keyvalues_parser::error::Error::ParseError(e) => e.to_string(), + keyvalues_parser::error::Error::InvalidTokenStream(c) => c.to_string(), + }; - let language = keyvalues_parser::Vdf::parse(&content)? + Error::VdfParsingError(appmanifest_acf_path.to_path_buf(), detail) + })? .value .get_obj() .and_then(|o| o.get("UserConfig")) diff --git a/src/load_order/asterisk_based.rs b/src/load_order/asterisk_based.rs index 37cb8ee..26b3126 100644 --- a/src/load_order/asterisk_based.rs +++ b/src/load_order/asterisk_based.rs @@ -123,9 +123,10 @@ impl WritableLoadOrder for AsteriskBasedLoadOrder { } fn save(&mut self) -> Result<(), Error> { - create_parent_dirs(self.game_settings().active_plugins_file())?; + let path = self.game_settings().active_plugins_file(); + create_parent_dirs(path)?; - let file = File::create(self.game_settings().active_plugins_file())?; + let file = File::create(path).map_err(|e| Error::IoError(path.clone(), e))?; let mut writer = BufWriter::new(file); for plugin in self.plugins() { if self.game_settings().loads_early(plugin.name()) { @@ -135,10 +136,12 @@ impl WritableLoadOrder for AsteriskBasedLoadOrder { } if plugin.is_active() { - write!(writer, "*")?; + write!(writer, "*").map_err(|e| Error::IoError(path.clone(), e))?; } - writer.write_all(&strict_encode(plugin.name())?)?; - writeln!(writer)?; + writer + .write_all(&strict_encode(plugin.name())?) + .map_err(|e| Error::IoError(path.clone(), e))?; + writeln!(writer).map_err(|e| Error::IoError(path.clone(), e))?; } if self.ignore_active_plugins_file() { @@ -161,12 +164,14 @@ impl WritableLoadOrder for AsteriskBasedLoadOrder { } fn set_load_order(&mut self, plugin_names: &[&str]) -> Result<(), Error> { + let game_master_file = self.game_settings().master_file(); + let is_game_master_first = plugin_names .first() - .map(|name| eq(*name, self.game_settings().master_file())) + .map(|name| eq(*name, game_master_file)) .unwrap_or(false); if !is_game_master_first { - return Err(Error::GameMasterMustLoadFirst); + return Err(Error::GameMasterMustLoadFirst(game_master_file.to_string())); } // Check that all early loading plugins that are present load in @@ -197,14 +202,13 @@ impl WritableLoadOrder for AsteriskBasedLoadOrder { } fn set_plugin_index(&mut self, plugin_name: &str, position: usize) -> Result { - if position != 0 - && !self.plugins().is_empty() - && eq(plugin_name, self.game_settings().master_file()) - { - return Err(Error::GameMasterMustLoadFirst); + let game_master_file = self.game_settings().master_file(); + + if position != 0 && !self.plugins().is_empty() && eq(plugin_name, game_master_file) { + return Err(Error::GameMasterMustLoadFirst(game_master_file.to_string())); } - if position == 0 && !eq(plugin_name, self.game_settings().master_file()) { - return Err(Error::GameMasterMustLoadFirst); + if position == 0 && !eq(plugin_name, game_master_file) { + return Err(Error::GameMasterMustLoadFirst(game_master_file.to_string())); } self.move_or_insert_plugin_with_index(plugin_name, position) diff --git a/src/load_order/mutable.rs b/src/load_order/mutable.rs index c07529c..dcf24fd 100644 --- a/src/load_order/mutable.rs +++ b/src/load_order/mutable.rs @@ -101,8 +101,14 @@ pub trait MutableLoadOrder: ReadableLoadOrder + ReadableLoadOrderBase + Sync { } fn replace_plugins(&mut self, plugin_names: &[&str]) -> Result<(), Error> { - if !are_plugin_names_unique(plugin_names) { - return Err(Error::DuplicatePlugin); + let mut unique_plugin_names = HashSet::new(); + + let non_unique_plugin = plugin_names + .iter() + .find(|n| !unique_plugin_names.insert(UniCase::new(*n))); + + if let Some(n) = non_unique_plugin { + return Err(Error::DuplicatePlugin(n.to_string())); } let mut plugins = match map_to_plugins(self, plugin_names) { @@ -178,7 +184,8 @@ where return Ok(Vec::new()); } - let content = std::fs::read(file_path)?; + let content = + std::fs::read(file_path).map_err(|e| Error::IoError(file_path.to_path_buf(), e))?; // This should never fail, as although Windows-1252 has a few unused bytes // they get mapped to C1 control characters. @@ -315,12 +322,15 @@ fn validate_master_file_index( // Check that all of the plugins that load between this index and // the previous plugin are masters of this plugin. - if preceding_plugins + if let Some(n) = preceding_plugins .iter() .skip(previous_master_pos + 1) - .any(|p| !master_names.contains(&UniCase::new(p.name()))) + .find(|p| !master_names.contains(&UniCase::new(p.name()))) { - return Err(Error::NonMasterBeforeMaster); + return Err(Error::NonMasterBeforeMaster { + master: plugin.name().to_string(), + non_master: n.name().to_string(), + }); } // Check that none of the non-masters that load after index are @@ -373,7 +383,10 @@ fn validate_non_master_file_index( { Ok(()) } else { - Err(Error::NonMasterBeforeMaster) + Err(Error::NonMasterBeforeMaster { + master: next_master.name().to_string(), + non_master: plugin.name().to_string(), + }) } } @@ -441,13 +454,6 @@ fn get_plugin_to_insert_at( } } -fn are_plugin_names_unique(plugin_names: &[&str]) -> bool { - let unique_plugin_names: HashSet<_> = - plugin_names.par_iter().map(|s| UniCase::new(s)).collect(); - - unique_plugin_names.len() == plugin_names.len() -} - fn validate_load_order(plugins: &[Plugin]) -> Result<(), Error> { let first_non_master_pos = match find_first_non_master_position(plugins) { None => return Ok(()), @@ -478,8 +484,11 @@ fn validate_load_order(plugins: &[Plugin]) -> Result<(), Error> { plugin_names.remove(&UniCase::new(master.clone())); } - if !plugin_names.is_empty() { - return Err(Error::NonMasterBeforeMaster); + if let Some(n) = plugin_names.iter().next() { + return Err(Error::NonMasterBeforeMaster { + master: plugin.name().to_string(), + non_master: n.to_string(), + }); } } } diff --git a/src/load_order/textfile_based.rs b/src/load_order/textfile_based.rs index 0129e57..32ea98c 100644 --- a/src/load_order/textfile_based.rs +++ b/src/load_order/textfile_based.rs @@ -69,23 +69,27 @@ impl TextfileBasedLoadOrder { if let Some(file_path) = self.game_settings().load_order_file() { create_parent_dirs(file_path)?; - let file = File::create(file_path)?; + let file = File::create(file_path).map_err(|e| Error::IoError(file_path.clone(), e))?; let mut writer = BufWriter::new(file); for plugin_name in self.plugin_names() { - writeln!(writer, "{}", plugin_name)?; + writeln!(writer, "{}", plugin_name) + .map_err(|e| Error::IoError(file_path.clone(), e))?; } } Ok(()) } fn save_active_plugins(&self) -> Result<(), Error> { - create_parent_dirs(self.game_settings().active_plugins_file())?; + let path = self.game_settings().active_plugins_file(); + create_parent_dirs(path)?; - let file = File::create(self.game_settings().active_plugins_file())?; + let file = File::create(path).map_err(|e| Error::IoError(path.clone(), e))?; let mut writer = BufWriter::new(file); for plugin_name in self.active_plugin_names() { - writer.write_all(&strict_encode(plugin_name)?)?; - writeln!(writer)?; + writer + .write_all(&strict_encode(plugin_name)?) + .map_err(|e| Error::IoError(path.clone(), e))?; + writeln!(writer).map_err(|e| Error::IoError(path.clone(), e))?; } Ok(()) @@ -170,22 +174,22 @@ impl WritableLoadOrder for TextfileBasedLoadOrder { } fn set_load_order(&mut self, plugin_names: &[&str]) -> Result<(), Error> { - if plugin_names.is_empty() || !eq(plugin_names[0], self.game_settings().master_file()) { - return Err(Error::GameMasterMustLoadFirst); + let game_master_file = self.game_settings().master_file(); + if plugin_names.is_empty() || !eq(plugin_names[0], game_master_file) { + return Err(Error::GameMasterMustLoadFirst(game_master_file.to_string())); } self.replace_plugins(plugin_names) } fn set_plugin_index(&mut self, plugin_name: &str, position: usize) -> Result { - if position != 0 - && !self.plugins().is_empty() - && eq(plugin_name, self.game_settings().master_file()) - { - return Err(Error::GameMasterMustLoadFirst); + let game_master_file = self.game_settings().master_file(); + + if position != 0 && !self.plugins().is_empty() && eq(plugin_name, game_master_file) { + return Err(Error::GameMasterMustLoadFirst(game_master_file.to_string())); } - if position == 0 && !eq(plugin_name, self.game_settings().master_file()) { - return Err(Error::GameMasterMustLoadFirst); + if position == 0 && !eq(plugin_name, game_master_file) { + return Err(Error::GameMasterMustLoadFirst(game_master_file.to_string())); } self.move_or_insert_plugin_with_index(plugin_name, position) @@ -254,8 +258,9 @@ where } let mut content: String = String::new(); - let mut file = File::open(file_path)?; - file.read_to_string(&mut content)?; + let mut file = File::open(file_path).map_err(|e| Error::IoError(file_path.to_path_buf(), e))?; + file.read_to_string(&mut content) + .map_err(|e| Error::IoError(file_path.to_path_buf(), e))?; Ok(content.lines().filter_map(line_mapper).collect()) } diff --git a/src/load_order/timestamp_based.rs b/src/load_order/timestamp_based.rs index ac0c73f..3129a3f 100644 --- a/src/load_order/timestamp_based.rs +++ b/src/load_order/timestamp_based.rs @@ -63,19 +63,25 @@ impl TimestampBasedLoadOrder { } fn save_active_plugins(&mut self) -> Result<(), Error> { - create_parent_dirs(self.game_settings().active_plugins_file())?; + let path = self.game_settings().active_plugins_file(); + create_parent_dirs(path)?; let prelude = get_file_prelude(self.game_settings())?; - let file = File::create(self.game_settings().active_plugins_file())?; + let file = File::create(path).map_err(|e| Error::IoError(path.clone(), e))?; let mut writer = BufWriter::new(file); - writer.write_all(&prelude)?; + writer + .write_all(&prelude) + .map_err(|e| Error::IoError(path.clone(), e))?; for (index, plugin_name) in self.active_plugin_names().iter().enumerate() { if self.game_settings().id() == GameId::Morrowind { - write!(writer, "GameFile{}=", index)?; + write!(writer, "GameFile{}=", index) + .map_err(|e| Error::IoError(path.clone(), e))?; } - writer.write_all(&strict_encode(plugin_name)?)?; - writeln!(writer)?; + writer + .write_all(&strict_encode(plugin_name)?) + .map_err(|e| Error::IoError(path.clone(), e))?; + writeln!(writer).map_err(|e| Error::IoError(path.clone(), e))?; } Ok(()) @@ -228,12 +234,15 @@ fn padded_unique_timestamps(plugins: &[Plugin]) -> Vec { fn get_file_prelude(game_settings: &GameSettings) -> Result, Error> { let mut prelude: Vec = Vec::new(); - if game_settings.id() == GameId::Morrowind && game_settings.active_plugins_file().exists() { - let input = File::open(game_settings.active_plugins_file())?; + + let path = game_settings.active_plugins_file(); + + if game_settings.id() == GameId::Morrowind && path.exists() { + let input = File::open(path).map_err(|e| Error::IoError(path.clone(), e))?; let buffered = BufReader::new(input); for line in buffered.split(b'\n') { - let line = line?; + let line = line.map_err(|e| Error::IoError(path.clone(), e))?; prelude.append(&mut line.clone()); prelude.push(b'\n'); diff --git a/src/load_order/writable.rs b/src/load_order/writable.rs index 6beb677..9723a6b 100644 --- a/src/load_order/writable.rs +++ b/src/load_order/writable.rs @@ -59,7 +59,7 @@ pub trait WritableLoadOrder: ReadableLoadOrder { pub fn add(load_order: &mut T, plugin_name: &str) -> Result { match load_order.index_of(plugin_name) { - Some(_) => Err(Error::DuplicatePlugin), + Some(_) => Err(Error::DuplicatePlugin(plugin_name.to_string())), None => { let plugin = Plugin::new(plugin_name, load_order.game_settings())?; @@ -111,7 +111,7 @@ pub fn remove(load_order: &mut T, plugin_name: &str) -> Res masters.retain(|m| !next_master_master_names.contains(&UniCase::new(m))); // Finally, check if any remaining masters are non-master plugins. - if masters.iter().any(|n| { + if let Some(n) = masters.iter().find(|n| { load_order .index_of(n) .map(|i| !load_order.plugins()[i].is_master_file()) @@ -119,7 +119,10 @@ pub fn remove(load_order: &mut T, plugin_name: &str) -> Res // doesn't prevent removal of the target plugin. .unwrap_or(false) }) { - return Err(Error::NonMasterBeforeMaster); + return Err(Error::NonMasterBeforeMaster { + master: plugin_name.to_string(), + non_master: n.to_string(), + }); } } } @@ -244,7 +247,7 @@ pub fn set_active_plugins( pub fn create_parent_dirs(path: &Path) -> Result<(), Error> { if let Some(x) = path.parent() { if !x.exists() { - create_dir_all(x)? + create_dir_all(x).map_err(|e| Error::IoError(x.to_path_buf(), e))? } } Ok(()) @@ -438,7 +441,10 @@ mod tests { std::fs::remove_file(&plugins_dir.join(plugin_to_remove)).unwrap(); match remove(&mut load_order, plugin_to_remove).unwrap_err() { - Error::NonMasterBeforeMaster => {} + Error::NonMasterBeforeMaster { master, non_master } => { + assert_eq!("Blank - Different Master Dependent.esm", master); + assert_eq!("Blank - Different.esm", non_master); + } e => panic!("Unexpected error type: {:?}", e), } } diff --git a/src/plugin.rs b/src/plugin.rs index 271bbaf..1987abe 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -78,11 +78,15 @@ impl Plugin { return Err(Error::InvalidPlugin(filename.to_owned())); } - let file = File::open(path)?; - let modification_time = file.metadata()?.modified()?; + let file = File::open(path).map_err(|e| Error::IoError(path.to_path_buf(), e))?; + let modification_time = file + .metadata() + .and_then(|m| m.modified()) + .map_err(|e| Error::IoError(path.to_path_buf(), e))?; let mut data = esplugin::Plugin::new(game_id.to_esplugin_id(), path); - data.parse_open_file(file, true)?; + data.parse_open_file(file, true) + .map_err(|e| file_error(path, e))?; Ok(Plugin { active, @@ -121,7 +125,9 @@ impl Plugin { } pub fn masters(&self) -> Result, Error> { - self.data.masters().map_err(Error::from) + self.data + .masters() + .map_err(|e| file_error(self.data.path(), e)) } pub fn set_modification_time(&mut self, time: SystemTime) -> Result<(), Error> { @@ -134,7 +140,8 @@ impl Plugin { self.data.path(), FileTime::from_system_time(SystemTime::now()), FileTime::from_system_time(time), - )?; + ) + .map_err(|e| Error::IoError(self.data.path().to_path_buf(), e))?; self.modification_time = time; Ok(()) @@ -146,7 +153,9 @@ impl Plugin { let new_path = self.data.path().unghost()?; self.data = esplugin::Plugin::new(*self.data.game_id(), &new_path); - self.data.parse_file(true)?; + self.data + .parse_file(true) + .map_err(|e| file_error(self.data.path(), e))?; let modification_time = self.modification_time(); self.set_modification_time(modification_time)?; } @@ -192,6 +201,19 @@ pub fn trim_dot_ghost(string: &str) -> &str { } } +fn file_error(file_path: &Path, error: esplugin::Error) -> Error { + match error { + esplugin::Error::IoError(x) => Error::IoError(file_path.to_path_buf(), x), + esplugin::Error::NoFilename => Error::NoFilename, + esplugin::Error::ParsingIncomplete | esplugin::Error::ParsingError(_, _) => { + Error::PluginParsingError(file_path.to_path_buf()) + } + esplugin::Error::DecodeError => { + Error::DecodeError("invalid byte sequence in plugin string".into()) + } + } +} + #[cfg(test)] mod tests { use super::*;