feat(core): implement runtime Preferences module
This commit is contained in:
		
							parent
							
								
									cfe44fd861
								
							
						
					
					
						commit
						8fb3826298
					
				
							
								
								
									
										13
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -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"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,4 +15,5 @@ members = [
 | 
			
		|||
  "espanso-modulo",
 | 
			
		||||
  "espanso-migrate",
 | 
			
		||||
  "espanso-mac-utils",
 | 
			
		||||
  "espanso-kvs",
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			@ -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"]
 | 
			
		||||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										64
									
								
								espanso/src/preferences/default.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								espanso/src/preferences/default.rs
									
									
									
									
									
										Normal 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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								espanso/src/preferences/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								espanso/src/preferences/mod.rs
									
									
									
									
									
										Normal 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)
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user