[CORE] Simplify file loading and error handling

This commit is contained in:
Ben C 2024-07-10 22:03:51 -04:00
parent 65c785ccb8
commit dac9e5f1c5
No known key found for this signature in database
10 changed files with 48 additions and 97 deletions

View File

@ -1,6 +1,6 @@
use std::{fmt::Write, path::PathBuf, process};
use anyhow::{anyhow, Result};
use anyhow::{Context, Result};
use clap::{CommandFactory, Parser};
use colored::Colorize;
use log::{error, info, warn, LevelFilter};
@ -96,7 +96,7 @@ async fn run_from_cli(cli: BaseCli) -> Result<()> {
let db = RemoteDatabase::fetch(&config.database_url).await?;
let owml = db
.get_owml()
.ok_or_else(|| anyhow!("OWML not found, is the database URL correct?"))?;
.context("OWML not found, is the database URL correct?")?;
download_and_install_owml(&config, owml, *prerelease).await?;
info!("Done! Happy Modding!");
}

View File

@ -3,11 +3,11 @@ use std::{
path::{Path, PathBuf},
};
use anyhow::{anyhow, Result};
use anyhow::{Context, Result};
use log::{debug, warn};
use crate::{
file::{deserialize_from_json, fix_json_file},
file::deserialize_from_json,
mods::local::{FailedMod, LocalMod, ModManifest, UnsafeLocalMod},
search::search_list,
toggle::get_mod_enabled,
@ -155,7 +155,6 @@ impl LocalDatabase {
///
pub fn get_owml(owml_path: &str) -> Option<LocalMod> {
let manifest_path = PathBuf::from(owml_path).join("OWML.Manifest.json");
fix_json_file(&manifest_path).ok();
let mut owml_manifest: ModManifest = deserialize_from_json(&manifest_path).ok()?;
owml_manifest.version = fix_version(&owml_manifest.version).to_string();
Some(LocalMod {
@ -198,12 +197,7 @@ impl LocalDatabase {
"Loading Mod With Manifest: {}",
manifest_path.to_str().unwrap()
);
let folder_path = manifest_path.parent();
if folder_path.is_none() {
return Err(anyhow!("Mod Path Not Found"));
}
let folder_path = folder_path.unwrap(); // <- Unwrap is safe, .is_none() check is above
fix_json_file(manifest_path).ok();
let folder_path = manifest_path.parent().context("Mod Path Not Found")?;
let mut manifest: ModManifest = deserialize_from_json(manifest_path)?;
manifest.version = fix_version(&manifest.version).to_string();
Ok(LocalMod {
@ -414,7 +408,7 @@ impl LocalDatabase {
glob::glob(mods_path.join("**").join("manifest.json").to_str().unwrap())?;
for entry in glob_matches {
let entry = entry?;
let parent = entry.parent().ok_or_else(|| anyhow!("Invalid Manifest!"))?;
let parent = entry.parent().context("Invalid Manifest!")?;
let path = parent.to_str().unwrap().to_string();
let display_path = parent
.strip_prefix(mods_path)

View File

@ -6,8 +6,8 @@ use std::{
time::Duration,
};
use anyhow::anyhow;
use anyhow::Result;
use anyhow::{anyhow, Context};
use futures::{stream::FuturesUnordered, StreamExt};
use log::{debug, info};
use tempfile::TempDir;
@ -18,9 +18,11 @@ use crate::{
config::Config,
constants::OWML_UNIQUE_NAME,
db::{LocalDatabase, RemoteDatabase},
file::{check_file_matches_paths, create_all_parents, fix_json},
mods::local::{get_paths_to_preserve, LocalMod, ModManifest},
mods::remote::RemoteMod,
file::{check_file_matches_paths, create_all_parents, fix_bom},
mods::{
local::{get_paths_to_preserve, LocalMod, ModManifest},
remote::RemoteMod,
},
progress::{ProgressAction, ProgressBar, ProgressType},
remove::remove_old_mod_files,
toggle::generate_config,
@ -89,7 +91,7 @@ fn get_manifest_path_from_zip(zip_path: &PathBuf) -> Result<(String, PathBuf)> {
zip_file.name().to_string(),
zip_file
.enclosed_name()
.ok_or_else(|| anyhow!("Error reading zip file"))?
.context("Error reading zip file")?
.to_path_buf(),
));
}
@ -105,8 +107,7 @@ fn get_unique_name_from_zip(zip_path: &PathBuf) -> Result<String> {
let mut manifest = archive.by_name(&manifest_name)?;
let mut buf = String::new();
manifest.read_to_string(&mut buf)?;
let txt = fix_json(&buf);
let manifest: ModManifest = serde_json::from_str(&txt)?;
let manifest: ModManifest = serde_json::from_str(fix_bom(&buf))?;
Ok(manifest.unique_name)
}
@ -173,9 +174,7 @@ fn extract_mod_zip(
progress.inc(1);
let zip_file = archive.by_index(idx)?;
if zip_file.is_file() {
let file_path = zip_file
.enclosed_name()
.ok_or_else(|| anyhow!("Can't Read Zip File"))?;
let file_path = zip_file.enclosed_name().context("Can't Read Zip File")?;
if file_path.starts_with(parent_path) {
// Unwrap is safe bc we know it's a file and OsStr.to_str shouldn't fail
let file_name = file_path.file_name().unwrap().to_str().unwrap();
@ -256,7 +255,7 @@ pub async fn download_and_install_owml(
owml.prerelease
.as_ref()
.map(|p| &p.download_url)
.ok_or_else(|| anyhow!("No prerelease for OWML found"))
.context("No prerelease for OWML found")
} else {
Ok(&owml.download_url)
}?;
@ -463,7 +462,7 @@ pub async fn install_mods_parallel(
for name in unique_names.iter() {
let remote_mod = remote_db
.get_mod(name)
.ok_or_else(|| anyhow!("Mod {} not found in database.", name))?;
.with_context(|| format!("Mod {} not found in database.", name))?;
let task = install_mod_from_url(
&remote_mod.download_url,
@ -559,12 +558,12 @@ pub async fn install_mod_from_db(
let remote_mod = remote_db
.get_mod(unique_name)
.ok_or_else(|| anyhow!("Mod {} not found", unique_name))?;
.with_context(|| format!("Mod {} not found", unique_name))?;
let target_url = if prerelease {
let prerelease = remote_mod
.prerelease
.as_ref()
.ok_or_else(|| anyhow!("No prerelease for {} found", unique_name))?;
.with_context(|| format!("No prerelease for {} found", unique_name))?;
let url = &prerelease.download_url;
info!(
"Using Prerelease {} for {}",

View File

@ -1,10 +1,9 @@
use std::{
fs::{create_dir_all, read_to_string, File},
io::{BufReader, BufWriter, Write},
fs::{create_dir_all, read_to_string},
path::{Path, PathBuf},
};
use anyhow::{anyhow, Result};
use anyhow::{anyhow, Context, Result};
use directories::{BaseDirs, ProjectDirs};
use serde::{Deserialize, Serialize};
@ -49,10 +48,8 @@ pub fn deserialize_from_json<T>(file_path: &Path) -> Result<T>
where
for<'a> T: Deserialize<'a>,
{
let file = File::open(file_path)?;
let buffer = BufReader::new(file);
let result = serde_json::from_reader(buffer)?;
Ok(result)
let text = read_to_string(file_path)?;
serde_json::from_str(fix_bom(&text)).context("Failed to parse JSON")
}
/// Utility function to serialize an object to a JSON file.
@ -99,10 +96,8 @@ where
create_dir_all(parent_path)?;
}
}
let file = File::create(out_path)?;
let buffer = BufWriter::new(file);
serde_json::to_writer_pretty(buffer, obj)?;
Ok(())
let text = serde_json::to_string_pretty(obj)?;
std::fs::write(out_path, text).context("Failed to write JSON")
}
/// Utility function to get the application directory in the user's files
@ -146,27 +141,11 @@ pub fn get_app_path() -> Result<PathBuf> {
/// If we can't get the user's app data dir (or equivalent on Linux.
///
pub fn get_default_owml_path() -> Result<PathBuf> {
let base_dirs = BaseDirs::new().ok_or_else(|| anyhow!("Couldn't Get User App Data"))?;
let base_dirs = BaseDirs::new().context("Couldn't Get User App Data")?;
let appdata_dir = base_dirs.data_dir();
Ok(appdata_dir.join(OLD_MANAGER_FOLDER_NAME).join("OWML"))
}
/// Fix a string of JSON by removing the BOM
pub fn fix_json(txt: &str) -> String {
fix_bom(txt).to_string()
}
/// Removes the BOM on a JSON file
pub fn fix_json_file(path: &Path) -> Result<()> {
let txt_old = read_to_string(path)?;
let txt = fix_json(&txt_old);
if txt != txt_old {
let mut file = File::create(path)?;
write!(file, "{}", txt)?;
}
Ok(())
}
/// Check if a path matches any path in a series of other paths
pub fn check_file_matches_paths(path: &Path, to_check: &[PathBuf]) -> bool {
for check in to_check.iter() {
@ -188,7 +167,8 @@ pub fn create_all_parents(file_path: &Path) -> Result<()> {
Ok(())
}
fn fix_bom(str: &str) -> &str {
/// Removes the BOM from a string if it exists
pub fn fix_bom(str: &str) -> &str {
str.strip_prefix('\u{FEFF}').unwrap_or(str)
}
@ -197,20 +177,6 @@ mod tests {
use super::*;
#[derive(Deserialize)]
struct TestStruct {
prop: bool,
}
// Simple test rn, if some mods ever use weird json we'll need to test for and fix that
#[test]
fn test_fix_json() {
let json = include_str!("../test_files/whacky_json.json");
let json = fix_json(json);
let obj: TestStruct = serde_json::from_str(&json).unwrap();
assert!(obj.prop)
}
#[test]
fn test_file_matches_path() {
let test_path = Path::new("folder/some_file.json");

View File

@ -1,4 +1,4 @@
use anyhow::{anyhow, Result};
use anyhow::{Context, Result};
use crate::{
config::Config,
@ -55,7 +55,7 @@ pub fn open_shortcut(identifier: &str, conf: &Config, local_db: &LocalDatabase)
UnsafeLocalMod::Invalid(m) => &m.mod_path,
UnsafeLocalMod::Valid(m) => &m.mod_path,
})
.ok_or_else(|| anyhow!("Mod {} not found", identifier))?;
.with_context(|| format!("Mod {} not found", identifier))?;
opener::open(path)?;
} else {
opener::open(target)?;
@ -88,7 +88,7 @@ pub fn open_shortcut(identifier: &str, conf: &Config, local_db: &LocalDatabase)
pub fn open_readme(unique_name: &str, db: &RemoteDatabase) -> Result<()> {
let remote_mod = db
.get_mod(unique_name)
.ok_or_else(|| anyhow!("Mod {} not found", unique_name))?;
.with_context(|| format!("Mod {} not found", unique_name))?;
let slug = &remote_mod.slug;
opener::open(format!("{WEBSITE_URL}/mods/{slug}/"))?;
Ok(())
@ -118,7 +118,7 @@ pub fn open_readme(unique_name: &str, db: &RemoteDatabase) -> Result<()> {
pub fn open_github(unique_name: &str, db: &RemoteDatabase) -> Result<()> {
let remote_mod = db
.get_mod(unique_name)
.ok_or_else(|| anyhow!("Mod {} not found", unique_name))?;
.with_context(|| format!("Mod {} not found", unique_name))?;
let repo = &remote_mod.repo; // this is the entire link to the repo
opener::open(repo)?;
Ok(())

View File

@ -88,14 +88,14 @@ impl OWMLConfig {
///
#[cfg(not(windows))]
pub fn default(config: &Config) -> Result<OWMLConfig> {
use anyhow::anyhow;
use anyhow::Context;
use directories::UserDirs;
const LINUX_GAME_PATH: &str = ".steam/steam/steamapps/common/Outer Wilds/";
let path = Path::new(&config.owml_path).join(OWML_DEFAULT_CONFIG_NAME);
let mut conf: OWMLConfig = deserialize_from_json(&path)?;
let dirs = UserDirs::new().ok_or_else(|| anyhow!("Can't get user data dir"))?;
let dirs = UserDirs::new().context("Can't get user data dir")?;
conf.game_path = dirs
.home_dir()
.join(LINUX_GAME_PATH)

View File

@ -1,16 +1,15 @@
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result};
use anyhow::{Context, Result};
use log::warn;
use crate::{
db::LocalDatabase,
file::{deserialize_from_json, fix_json_file, serialize_to_json},
file::{deserialize_from_json, serialize_to_json},
mods::local::{LocalMod, ModStubConfig},
};
fn read_config(config_path: &Path) -> Result<ModStubConfig> {
fix_json_file(config_path).ok();
deserialize_from_json(config_path)
}
@ -97,7 +96,7 @@ pub fn toggle_mod(
let local_mod = local_db
.get_mod(unique_name)
.ok_or_else(|| anyhow!("Mod {} not found in local database.", unique_name))?;
.with_context(|| format!("Mod {} not found in local database.", unique_name))?;
let show_warning = _toggle_mod(local_mod, enabled)?;
if show_warning {

View File

@ -1,4 +1,4 @@
{
{
"filename": "SaveEditor.dll",
"author": "Bwc9876",
"name": "SaveEditor",

View File

@ -1,3 +0,0 @@
{
"prop": true
}

View File

@ -4,7 +4,7 @@ use std::{
path::PathBuf,
};
use anyhow::anyhow;
use anyhow::{anyhow, Context};
use log::error;
use owmods_core::{
alerts::{fetch_alert, Alert},
@ -182,7 +182,7 @@ pub async fn get_local_mod(
if unique_name == OWML_UNIQUE_NAME {
let config = state.config.read().await;
let owml = LocalDatabase::get_owml(&config.owml_path)
.ok_or_else(|| anyhow!("Couldn't Find OWML at path {}", &config.owml_path))?;
.with_context(|| format!("Couldn't Find OWML at path {}", &config.owml_path))?;
Ok(Some(UnsafeLocalMod::Valid(Box::new(owml))))
} else {
Ok(state
@ -406,7 +406,7 @@ pub async fn uninstall_mod(
let db = state.local_db.read().await;
let local_mod = db
.get_mod(unique_name)
.ok_or_else(|| anyhow!("Mod {} not found", unique_name))?;
.with_context(|| format!("Mod {} not found", unique_name))?;
let warnings = remove_mod(local_mod, &db, false)?;
Ok(warnings)
@ -417,7 +417,7 @@ pub async fn uninstall_broken_mod(mod_path: &str, state: tauri::State<'_, State>
let db = state.local_db.read().await;
let local_mod = db
.get_mod_unsafe(mod_path)
.ok_or_else(|| anyhow!("Mod {} not found", mod_path))?;
.with_context(|| format!("Mod {} not found", mod_path))?;
match local_mod {
UnsafeLocalMod::Invalid(m) => {
remove_failed_mod(m)?;
@ -520,9 +520,7 @@ pub async fn install_owml(
let config = state.config.read().await;
let db = state.remote_db.read().await;
let db = db.try_get()?;
let owml = db
.get_owml()
.ok_or_else(|| anyhow!("Error Installing OWML"))?;
let owml = db.get_owml().context("Error Installing OWML")?;
download_and_install_owml(&config, owml, prerelease).await?;
handle.typed_emit_all(&Event::OwmlConfigReload(())).ok();
Ok(())
@ -608,13 +606,11 @@ pub async fn update_mod(
let remote_db = remote_db.try_get()?;
let remote_mod = if unique_name == OWML_UNIQUE_NAME {
remote_db
.get_owml()
.ok_or_else(|| anyhow!("Can't find OWML"))?
remote_db.get_owml().context("Can't find OWML")?
} else {
remote_db
.get_mod(unique_name)
.ok_or_else(|| anyhow!("Can't find mod {} in remote", unique_name))?
.with_context(|| format!("Can't find mod {} in remote", unique_name))?
};
let res = if unique_name == OWML_UNIQUE_NAME {
@ -666,7 +662,7 @@ pub async fn update_all_mods(
&config,
remote_db
.get_owml()
.ok_or_else(|| anyhow!("Couldn't find OWML in database"))?,
.context("Couldn't find OWML in database")?,
false,
)
.await?;
@ -890,7 +886,7 @@ pub async fn fix_mod_deps(
let remote_db = remote_db.try_get()?;
let local_mod = local_db
.get_mod(unique_name)
.ok_or_else(|| anyhow!("Can't find mod {}", unique_name))?;
.with_context(|| format!("Can't find mod {}", unique_name))?;
mark_mod_busy(unique_name, true, true, &state, &handle).await;
let res = fix_deps(local_mod, &config, &local_db, remote_db).await;
@ -1053,7 +1049,7 @@ pub async fn has_disabled_deps(unique_name: &str, state: tauri::State<'_, State>
let db = state.local_db.read().await;
let local_mod = db
.get_mod(unique_name)
.ok_or_else(|| anyhow!("Mod Not Found: {unique_name}"))?;
.context("Mod Not Found: {unique_name}")?;
let mut flag = false;
if let Some(deps) = &local_mod.manifest.dependencies {
for dep in deps.iter() {