feat(core): implement runtime Preferences module

This commit is contained in:
Federico Terzi 2021-06-25 18:57:17 +02:00
parent cfe44fd861
commit 8fb3826298
8 changed files with 177 additions and 49 deletions

13
Cargo.lock generated
View File

@ -427,6 +427,7 @@ dependencies = [
"espanso-info",
"espanso-inject",
"espanso-ipc",
"espanso-kvs",
"espanso-mac-utils",
"espanso-match",
"espanso-migrate",
@ -549,6 +550,18 @@ dependencies = [
"thiserror",
]
[[package]]
name = "espanso-kvs"
version = "0.1.0"
dependencies = [
"anyhow",
"log",
"serde",
"serde_json",
"tempdir",
"thiserror",
]
[[package]]
name = "espanso-mac-utils"
version = "0.1.0"

View File

@ -15,4 +15,5 @@ members = [
"espanso-modulo",
"espanso-migrate",
"espanso-mac-utils",
"espanso-kvs",
]

View File

@ -34,8 +34,8 @@ dependencies=["create-bundle"]
[tasks.test]
command = "cargo"
args = ["test", "--workspace", "--exclude", "espanso-modulo", "--no-default-features"]
args = ["test", "--workspace", "--exclude", "espanso-modulo", "--exclude", "espanso-ipc", "--no-default-features"]
[tasks.test-output]
command = "cargo"
args = ["test", "--workspace", "--exclude", "espanso-modulo", "--no-default-features", "--", "--nocapture"]
args = ["test", "--workspace", "--exclude", "espanso-modulo", "--exclude", "espanso-ipc", "--no-default-features", "--", "--nocapture"]

View File

@ -32,6 +32,7 @@ espanso-path = { path = "../espanso-path" }
espanso-ipc = { path = "../espanso-ipc" }
espanso-modulo = { path = "../espanso-modulo", optional = true }
espanso-migrate = { path = "../espanso-migrate" }
espanso-kvs = { path = "../espanso-kvs" }
maplit = "1.0.2"
simplelog = "0.9.0"
log = "0.4.14"

View File

@ -17,9 +17,8 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use espanso_modulo::wizard::{MigrationResult, WizardHandlers, WizardOptions};
use self::util::MigrationError;
use crate::preferences::Preferences;
use super::{CliModule, CliModuleArgs};
@ -41,10 +40,19 @@ pub fn new() -> CliModule {
#[cfg(feature = "modulo")]
fn launcher_main(args: CliModuleArgs) -> i32 {
use espanso_modulo::wizard::{MigrationResult, WizardHandlers, WizardOptions};
// 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 icon_paths = crate::icon::load_icon_paths(&paths.runtime).expect("unable to load icon paths");
// TODO: should move wizard to "init" subcommand?
let preferences =
crate::preferences::get_default(&paths.runtime).expect("unable to initialize preferences");
let is_welcome_page_enabled = !preferences.has_completed_wizard();
let is_move_bundle_page_enabled = false; // TODO
let is_legacy_version_page_enabled = util::is_legacy_version_running(&paths.runtime);
let runtime_dir_clone = paths.runtime.clone();
@ -55,26 +63,20 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
let paths_clone = paths.clone();
let backup_and_migrate_handler =
Box::new(move || match util::migrate_configuration(&paths_clone) {
Ok(_) => {
MigrationResult::Success
}
Err(error) => {
match error.downcast_ref::<MigrationError>() {
Some(MigrationError::DirtyError) => {
MigrationResult::DirtyFailure
}
Some(MigrationError::CleanError) => {
MigrationResult::CleanFailure
}
_ => {
MigrationResult::UnknownFailure
}
}
}
Ok(_) => MigrationResult::Success,
Err(error) => match error.downcast_ref::<MigrationError>() {
Some(MigrationError::DirtyError) => MigrationResult::DirtyFailure,
Some(MigrationError::CleanError) => MigrationResult::CleanFailure,
_ => MigrationResult::UnknownFailure,
},
});
// TODO: enable "Add to PATH" page only when NOT in portable mode
// TODO: if the user clicks on "Continue" after unchecking the "ADD to PATH"
// checkbox, we should remember (with the kvs) the choice and avoid asking again.
let is_add_path_page_enabled = if cfg!(target_os = "macos") {
// TODO: add actual check
// TODO: consider also Windows case
true
} else {
false
@ -87,39 +89,46 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
false
};
// TODO: show welcome page only the first time (we need a persistent key-value store)
// TODO: show a "espanso is now running page at the end" (it should be triggered everytime
// espanso is started, unless the user unchecks "show this message at startup")
// This page could also be used when the user starts espanso, but an instance is already running.
espanso_modulo::wizard::show(WizardOptions {
version: crate::VERSION.to_string(),
is_welcome_page_enabled: true, // TODO
is_move_bundle_page_enabled: false, // TODO
is_legacy_version_page_enabled,
is_migrate_page_enabled,
is_add_path_page_enabled,
is_accessibility_page_enabled,
window_icon_path: icon_paths
.wizard_icon
.map(|path| path.to_string_lossy().to_string()),
welcome_image_path: icon_paths
.logo_no_background
.map(|path| path.to_string_lossy().to_string()),
accessibility_image_1_path: None, // TODO
accessibility_image_2_path: None, // TODO
handlers: WizardHandlers {
is_legacy_version_running: Some(is_legacy_version_running_handler),
backup_and_migrate: Some(backup_and_migrate_handler),
add_to_path: None, // TODO
enable_accessibility: None, // TODO
is_accessibility_enabled: None, // TODO
},
});
// Only show the wizard if a panel should be displayed
if is_welcome_page_enabled
|| is_move_bundle_page_enabled
|| is_legacy_version_page_enabled
|| is_migrate_page_enabled
|| is_add_path_page_enabled
|| is_accessibility_page_enabled
{
espanso_modulo::wizard::show(WizardOptions {
version: crate::VERSION.to_string(),
is_welcome_page_enabled,
is_move_bundle_page_enabled,
is_legacy_version_page_enabled,
is_migrate_page_enabled,
is_add_path_page_enabled,
is_accessibility_page_enabled,
window_icon_path: icon_paths
.wizard_icon
.map(|path| path.to_string_lossy().to_string()),
welcome_image_path: icon_paths
.logo_no_background
.map(|path| path.to_string_lossy().to_string()),
accessibility_image_1_path: None, // TODO
accessibility_image_2_path: None, // TODO
handlers: WizardHandlers {
is_legacy_version_running: Some(is_legacy_version_running_handler),
backup_and_migrate: Some(backup_and_migrate_handler),
add_to_path: None, // TODO
enable_accessibility: None, // TODO
is_accessibility_enabled: None, // TODO
},
});
// TODO: enable "Add to PATH" page only when NOT in portable mode
// TODO: if the user clicks on "Continue" after unchecking the "ADD to PATH"
// checkbox, we should remember (with the kvs) the choice and avoid asking again.
// TODO: check the wizard return status?
preferences.set_completed_wizard(true);
}
0
}
@ -127,4 +136,6 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
#[cfg(not(feature = "modulo"))]
fn launcher_main(_: CliModuleArgs) -> i32 {
// TODO: handle what happens here
0
}

View File

@ -43,6 +43,7 @@ mod icon;
mod ipc;
mod lock;
mod logging;
mod preferences;
mod util;
const VERSION: &str = env!("CARGO_PKG_VERSION");
@ -289,6 +290,10 @@ fn main() {
// TODO: explain that the register and unregister commands are only meaningful
// when using the system daemon manager on macOS and Linux
// TODO: set the LSEnvironment variable as described here: https://stackoverflow.com/questions/12203377/combined-gui-and-command-line-os-x-app?rq=1
// to detect if the executable was launched inside an AppBundle, and if so, launch the "launcher" handler
// This should only apply when on macOS.
let matches = clap_instance.clone().get_matches();
let log_level = match matches.occurrences_of("v") {
0 | 1 => LevelFilter::Info,

View File

@ -0,0 +1,64 @@
/*
* 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 espanso_kvs::KVS;
use serde::{de::DeserializeOwned, Serialize};
use std::path::Path;
use anyhow::Result;
use super::Preferences;
const HAS_COMPLETED_WIZARD_KEY: &str = "has_completed_wizard";
#[derive(Clone)]
pub struct DefaultPreferences<KVSType: KVS> {
kvs: KVSType,
}
impl<KVSType: KVS> DefaultPreferences<KVSType> {
pub fn new(runtime_dir: &Path, kvs: KVSType) -> Result<Self> {
Ok(Self { kvs })
}
fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
let value = self
.kvs
.get(key)
.expect(&format!("unable to read preference for key {}", key));
value
}
fn set<T: Serialize>(&self, key: &str, value: T) {
self
.kvs
.set(key, value)
.expect(&format!("unable to write preference for key {}", key))
}
}
impl<KVSType: KVS> Preferences for DefaultPreferences<KVSType> {
fn has_completed_wizard(&self) -> bool {
self.get(HAS_COMPLETED_WIZARD_KEY).unwrap_or(false)
}
fn set_completed_wizard(&self, value: bool) {
self.set(HAS_COMPLETED_WIZARD_KEY, value);
}
}

View File

@ -0,0 +1,33 @@
/*
* 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::path::Path;
use anyhow::Result;
mod default;
pub trait Preferences: Send + Sync {
fn has_completed_wizard(&self) -> bool;
fn set_completed_wizard(&self, value: bool);
}
pub fn get_default(runtime_dir: &Path) -> Result<impl Preferences> {
let kvs = espanso_kvs::get_persistent(runtime_dir)?;
default::DefaultPreferences::new(runtime_dir, kvs)
}