From f42c4ef56e3e79daf6ecbb2974c149319ec56e2a Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Mon, 19 Jul 2021 23:35:20 +0200 Subject: [PATCH] feat(core): implement basic troubleshooting logic --- Cargo.lock | 28 +++++++ espanso/Cargo.toml | 1 + espanso/src/cli/daemon/mod.rs | 87 +++++++++++--------- espanso/src/cli/daemon/troubleshoot.rs | 102 ++++++++++++++++++++++++ espanso/src/cli/launcher/mod.rs | 13 +-- espanso/src/cli/mod.rs | 4 +- espanso/src/cli/modulo/mod.rs | 6 ++ espanso/src/cli/modulo/troubleshoot.rs | 105 +++++++++++++++++++++++++ espanso/src/config.rs | 71 +++++++++++++++-- espanso/src/exit_code.rs | 1 + espanso/src/main.rs | 34 ++++---- espanso/src/preferences/default.rs | 10 +++ espanso/src/preferences/mod.rs | 3 + 13 files changed, 397 insertions(+), 68 deletions(-) create mode 100644 espanso/src/cli/daemon/troubleshoot.rs create mode 100644 espanso/src/cli/modulo/troubleshoot.rs diff --git a/Cargo.lock b/Cargo.lock index 37a15ff..95e63e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,6 +105,17 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "bstr" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -508,6 +519,7 @@ dependencies = [ "markdown", "named_pipe", "notify", + "opener", "regex", "serde", "serde_json", @@ -1317,6 +1329,16 @@ dependencies = [ "objc", ] +[[package]] +name = "opener" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea3ebcd72a54701f56345f16785a6d3ac2df7e986d273eb4395c0b01db17952" +dependencies = [ + "bstr", + "winapi 0.3.9", +] + [[package]] name = "ordered-float" version = "2.1.1" @@ -1639,6 +1661,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.22" diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml index b518282..993cbe3 100644 --- a/espanso/Cargo.toml +++ b/espanso/Cargo.toml @@ -55,6 +55,7 @@ dialoguer = "0.8.0" colored = "2.0.0" tempdir = "0.3.7" notify = "4.0.17" +opener = "0.5.0" [target.'cfg(windows)'.dependencies] named_pipe = "0.4.1" diff --git a/espanso/src/cli/daemon/mod.rs b/espanso/src/cli/daemon/mod.rs index f770583..8cb2252 100644 --- a/espanso/src/cli/daemon/mod.rs +++ b/espanso/src/cli/daemon/mod.rs @@ -27,14 +27,22 @@ use espanso_ipc::IPCClient; use espanso_path::Paths; use log::{error, info, warn}; -use crate::{VERSION, exit_code::{ - DAEMON_ALREADY_RUNNING, DAEMON_GENERAL_ERROR, DAEMON_LEGACY_ALREADY_RUNNING, DAEMON_SUCCESS, - WORKER_EXIT_ALL_PROCESSES, WORKER_RESTART, WORKER_SUCCESS, - }, ipc::{create_ipc_client_to_worker, IPCEvent}, lock::{acquire_daemon_lock, acquire_legacy_lock, acquire_worker_lock}}; +use crate::{ + cli::util::CommandExt, + exit_code::{ + DAEMON_ALREADY_RUNNING, DAEMON_FATAL_CONFIG_ERROR, DAEMON_GENERAL_ERROR, + DAEMON_LEGACY_ALREADY_RUNNING, DAEMON_SUCCESS, WORKER_EXIT_ALL_PROCESSES, WORKER_RESTART, + WORKER_SUCCESS, + }, + ipc::{create_ipc_client_to_worker, IPCEvent}, + lock::{acquire_daemon_lock, acquire_legacy_lock, acquire_worker_lock}, + VERSION, +}; -use super::{CliModule, CliModuleArgs}; +use super::{CliModule, CliModuleArgs, PathsOverrides}; mod ipc; +mod troubleshoot; mod ui; mod watcher; @@ -42,8 +50,6 @@ pub fn new() -> CliModule { #[allow(clippy::needless_update)] CliModule { requires_paths: true, - requires_config: true, - enable_logs: true, log_mode: super::LogMode::CleanAndAppend, subcommand: "daemon".to_string(), entry: daemon_main, @@ -53,10 +59,11 @@ pub fn new() -> CliModule { fn daemon_main(args: CliModuleArgs) -> i32 { let paths = args.paths.expect("missing paths in daemon main"); - let config_store = args - .config_store - .expect("missing config store in worker main"); - let preferences = crate::preferences::get_default(&paths.runtime).expect("unable to obtain preferences"); + let paths_overrides = args + .paths_overrides + .expect("missing paths_overrides in daemon main"); + let preferences = + crate::preferences::get_default(&paths.runtime).expect("unable to obtain preferences"); let cli_args = args.cli_args.expect("missing cli_args in daemon main"); // Make sure only one instance of the daemon is running @@ -77,6 +84,15 @@ fn daemon_main(args: CliModuleArgs) -> i32 { // TODO: we might need to check preconditions: accessibility on macOS, presence of binaries on Linux, etc + let config_store = + match troubleshoot::load_config_or_troubleshoot(&paths, &paths_overrides, false) { + Ok((load_result, _)) => load_result.config_store, + Err(fatal_err) => { + error!("critical error while loading config: {}", fatal_err); + return DAEMON_FATAL_CONFIG_ERROR; + } + }; + info!("espanso version: {}", VERSION); // TODO: print os system and version? (with os_info crate) @@ -86,7 +102,7 @@ fn daemon_main(args: CliModuleArgs) -> i32 { // TODO: register signals to terminate the worker if the daemon terminates - spawn_worker(&paths, exit_notify.clone()); + spawn_worker(&paths, &paths_overrides, exit_notify.clone()); ipc::initialize_and_spawn(&paths.runtime, exit_notify.clone()) .expect("unable to initialize ipc server for daemon"); @@ -135,7 +151,7 @@ fn daemon_main(args: CliModuleArgs) -> i32 { } if !has_timed_out { - spawn_worker(&paths, exit_notify.clone()); + spawn_worker(&paths, &paths_overrides, exit_notify.clone()); } else { error!("could not restart worker, as the exit process has timed out"); } @@ -150,7 +166,7 @@ fn daemon_main(args: CliModuleArgs) -> i32 { } WORKER_RESTART => { info!("worker requested a restart, spawning a new one..."); - spawn_worker(&paths, exit_notify.clone()); + spawn_worker(&paths, &paths_overrides, exit_notify.clone()); } _ => { error!("received unexpected exit code from worker {}, exiting", code); @@ -207,34 +223,33 @@ fn terminate_worker_if_already_running(runtime_dir: &Path) { ) } -fn spawn_worker(paths: &Paths, exit_notify: Sender) { +fn spawn_worker(paths: &Paths, paths_overrides: &PathsOverrides, exit_notify: Sender) { info!("spawning the worker process..."); let espanso_exe_path = std::env::current_exe().expect("unable to obtain espanso executable location"); + // Before starting the worker, check if the configuration has any error, and if so, show the troubleshooting GUI + let troubleshoot_guard = match troubleshoot::load_config_or_troubleshoot(paths, paths_overrides, true) { + Ok((_, troubleshoot_guard)) => troubleshoot_guard, + Err(fatal_err) => { + error!("critical configuration error detected, unable to restart worker: {:?}", fatal_err); + + // TODO: we should show a "Reload & Retry" button in the troubleshooter to retry + // loading the configuration and: + // - if the configuration is good -> start the worker + // - if the configuration is bad -> show the window again + // Until we have this logic, we choose the safest option and kill the daemon + // (otherwise, we would have a dangling daemon running after closing the troubleshooting). + + unimplemented!(); + return; + }, + }; + let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string()); command.args(&["worker", "--monitor-daemon"]); - command.env( - "ESPANSO_CONFIG_DIR", - paths.config.to_string_lossy().to_string(), - ); - command.env( - "ESPANSO_PACKAGE_DIR", - paths.packages.to_string_lossy().to_string(), - ); - command.env( - "ESPANSO_RUNTIME_DIR", - paths.runtime.to_string_lossy().to_string(), - ); - - // TODO: investigate if this is needed here, especially when invoking a form - // // On windows, we need to spawn the process as "Detached" - // #[cfg(target_os = "windows")] - // { - // use std::os::windows::process::CommandExt; - // //command.creation_flags(0x08000008); // CREATE_NO_WINDOW + DETACHED_PROCESS - // } + command.with_paths_overrides(paths_overrides); let mut child = command.spawn().expect("unable to spawn worker process"); @@ -243,6 +258,8 @@ fn spawn_worker(paths: &Paths, exit_notify: Sender) { std::thread::Builder::new() .name("worker-status-monitor".to_string()) .spawn(move || { + let _guard = troubleshoot_guard; + let result = child.wait(); if let Ok(status) = result { if let Some(code) = status.code() { diff --git a/espanso/src/cli/daemon/troubleshoot.rs b/espanso/src/cli/daemon/troubleshoot.rs new file mode 100644 index 0000000..1e85cd0 --- /dev/null +++ b/espanso/src/cli/daemon/troubleshoot.rs @@ -0,0 +1,102 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::process::{Child, Command}; + +use anyhow::{Result, bail}; +use espanso_path::Paths; + +use crate::cli::util::CommandExt; +use crate::cli::PathsOverrides; +use crate::config::ConfigLoadResult; +use crate::error_eprintln; +use crate::preferences::Preferences; + +pub fn launch_troubleshoot( + paths_overrides: &PathsOverrides, +) -> Result { + let espanso_exe_path = std::env::current_exe()?; + let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string()); + command.args(&["modulo", "troubleshoot"]); + command.with_paths_overrides(paths_overrides); + + let child = command.spawn()?; + + Ok(TroubleshootGuard::new(child)) +} + +pub fn launch_troubleshoot_blocking(paths_overrides: &PathsOverrides) -> Result<()> { + let mut guard = launch_troubleshoot(paths_overrides)?; + guard.wait()?; + Ok(()) +} + +pub struct TroubleshootGuard { + child: Child, +} + +impl TroubleshootGuard { + pub fn new(child: Child) -> Self { + Self { child } + } + pub fn wait(&mut self) -> Result<()> { + self.child.wait()?; + Ok(()) + } +} + +impl Drop for TroubleshootGuard { + fn drop(&mut self) { + let _ = self.child.kill(); + } +} + +pub fn load_config_or_troubleshoot( + paths: &Paths, + paths_overrides: &PathsOverrides, + troubleshoot_non_fatal: bool, +) -> Result<(ConfigLoadResult, Option)> { + match crate::load_config(&paths.config, &paths.packages) { + Ok(load_result) => { + let mut troubleshoot_handle = None; + + if troubleshoot_non_fatal && !load_result.non_fatal_errors.is_empty() { + let preferences = + crate::preferences::get_default(&paths.runtime).expect("unable to get preferences"); + + if preferences.should_display_troubleshoot_for_non_fatal_errors() { + match launch_troubleshoot(paths_overrides) { + Ok(handle) => { + troubleshoot_handle = Some(handle); + }, + Err(err) => { + error_eprintln!("unable to launch troubleshoot GUI: {}", err); + } + } + } + } + Ok((load_result, troubleshoot_handle)) + } + Err(fatal_err) => { + launch_troubleshoot_blocking(paths_overrides)?; + + bail!("fatal error while loading config: {}", fatal_err); + } + } +} \ No newline at end of file diff --git a/espanso/src/cli/launcher/mod.rs b/espanso/src/cli/launcher/mod.rs index 6647111..79182a8 100644 --- a/espanso/src/cli/launcher/mod.rs +++ b/espanso/src/cli/launcher/mod.rs @@ -17,10 +17,10 @@ * along with espanso. If not, see . */ -use log::{error}; use self::util::MigrationError; -use crate::preferences::Preferences; use crate::exit_code::{LAUNCHER_CONFIG_DIR_POPULATION_FAILURE, LAUNCHER_SUCCESS}; +use crate::preferences::Preferences; +use log::error; use super::{CliModule, CliModuleArgs}; @@ -49,7 +49,9 @@ fn launcher_main(args: CliModuleArgs) -> i32 { // TODO: should we create a non-gui wizard? We can also use it for the non-modulo versions of espanso let paths = args.paths.expect("missing paths in launcher main"); - let paths_overrides = args.paths_overrides.expect("missing paths overrides in launcher main"); + let paths_overrides = args + .paths_overrides + .expect("missing paths overrides in launcher main"); let icon_paths = crate::icon::load_icon_paths(&paths.runtime).expect("unable to load icon paths"); let preferences = @@ -103,9 +105,8 @@ fn launcher_main(args: CliModuleArgs) -> i32 { } else { false }; - let is_accessibility_enabled_handler = Box::new(move || { - accessibility::is_accessibility_enabled() - }); + let is_accessibility_enabled_handler = + Box::new(move || accessibility::is_accessibility_enabled()); let enable_accessibility_handler = Box::new(move || { accessibility::prompt_enable_accessibility(); }); diff --git a/espanso/src/cli/mod.rs b/espanso/src/cli/mod.rs index 7ac0e32..c07d258 100644 --- a/espanso/src/cli/mod.rs +++ b/espanso/src/cli/mod.rs @@ -20,7 +20,7 @@ use std::path::PathBuf; use clap::ArgMatches; -use espanso_config::{config::ConfigStore, matches::store::MatchStore}; +use espanso_config::{config::ConfigStore, error::NonFatalErrorSet, matches::store::MatchStore}; use espanso_path::Paths; pub mod daemon; @@ -73,6 +73,7 @@ pub struct CliModuleArgs { pub config_store: Option>, pub match_store: Option>, pub is_legacy_config: bool, + pub non_fatal_errors: Vec, pub paths: Option, pub paths_overrides: Option, pub cli_args: Option>, @@ -84,6 +85,7 @@ impl Default for CliModuleArgs { config_store: None, match_store: None, is_legacy_config: false, + non_fatal_errors: Vec::new(), paths: None, paths_overrides: None, cli_args: None, diff --git a/espanso/src/cli/modulo/mod.rs b/espanso/src/cli/modulo/mod.rs index 753b2ba..bcd5918 100644 --- a/espanso/src/cli/modulo/mod.rs +++ b/espanso/src/cli/modulo/mod.rs @@ -25,6 +25,8 @@ mod form; mod search; #[cfg(feature = "modulo")] mod welcome; +#[cfg(feature = "modulo")] +mod troubleshoot; pub fn new() -> CliModule { #[allow(clippy::needless_update)] @@ -54,6 +56,10 @@ fn modulo_main(args: CliModuleArgs) -> i32 { if let Some(_) = cli_args.subcommand_matches("welcome") { return welcome::welcome_main(&paths, &icon_paths); } + + if let Some(_) = cli_args.subcommand_matches("troubleshoot") { + return troubleshoot::troubleshoot_main(&paths, &icon_paths); + } 0 } diff --git a/espanso/src/cli/modulo/troubleshoot.rs b/espanso/src/cli/modulo/troubleshoot.rs new file mode 100644 index 0000000..38bc5ad --- /dev/null +++ b/espanso/src/cli/modulo/troubleshoot.rs @@ -0,0 +1,105 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2 file: (), errors: () file: (), errors: () 021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::path::{Path}; + +use crate::icon::IconPaths; +use crate::preferences::Preferences; +use espanso_modulo::troubleshooting::{TroubleshootingHandlers, TroubleshootingOptions}; +use espanso_path::Paths; + +pub fn troubleshoot_main(paths: &Paths, icon_paths: &IconPaths) -> i32 { + let preferences = + crate::preferences::get_default(&paths.runtime).expect("unable to initialize preferences"); + + let dont_show_again_handler = Box::new(move |dont_show: bool| { + preferences.set_should_display_troubleshoot_for_non_fatal_errors(!dont_show); + }); + + let open_file_handler = Box::new(move |file_path: &Path| { + if let Err(err) = opener::open(file_path) { + eprintln!("error opening file: {}", err); + } + }); + + let (is_fatal_error, error_sets) = match crate::config::load_config(&paths.config, &paths.packages) + { + Ok(config_result) => { + let error_sets = config_result + .non_fatal_errors + .into_iter() + .map(|error_set| espanso_modulo::troubleshooting::ErrorSet { + file: Some(error_set.file), + errors: error_set + .errors + .into_iter() + .map(|error| espanso_modulo::troubleshooting::ErrorRecord { + level: match error.level { + espanso_config::error::ErrorLevel::Error => { + espanso_modulo::troubleshooting::ErrorLevel::Error + } + espanso_config::error::ErrorLevel::Warning => { + espanso_modulo::troubleshooting::ErrorLevel::Warning + } + }, + message: format!("{:?}", error.error), + }) + .collect(), + }) + .collect(); + + (false, error_sets) + } + Err(err) => { + let message = format!("{:?}", err); + let file_path = if message.contains("default.yml") { + let default_file_path = paths.config.join("config").join("default.yml"); + Some(default_file_path) + } else { + None + }; + + ( + true, + vec![espanso_modulo::troubleshooting::ErrorSet { + file: file_path, + errors: vec![espanso_modulo::troubleshooting::ErrorRecord { + level: espanso_modulo::troubleshooting::ErrorLevel::Error, + message: format!("{:?}", err), + }], + }], + ) + } + }; + + espanso_modulo::troubleshooting::show(TroubleshootingOptions { + window_icon_path: icon_paths + .wizard_icon + .as_ref() + .map(|path| path.to_string_lossy().to_string()), + error_sets, + is_fatal_error, + handlers: TroubleshootingHandlers { + dont_show_again_changed: Some(dont_show_again_handler), + open_file: Some(open_file_handler), + }, + }).expect("troubleshoot GUI returned error"); + + 0 +} diff --git a/espanso/src/config.rs b/espanso/src/config.rs index 4c36588..b318c5a 100644 --- a/espanso/src/config.rs +++ b/espanso/src/config.rs @@ -17,16 +17,20 @@ * along with espanso. If not, see . */ -use anyhow::Result; -use log::info; +use anyhow::{Context, Result}; +use espanso_config::{config::ConfigStore, error::{ErrorLevel, NonFatalErrorSet}, matches::store::MatchStore}; +use log::{error, info, warn}; use std::path::Path; const DEFAULT_CONFIG_FILE_CONTENT: &str = include_str!("./res/config/default.yml"); -const DEFAULT_MATCH_FILE_CONTENT: &str = include_str!("./res/config/base.yml"); +const DEFAULT_MATCH_FILE_CONTENT: &str = include_str!("./res/config/base.yml"); pub fn populate_default_config(config_dir: &Path) -> Result<()> { if !config_dir.is_dir() { - info!("generating base configuration directory in: {:?}", config_dir); + info!( + "generating base configuration directory in: {:?}", + config_dir + ); std::fs::create_dir_all(config_dir)?; } @@ -46,13 +50,66 @@ pub fn populate_default_config(config_dir: &Path) -> Result<()> { let match_file = sub_match_dir.join("base.yml"); if !default_file.is_file() { - info!("populating default.yml file with initial content: {:?}", default_file); + info!( + "populating default.yml file with initial content: {:?}", + default_file + ); std::fs::write(default_file, DEFAULT_CONFIG_FILE_CONTENT)?; } if !match_file.is_file() { - info!("populating base.yml file with initial content: {:?}", match_file); + info!( + "populating base.yml file with initial content: {:?}", + match_file + ); std::fs::write(match_file, DEFAULT_MATCH_FILE_CONTENT)?; } Ok(()) -} \ No newline at end of file +} + +pub struct ConfigLoadResult { + pub config_store: Box, + pub match_store: Box, + pub is_legacy_config: bool, + pub non_fatal_errors: Vec, +} + +pub fn load_config(config_path: &Path, packages_path: &Path) -> Result { + if espanso_config::is_legacy_config(&config_path) { + let (config_store, match_store) = espanso_config::load_legacy(&config_path, &packages_path) + .context("unable to load legacy config")?; + + Ok(ConfigLoadResult { + config_store, + match_store, + is_legacy_config: true, + non_fatal_errors: Vec::new(), + }) + } else { + let (config_store, match_store, non_fatal_errors) = + espanso_config::load(&config_path).context("unable to load config")?; + + // TODO: add an option to avoid dumping the errors in the logs + if !non_fatal_errors.is_empty() { + warn!("------- detected some errors in the configuration: -------"); + for non_fatal_error_set in &non_fatal_errors { + warn!(">>> {}", non_fatal_error_set.file.to_string_lossy().to_string()); + for record in &non_fatal_error_set.errors { + if record.level == ErrorLevel::Error { + error!("{:?}", record.error); + } else { + warn!("{:?}", record.error); + } + } + } + warn!("-----------------------------------------------------------"); + } + + Ok(ConfigLoadResult { + config_store, + match_store, + is_legacy_config: false, + non_fatal_errors, + }) + } +} diff --git a/espanso/src/exit_code.rs b/espanso/src/exit_code.rs index b310c76..f5cbac0 100644 --- a/espanso/src/exit_code.rs +++ b/espanso/src/exit_code.rs @@ -28,6 +28,7 @@ pub const DAEMON_SUCCESS: i32 = 0; pub const DAEMON_ALREADY_RUNNING: i32 = 1; pub const DAEMON_GENERAL_ERROR: i32 = 2; pub const DAEMON_LEGACY_ALREADY_RUNNING: i32 = 3; +pub const DAEMON_FATAL_CONFIG_ERROR: i32 = 4; pub const MIGRATE_SUCCESS: i32 = 0; pub const MIGRATE_ALREADY_NEW_FORMAT: i32 = 1; diff --git a/espanso/src/main.rs b/espanso/src/main.rs index eda2df2..a3bb207 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -33,7 +33,10 @@ use simplelog::{ CombinedLogger, ConfigBuilder, LevelFilter, SharedLogger, TermLogger, TerminalMode, WriteLogger, }; -use crate::cli::{LogMode, PathsOverrides}; +use crate::{ + cli::{LogMode, PathsOverrides}, + config::load_config, +}; mod capabilities; mod cli; @@ -218,6 +221,7 @@ fn main() { .help("Interpret the input data as JSON"), ), ) + .subcommand(SubCommand::with_name("troubleshoot").about("Display the troubleshooting GUI")) .subcommand(SubCommand::with_name("welcome").about("Display the welcome screen")), ) // .subcommand(SubCommand::with_name("start") @@ -406,7 +410,7 @@ fn main() { if !handler.requires_linux_capabilities { if let Err(err) = crate::capabilities::clear_capabilities() { error!("unable to clear linux capabilities: {}", err); - } + } } // If explicitly requested, we show the Dock icon on macOS @@ -441,23 +445,15 @@ fn main() { info!("using runtime dir: {:?}", paths.runtime); if handler.requires_config { - let (config_store, match_store, is_legacy_config) = - if espanso_config::is_legacy_config(&paths.config) { - let (config_store, match_store) = - espanso_config::load_legacy(&paths.config, &paths.packages) - .expect("unable to load legacy config"); - (config_store, match_store, true) - } else { - let (config_store, match_store) = - espanso_config::load(&paths.config).expect("unable to load config"); - (config_store, match_store, false) - }; + let config_result = + load_config(&paths.config, &paths.packages).expect("unable to load config"); - cli_args.is_legacy_config = is_legacy_config; - cli_args.config_store = Some(config_store); - cli_args.match_store = Some(match_store); + cli_args.is_legacy_config = config_result.is_legacy_config; + cli_args.config_store = Some(config_result.config_store); + cli_args.match_store = Some(config_result.match_store); + cli_args.non_fatal_errors = config_result.non_fatal_errors; - if is_legacy_config { + if config_result.is_legacy_config { warn!("espanso is reading the configuration using compatibility mode, thus some features might not be available"); warn!("you can migrate to the new configuration format by running 'espanso migrate' in a terminal"); } @@ -497,7 +493,7 @@ fn get_path_override(matches: &ArgMatches, argument: &str, env_var: &str) -> Opt if path.is_dir() { return Some(path); } else { - error!("{} argument was specified, but it doesn't point to a valid directory. Make sure to create it first.", argument); + error_eprintln!("{} argument was specified, but it doesn't point to a valid directory. Make sure to create it first.", argument); std::process::exit(1); } } else if let Ok(path) = std::env::var(env_var) { @@ -505,7 +501,7 @@ fn get_path_override(matches: &ArgMatches, argument: &str, env_var: &str) -> Opt if path.is_dir() { return Some(path); } else { - error!("{} env variable was specified, but it doesn't point to a valid directory. Make sure to create it first.", env_var); + error_eprintln!("{} env variable was specified, but it doesn't point to a valid directory. Make sure to create it first.", env_var); std::process::exit(1); } } else { diff --git a/espanso/src/preferences/default.rs b/espanso/src/preferences/default.rs index 03caf6e..b2516f3 100644 --- a/espanso/src/preferences/default.rs +++ b/espanso/src/preferences/default.rs @@ -26,6 +26,8 @@ use super::Preferences; const HAS_COMPLETED_WIZARD_KEY: &str = "has_completed_wizard"; const SHOULD_DISPLAY_WELCOME_KEY: &str = "should_display_welcome"; +const SHOULD_DISPLAY_TROUBLESHOOT_FOR_NON_FATAL_ERRORS: &str = + "should_display_troubleshoot_for_non_fatal_errors"; #[derive(Clone)] pub struct DefaultPreferences { @@ -69,4 +71,12 @@ impl Preferences for DefaultPreferences { fn set_should_display_welcome(&self, value: bool) { self.set(SHOULD_DISPLAY_WELCOME_KEY, value); } + + fn should_display_troubleshoot_for_non_fatal_errors(&self) -> bool { + self.get(SHOULD_DISPLAY_TROUBLESHOOT_FOR_NON_FATAL_ERRORS).unwrap_or(true) + } + + fn set_should_display_troubleshoot_for_non_fatal_errors(&self, value: bool) { + self.set(SHOULD_DISPLAY_TROUBLESHOOT_FOR_NON_FATAL_ERRORS, value); + } } diff --git a/espanso/src/preferences/mod.rs b/espanso/src/preferences/mod.rs index ca19f89..e7aad17 100644 --- a/espanso/src/preferences/mod.rs +++ b/espanso/src/preferences/mod.rs @@ -28,6 +28,9 @@ pub trait Preferences: Send + Sync + Clone { fn should_display_welcome(&self) -> bool; fn set_should_display_welcome(&self, value: bool); + + fn should_display_troubleshoot_for_non_fatal_errors(&self) -> bool; + fn set_should_display_troubleshoot_for_non_fatal_errors(&self, value: bool); } pub fn get_default(runtime_dir: &Path) -> Result {