feat(core): implement basic troubleshooting logic
This commit is contained in:
parent
dc6b11cfc8
commit
f42c4ef56e
28
Cargo.lock
generated
28
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
102
espanso/src/cli/daemon/troubleshoot.rs
Normal file
102
espanso/src/cli/daemon/troubleshoot.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
105
espanso/src/cli/modulo/troubleshoot.rs
Normal file
105
espanso/src/cli/modulo/troubleshoot.rs
Normal 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
|
||||||
|
}
|
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user