Skip to content

Commit

Permalink
refactor(tauri): app setup and unrecoverable error logic (#1262)
Browse files Browse the repository at this point in the history
* move state init pieces into tauri setup function

* refactor unrecoverable startup errors logic
  • Loading branch information
doums authored Oct 7, 2024
1 parent 2f06674 commit 7165852
Show file tree
Hide file tree
Showing 14 changed files with 210 additions and 219 deletions.
6 changes: 5 additions & 1 deletion nym-vpn-app/src-tauri/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ pub enum DbCommands {
},
}

pub fn db_command(db: &Db, command: &DbCommands) -> Result<()> {
pub fn db_command(command: &DbCommands) -> Result<()> {
let db = Db::new().inspect_err(|e| {
error!("failed to get db: {e}");
})?;

match command {
DbCommands::Get { key: k } => {
info!("cli db get {k}");
Expand Down
2 changes: 1 addition & 1 deletion nym-vpn-app/src-tauri/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl Db {
.clone()
.ok_or(anyhow!("failed to get app data dir"))?
.join(DB_DIR);
info!("opening sled db at {}", path.display());
info!("opening db at {}", path.display());
create_dir_all(&path).map_err(|e| {
error!("failed to create db directory {}", path.display());
DbError::Io(e)
Expand Down
16 changes: 6 additions & 10 deletions nym-vpn-app/src-tauri/src/fs/storage.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use anyhow::{anyhow, Context, Result};
use serde::{de::DeserializeOwned, Serialize};
use std::{fmt, path::PathBuf, str};
use tokio::fs;
use std::{fmt, fs, path::PathBuf, str};
use tracing::{debug, error, instrument};

use super::util::{check_dir, check_file};
Expand All @@ -22,14 +21,14 @@ impl<T> AppStorage<T>
where
T: Serialize + DeserializeOwned + Default + fmt::Debug,
{
pub async fn new(dir_path: PathBuf, filename: &str, data: Option<T>) -> Result<Self> {
pub fn new(dir_path: PathBuf, filename: &str, data: Option<T>) -> Result<Self> {
let mut full_path = dir_path.clone();
full_path.push(filename);

// check if the directory exists, if not create it
check_dir(&dir_path)?;
// check if the file exists, if not create it
check_file(&full_path).await?;
check_file(&full_path)?;

Ok(Self {
data: data.unwrap_or_default(),
Expand All @@ -40,10 +39,9 @@ where
}

#[instrument]
pub async fn read(&self) -> Result<T> {
pub fn read(&self) -> Result<T> {
debug!("reading stored data from {}", self.full_path.display());
let content = fs::read(&self.full_path)
.await
.inspect_err(|e| error!("Failed to read file `{}`: {e}", &self.full_path.display()))
.context(format!("Failed to read file {}", self.full_path.display()))?;

Expand All @@ -54,20 +52,18 @@ where
}

#[instrument]
pub async fn write(&self) -> Result<()> {
pub fn write(&self) -> Result<()> {
debug!("writing data to {}", self.full_path.display());
let toml = toml::to_string(&self.data)?;
fs::write(&self.full_path, toml)
.await
.inspect_err(|e| error!("Failed to write to `{}`: {e}", &self.full_path.display()))?;
Ok(())
}

#[instrument]
pub async fn clear(&self) -> Result<()> {
pub fn clear(&self) -> Result<()> {
debug!("clearing data {}", self.full_path.display());
fs::write(&self.full_path, vec![])
.await
.inspect_err(|e| error!("Failed to write to `{}`: {e}", &self.full_path.display()))?;
Ok(())
}
Expand Down
16 changes: 7 additions & 9 deletions nym-vpn-app/src-tauri/src/fs/util.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
use anyhow::{Context, Result};
use std::path::PathBuf;
use tokio::fs::{self, File};
use std::fs;
use std::fs::File;
use std::path::{Path, PathBuf};
use tracing::{debug, error};

/// Check if a directory exists, if not create it including all
/// parent components
pub fn check_dir(path: &PathBuf) -> Result<()> {
if !path.is_dir() {
debug!("directory `{}` does not exist, creating it", path.display());
return std::fs::create_dir_all(path)
return fs::create_dir_all(path)
.inspect_err(|e| error!("Failed to create directory `{}`: {e}", path.display()))
.context(format!("Failed to create directory `{}`", path.display()));
}
Ok(())
}

/// Check if a file exists, if not create it
pub async fn check_file(path: &PathBuf) -> Result<()> {
if !fs::try_exists(&path)
.await
pub fn check_file(path: &PathBuf) -> Result<()> {
if !Path::try_exists(path)
.inspect_err(|e| error!("Failed to check if path exists `{}`: {e}", path.display()))
.context(format!(
"Failed to check if path exists `{}`",
path.display()
))?
{
debug!("file `{}` does not exist, creating it", path.display());
File::create(&path)
.await
File::create(path)
.inspect_err(|e| error!("Failed to create file `{}`: {e}", path.display()))
.context(format!("Failed to create file `{}`", path.display()))?;
return Ok(());
}
Ok(())
}
12 changes: 3 additions & 9 deletions nym-vpn-app/src-tauri/src/grpc/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,9 @@ impl GrpcClient {
// TODO this is dirty, this logic shouldn't be handled in the client side
#[instrument(skip_all)]
pub async fn update_agent(&mut self, pkg: &PackageInfo) -> Result<(), VpndError> {
let mut vpnd = self.vpnd().await?;

let request = Request::new(InfoRequest {});
let response = vpnd.info(request).await.map_err(|e| {
error!("grpc info: {}", e);
VpndError::GrpcError(e)
})?;
let d_info = response.get_ref();
self.user_agent = GrpcClient::user_agent(pkg, Some(d_info));
let d_info = self.vpnd_info().await?;
self.user_agent = GrpcClient::user_agent(pkg, Some(&d_info));
info!("vpnd version: {}", d_info.version);
info!("updated user agent: {:?}", self.user_agent);
Ok(())
}
Expand Down
129 changes: 68 additions & 61 deletions nym-vpn-app/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use std::path::PathBuf;
use std::process::exit;
use std::time::Duration;

use crate::cli::{db_command, Commands};
Expand Down Expand Up @@ -31,6 +30,7 @@ use nym_config::defaults;
use states::app::AppState;
#[cfg(windows)]
use states::app::VpnMode;
use tauri::Manager;
use tokio::sync::Mutex;
use tokio::time::sleep;
use tracing::{debug, error, info, trace, warn};
Expand Down Expand Up @@ -77,69 +77,18 @@ async fn main() -> Result<()> {
}

let context = tauri::generate_context!();
let pkg_info = context.package_info();
info!("app version: {}", pkg_info.version);

if cli.build_info {
print_build_info(context.package_info());
print_build_info(pkg_info);
return Ok(());
}

info!("Creating k/v embedded db");
let Ok(db) = Db::new().inspect_err(|e| {
startup_error::set_error(ErrorKey::from(e), Some(&e.to_string()));
}) else {
startup_error::show_window()?;
exit(1);
};

if let Some(Commands::Db { command: Some(cmd) }) = &cli.command {
return db_command(&db, cmd);
}

let app_config_store = {
let path = APP_CONFIG_DIR
.clone()
.ok_or(anyhow!("failed to get app config dir"))?;
AppStorage::<AppConfig>::new(path, APP_CONFIG_FILE, None)
.await
.inspect_err(|e| error!("Failed to init app config store: {e}"))?
};
debug!(
"app_config_store: {}",
&app_config_store.full_path.display()
);

let app_config = match app_config_store.read().await {
Ok(cfg) => cfg,
Err(e) => {
warn!("failed to read app config: {e}, falling back to default (empty) config");
debug!("clearing the config file");
app_config_store
.clear()
.await
.inspect_err(|e| error!("failed to clear the config file: {e}"))
.ok();
AppConfig::default()
}
};
debug!("app_config: {app_config:?}");

if let Some(env_file) = cli
.network_env
.as_ref()
.or(app_config.network_env_file.as_ref())
{
info!("network environment: custom: {}", env_file.display());
defaults::setup_env(Some(env_file.clone()));
} else {
info!("network environment: mainnet");
defaults::setup_env::<PathBuf>(None);
return db_command(cmd);
}

let app_state = AppState::new(&db, &app_config, &cli);

let mut grpc = GrpcClient::new(&app_config, &cli, context.package_info());
grpc.update_agent(context.package_info()).await.ok();

info!("Starting tauri app");
tauri::Builder::default()
.plugin(tauri_plugin_clipboard_manager::init())
Expand All @@ -149,18 +98,75 @@ async fn main() -> Result<()> {
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_shell::init())
.manage(Mutex::new(app_state))
.manage(app_config)
.manage(cli.clone())
.manage(db.clone())
.manage(grpc.clone())
.setup(move |app| {
info!("app setup");

app.manage(cli.clone());

info!("Creating k/v embedded db");
let Ok(db) = Db::new().inspect_err(|e| {
startup_error::set_error(ErrorKey::from(e), Some(&e.to_string()));
}) else {
startup_error::show_window(app.handle())?;
return Ok(());
};
app.manage(db.clone());

// TODO remove when two-hop is supported on Windows
#[cfg(windows)]
db.insert(Key::VpnMode, VpnMode::Mixnet)?;

let app_config_store = {
let path = APP_CONFIG_DIR
.clone()
.ok_or(anyhow!("failed to get app config dir"))?;
AppStorage::<AppConfig>::new(path, APP_CONFIG_FILE, None)
.inspect_err(|e| error!("Failed to init app config store: {e}"))?
};
debug!(
"app_config_store: {}",
&app_config_store.full_path.display()
);

let app_config = match app_config_store.read() {
Ok(cfg) => cfg,
Err(e) => {
warn!("failed to read app config: {e}, falling back to default (empty) config");
debug!("clearing the config file");
app_config_store
.clear()
.inspect_err(|e| error!("failed to clear the config file: {e}"))
.ok();
AppConfig::default()
}
};
debug!("app_config: {app_config:?}");

if let Some(env_file) = cli
.network_env
.as_ref()
.or(app_config.network_env_file.as_ref())
{
info!("network environment: custom: {}", env_file.display());
defaults::setup_env(Some(env_file.clone()));
} else {
info!("network environment: mainnet");
defaults::setup_env::<PathBuf>(None);
}

let app_state = AppState::new(&db, &app_config, &cli);
app.manage(Mutex::new(app_state));

let pkg_info = app.package_info().clone();
let grpc = GrpcClient::new(&app_config, &cli, &pkg_info);
let mut c_grpc = grpc.clone();
tokio::spawn(async move {
c_grpc.update_agent(&pkg_info).await.ok();
});

app.manage(app_config);
app.manage(grpc.clone());

let app_win = AppWindow::new(app.handle(), MAIN_WINDOW_LABEL)?;
app_win.restore_size(&db)?;
app_win.restore_position(&db)?;
Expand Down Expand Up @@ -230,6 +236,7 @@ async fn main() -> Result<()> {
cmd_daemon::daemon_status,
cmd_daemon::daemon_info,
cmd_fs::log_dir,
startup::startup_error,
])
// keep the app running in the background on window close request
.on_window_event(|win, event| {
Expand Down
Loading

0 comments on commit 7165852

Please sign in to comment.