mirror of
https://github.com/ow-mods/ow-mod-man.git
synced 2025-12-11 20:15:50 +01:00
[CORE] Simplify file loading and error handling
This commit is contained in:
parent
65c785ccb8
commit
dac9e5f1c5
@ -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!");
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {}",
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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(())
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"filename": "SaveEditor.dll",
|
||||
"author": "Bwc9876",
|
||||
"name": "SaveEditor",
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"prop": true
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user