feat(core): implement basic troubleshooting logic

This commit is contained in:
Federico Terzi 2021-07-19 23:35:20 +02:00
parent dc6b11cfc8
commit f42c4ef56e
13 changed files with 397 additions and 68 deletions

28
Cargo.lock generated
View File

@ -105,6 +105,17 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 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]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.4.3"
@ -508,6 +519,7 @@ dependencies = [
"markdown", "markdown",
"named_pipe", "named_pipe",
"notify", "notify",
"opener",
"regex", "regex",
"serde", "serde",
"serde_json", "serde_json",
@ -1317,6 +1329,16 @@ dependencies = [
"objc", "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]] [[package]]
name = "ordered-float" name = "ordered-float"
version = "2.1.1" version = "2.1.1"
@ -1639,6 +1661,12 @@ dependencies = [
"regex-syntax", "regex-syntax",
] ]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.6.22" version = "0.6.22"

View File

@ -55,6 +55,7 @@ dialoguer = "0.8.0"
colored = "2.0.0" colored = "2.0.0"
tempdir = "0.3.7" tempdir = "0.3.7"
notify = "4.0.17" notify = "4.0.17"
opener = "0.5.0"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
named_pipe = "0.4.1" named_pipe = "0.4.1"

View File

@ -27,14 +27,22 @@ use espanso_ipc::IPCClient;
use espanso_path::Paths; use espanso_path::Paths;
use log::{error, info, warn}; use log::{error, info, warn};
use crate::{VERSION, exit_code::{ use crate::{
DAEMON_ALREADY_RUNNING, DAEMON_GENERAL_ERROR, DAEMON_LEGACY_ALREADY_RUNNING, DAEMON_SUCCESS, cli::util::CommandExt,
WORKER_EXIT_ALL_PROCESSES, WORKER_RESTART, WORKER_SUCCESS, exit_code::{
}, ipc::{create_ipc_client_to_worker, IPCEvent}, lock::{acquire_daemon_lock, acquire_legacy_lock, acquire_worker_lock}}; 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 ipc;
mod troubleshoot;
mod ui; mod ui;
mod watcher; mod watcher;
@ -42,8 +50,6 @@ pub fn new() -> CliModule {
#[allow(clippy::needless_update)] #[allow(clippy::needless_update)]
CliModule { CliModule {
requires_paths: true, requires_paths: true,
requires_config: true,
enable_logs: true,
log_mode: super::LogMode::CleanAndAppend, log_mode: super::LogMode::CleanAndAppend,
subcommand: "daemon".to_string(), subcommand: "daemon".to_string(),
entry: daemon_main, entry: daemon_main,
@ -53,10 +59,11 @@ pub fn new() -> CliModule {
fn daemon_main(args: CliModuleArgs) -> i32 { fn daemon_main(args: CliModuleArgs) -> i32 {
let paths = args.paths.expect("missing paths in daemon main"); let paths = args.paths.expect("missing paths in daemon main");
let config_store = args let paths_overrides = args
.config_store .paths_overrides
.expect("missing config store in worker main"); .expect("missing paths_overrides in daemon main");
let preferences = crate::preferences::get_default(&paths.runtime).expect("unable to obtain preferences"); 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"); let cli_args = args.cli_args.expect("missing cli_args in daemon main");
// Make sure only one instance of the daemon is running // 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 // 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); info!("espanso version: {}", VERSION);
// TODO: print os system and version? (with os_info crate) // 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 // 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()) ipc::initialize_and_spawn(&paths.runtime, exit_notify.clone())
.expect("unable to initialize ipc server for daemon"); .expect("unable to initialize ipc server for daemon");
@ -135,7 +151,7 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
} }
if !has_timed_out { if !has_timed_out {
spawn_worker(&paths, exit_notify.clone()); spawn_worker(&paths, &paths_overrides, exit_notify.clone());
} else { } else {
error!("could not restart worker, as the exit process has timed out"); error!("could not restart worker, as the exit process has timed out");
} }
@ -150,7 +166,7 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
} }
WORKER_RESTART => { WORKER_RESTART => {
info!("worker requested a restart, spawning a new one..."); 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); 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<i32>) { fn spawn_worker(paths: &Paths, paths_overrides: &PathsOverrides, exit_notify: Sender<i32>) {
info!("spawning the worker process..."); info!("spawning the worker process...");
let espanso_exe_path = let espanso_exe_path =
std::env::current_exe().expect("unable to obtain espanso executable location"); 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()); let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string());
command.args(&["worker", "--monitor-daemon"]); command.args(&["worker", "--monitor-daemon"]);
command.env( command.with_paths_overrides(paths_overrides);
"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
// }
let mut child = command.spawn().expect("unable to spawn worker process"); let mut child = command.spawn().expect("unable to spawn worker process");
@ -243,6 +258,8 @@ fn spawn_worker(paths: &Paths, exit_notify: Sender<i32>) {
std::thread::Builder::new() std::thread::Builder::new()
.name("worker-status-monitor".to_string()) .name("worker-status-monitor".to_string())
.spawn(move || { .spawn(move || {
let _guard = troubleshoot_guard;
let result = child.wait(); let result = child.wait();
if let Ok(status) = result { if let Ok(status) = result {
if let Some(code) = status.code() { if let Some(code) = status.code() {

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<TroubleshootGuard> {
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<TroubleshootGuard>)> {
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);
}
}
}

View File

@ -17,10 +17,10 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use log::{error};
use self::util::MigrationError; use self::util::MigrationError;
use crate::preferences::Preferences;
use crate::exit_code::{LAUNCHER_CONFIG_DIR_POPULATION_FAILURE, LAUNCHER_SUCCESS}; use crate::exit_code::{LAUNCHER_CONFIG_DIR_POPULATION_FAILURE, LAUNCHER_SUCCESS};
use crate::preferences::Preferences;
use log::error;
use super::{CliModule, CliModuleArgs}; 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 // 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 = 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 icon_paths = crate::icon::load_icon_paths(&paths.runtime).expect("unable to load icon paths");
let preferences = let preferences =
@ -103,9 +105,8 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
} else { } else {
false false
}; };
let is_accessibility_enabled_handler = Box::new(move || { let is_accessibility_enabled_handler =
accessibility::is_accessibility_enabled() Box::new(move || accessibility::is_accessibility_enabled());
});
let enable_accessibility_handler = Box::new(move || { let enable_accessibility_handler = Box::new(move || {
accessibility::prompt_enable_accessibility(); accessibility::prompt_enable_accessibility();
}); });

View File

@ -20,7 +20,7 @@
use std::path::PathBuf; use std::path::PathBuf;
use clap::ArgMatches; 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; use espanso_path::Paths;
pub mod daemon; pub mod daemon;
@ -73,6 +73,7 @@ pub struct CliModuleArgs {
pub config_store: Option<Box<dyn ConfigStore>>, pub config_store: Option<Box<dyn ConfigStore>>,
pub match_store: Option<Box<dyn MatchStore>>, pub match_store: Option<Box<dyn MatchStore>>,
pub is_legacy_config: bool, pub is_legacy_config: bool,
pub non_fatal_errors: Vec<NonFatalErrorSet>,
pub paths: Option<Paths>, pub paths: Option<Paths>,
pub paths_overrides: Option<PathsOverrides>, pub paths_overrides: Option<PathsOverrides>,
pub cli_args: Option<ArgMatches<'static>>, pub cli_args: Option<ArgMatches<'static>>,
@ -84,6 +85,7 @@ impl Default for CliModuleArgs {
config_store: None, config_store: None,
match_store: None, match_store: None,
is_legacy_config: false, is_legacy_config: false,
non_fatal_errors: Vec::new(),
paths: None, paths: None,
paths_overrides: None, paths_overrides: None,
cli_args: None, cli_args: None,

View File

@ -25,6 +25,8 @@ mod form;
mod search; mod search;
#[cfg(feature = "modulo")] #[cfg(feature = "modulo")]
mod welcome; mod welcome;
#[cfg(feature = "modulo")]
mod troubleshoot;
pub fn new() -> CliModule { pub fn new() -> CliModule {
#[allow(clippy::needless_update)] #[allow(clippy::needless_update)]
@ -54,6 +56,10 @@ fn modulo_main(args: CliModuleArgs) -> i32 {
if let Some(_) = cli_args.subcommand_matches("welcome") { if let Some(_) = cli_args.subcommand_matches("welcome") {
return welcome::welcome_main(&paths, &icon_paths); return welcome::welcome_main(&paths, &icon_paths);
} }
if let Some(_) = cli_args.subcommand_matches("troubleshoot") {
return troubleshoot::troubleshoot_main(&paths, &icon_paths);
}
0 0
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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
}

View File

@ -17,16 +17,20 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use anyhow::Result; use anyhow::{Context, Result};
use log::info; use espanso_config::{config::ConfigStore, error::{ErrorLevel, NonFatalErrorSet}, matches::store::MatchStore};
use log::{error, info, warn};
use std::path::Path; use std::path::Path;
const DEFAULT_CONFIG_FILE_CONTENT: &str = include_str!("./res/config/default.yml"); 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<()> { pub fn populate_default_config(config_dir: &Path) -> Result<()> {
if !config_dir.is_dir() { 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)?; 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"); let match_file = sub_match_dir.join("base.yml");
if !default_file.is_file() { 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)?; std::fs::write(default_file, DEFAULT_CONFIG_FILE_CONTENT)?;
} }
if !match_file.is_file() { 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)?; std::fs::write(match_file, DEFAULT_MATCH_FILE_CONTENT)?;
} }
Ok(()) Ok(())
} }
pub struct ConfigLoadResult {
pub config_store: Box<dyn ConfigStore>,
pub match_store: Box<dyn MatchStore>,
pub is_legacy_config: bool,
pub non_fatal_errors: Vec<NonFatalErrorSet>,
}
pub fn load_config(config_path: &Path, packages_path: &Path) -> Result<ConfigLoadResult> {
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,
})
}
}

View File

@ -28,6 +28,7 @@ pub const DAEMON_SUCCESS: i32 = 0;
pub const DAEMON_ALREADY_RUNNING: i32 = 1; pub const DAEMON_ALREADY_RUNNING: i32 = 1;
pub const DAEMON_GENERAL_ERROR: i32 = 2; pub const DAEMON_GENERAL_ERROR: i32 = 2;
pub const DAEMON_LEGACY_ALREADY_RUNNING: i32 = 3; 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_SUCCESS: i32 = 0;
pub const MIGRATE_ALREADY_NEW_FORMAT: i32 = 1; pub const MIGRATE_ALREADY_NEW_FORMAT: i32 = 1;

View File

@ -33,7 +33,10 @@ use simplelog::{
CombinedLogger, ConfigBuilder, LevelFilter, SharedLogger, TermLogger, TerminalMode, WriteLogger, CombinedLogger, ConfigBuilder, LevelFilter, SharedLogger, TermLogger, TerminalMode, WriteLogger,
}; };
use crate::cli::{LogMode, PathsOverrides}; use crate::{
cli::{LogMode, PathsOverrides},
config::load_config,
};
mod capabilities; mod capabilities;
mod cli; mod cli;
@ -218,6 +221,7 @@ fn main() {
.help("Interpret the input data as JSON"), .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("welcome").about("Display the welcome screen")),
) )
// .subcommand(SubCommand::with_name("start") // .subcommand(SubCommand::with_name("start")
@ -406,7 +410,7 @@ fn main() {
if !handler.requires_linux_capabilities { if !handler.requires_linux_capabilities {
if let Err(err) = crate::capabilities::clear_capabilities() { if let Err(err) = crate::capabilities::clear_capabilities() {
error!("unable to clear linux capabilities: {}", err); error!("unable to clear linux capabilities: {}", err);
} }
} }
// If explicitly requested, we show the Dock icon on macOS // If explicitly requested, we show the Dock icon on macOS
@ -441,23 +445,15 @@ fn main() {
info!("using runtime dir: {:?}", paths.runtime); info!("using runtime dir: {:?}", paths.runtime);
if handler.requires_config { if handler.requires_config {
let (config_store, match_store, is_legacy_config) = let config_result =
if espanso_config::is_legacy_config(&paths.config) { load_config(&paths.config, &paths.packages).expect("unable to load 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)
};
cli_args.is_legacy_config = is_legacy_config; cli_args.is_legacy_config = config_result.is_legacy_config;
cli_args.config_store = Some(config_store); cli_args.config_store = Some(config_result.config_store);
cli_args.match_store = Some(match_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!("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"); 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() { if path.is_dir() {
return Some(path); return Some(path);
} else { } 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); std::process::exit(1);
} }
} else if let Ok(path) = std::env::var(env_var) { } 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() { if path.is_dir() {
return Some(path); return Some(path);
} else { } 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); std::process::exit(1);
} }
} else { } else {

View File

@ -26,6 +26,8 @@ use super::Preferences;
const HAS_COMPLETED_WIZARD_KEY: &str = "has_completed_wizard"; const HAS_COMPLETED_WIZARD_KEY: &str = "has_completed_wizard";
const SHOULD_DISPLAY_WELCOME_KEY: &str = "should_display_welcome"; 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)] #[derive(Clone)]
pub struct DefaultPreferences<KVSType: KVS> { pub struct DefaultPreferences<KVSType: KVS> {
@ -69,4 +71,12 @@ impl<KVSType: KVS> Preferences for DefaultPreferences<KVSType> {
fn set_should_display_welcome(&self, value: bool) { fn set_should_display_welcome(&self, value: bool) {
self.set(SHOULD_DISPLAY_WELCOME_KEY, value); 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);
}
} }

View File

@ -28,6 +28,9 @@ pub trait Preferences: Send + Sync + Clone {
fn should_display_welcome(&self) -> bool; fn should_display_welcome(&self) -> bool;
fn set_should_display_welcome(&self, value: 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<impl Preferences> { pub fn get_default(runtime_dir: &Path) -> Result<impl Preferences> {