From f3db1005b5cac6582158fa83d785c1fa95c3f7a5 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Wed, 7 Aug 2024 19:14:11 +0300 Subject: [PATCH] feat: crash report --- Cargo.lock | 8 +++ Cargo.toml | 5 ++ locales/de.yml | 2 + locales/en.yml | 2 + locales/fr.yml | 2 + locales/ru.yml | 2 + locales/tr.yml | 2 + src/gui/views/content.rs | 85 ++++++++++++++++++++++++-------- src/main.rs | 65 +++++++++++++++--------- src/node/config.rs | 16 +++--- src/settings/config.rs | 19 ++++++- src/settings/settings.rs | 32 +++++++----- src/tor/config.rs | 4 +- src/wallet/config.rs | 2 +- src/wallet/connections/config.rs | 4 +- 15 files changed, 182 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7dc537b..04d065e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3803,6 +3803,7 @@ dependencies = [ "arboard", "arti-client", "arti-hyper", + "backtrace", "chrono", "curve25519-dalek 4.1.3", "dark-light", @@ -3839,6 +3840,7 @@ dependencies = [ "nokhwa", "openpnp_capture_sys", "openssl-sys", + "panic-message", "parking_lot 0.12.3", "qrcode", "qrcodegen", @@ -6563,6 +6565,12 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "panic-message" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384e52fd8fbd4cbe3c317e8216260c21a0f9134de108cea8a4dd4e7e152c472d" + [[package]] name = "parking" version = "2.2.0" diff --git a/Cargo.toml b/Cargo.toml index d0c9225..e8df5e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,9 @@ path = "src/main.rs" name="grim" crate-type = ["rlib"] +[profile.release] +debug = 1 + [profile.release-apk] inherits = "release" strip = true @@ -51,6 +54,8 @@ egui_extras = { version = "0.28.1", features = ["image", "svg"] } rust-i18n = "2.3.1" ## other +backtrace = "0.3" +panic-message = "0.3.0" thiserror = "1.0.58" futures = "0.3" dirs = "5.0.1" diff --git a/locales/de.yml b/locales/de.yml index c6fa96c..1be8874 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -26,6 +26,8 @@ theme: 'Theme:' dark: Dunkel light: Hell choose_file: Datei auswählen +crash_report: Absturzbericht +crash_report_warning: Anwendung wurde beim letzten Mal unerwartet geschlossen, Sie können den Absturzbericht mit Entwicklern teilen. wallets: await_conf_amount: Erwarte Bestätigung await_fin_amount: Warten auf die Fertigstellung diff --git a/locales/en.yml b/locales/en.yml index 1c783cd..622e6a5 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -26,6 +26,8 @@ theme: 'Theme:' dark: Dark light: Light choose_file: Choose file +crash_report: Crash report +crash_report_warning: Application closed unexpectedly last time, you can share crash report with developers. wallets: await_conf_amount: Awaiting confirmation await_fin_amount: Awaiting finalization diff --git a/locales/fr.yml b/locales/fr.yml index 64e9879..e5bc04d 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -26,6 +26,8 @@ theme: 'Thème:' dark: Sombre light: Clair choose_file: Choisir un fichier +crash_report: Rapport d'échec +crash_report_warning: L'application s'est fermée de manière inattendue la dernière fois, vous pouvez partager un rapport d'incident avec les développeurs. wallets: await_conf_amount: En attente de confirmation await_fin_amount: En attente de finalisation diff --git a/locales/ru.yml b/locales/ru.yml index 2e2f175..da0ddf1 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -26,6 +26,8 @@ theme: 'Тема:' dark: Тёмная light: Светлая choose_file: Выбрать файл +crash_report: Отчёт о сбое +crash_report_warning: В прошлый раз приложение неожиданно закрылось, вы можете поделиться отчетом о сбое с разработчиками. wallets: await_conf_amount: Ожидает подтверждения await_fin_amount: Ожидает завершения diff --git a/locales/tr.yml b/locales/tr.yml index 286d993..8c07d3f 100644 --- a/locales/tr.yml +++ b/locales/tr.yml @@ -26,6 +26,8 @@ theme: 'Tema:' dark: Karanlik light: Isik choose_file: Dosya seçin +crash_report: Ariza Raporu +crash_report_warning: Uygulama beklenmedik bir sekilde kapandi son kez, kilitlenme raporunu gelistiricilerle paylasabilirsiniz. wallets: await_conf_amount: Onay bekleniyor await_fin_amount: Tamamlanma bekleniyor diff --git a/src/gui/views/content.rs b/src/gui/views/content.rs index 188ed3a..8780813 100644 --- a/src/gui/views/content.rs +++ b/src/gui/views/content.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fs; use std::sync::atomic::{AtomicBool, Ordering}; use egui::os::OperatingSystem; use egui::{Align, Layout, RichText}; @@ -20,10 +21,10 @@ use lazy_static::lazy_static; use crate::gui::Colors; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, View}; -use crate::gui::views::types::ModalContainer; +use crate::gui::views::types::{ModalContainer, ModalPosition}; use crate::node::Node; -use crate::AppConfig; -use crate::gui::icons::{CHECK, CHECK_FAT}; +use crate::{AppConfig, Settings}; +use crate::gui::icons::{CHECK, CHECK_FAT, FILE_X}; use crate::gui::views::network::{NetworkContent, NodeSetup}; use crate::gui::views::wallets::WalletsContent; @@ -63,9 +64,10 @@ impl Default for Content { show_exit_progress: false, first_draw: true, allowed_modal_ids: vec![ - Self::EXIT_MODAL_ID, + Self::EXIT_CONFIRMATION_MODAL, Self::SETTINGS_MODAL, Self::ANDROID_INTEGRATED_NODE_WARNING_MODAL, + Self::CRASH_REPORT_MODAL ], } } @@ -79,11 +81,12 @@ impl ModalContainer for Content { fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, - _: &dyn PlatformCallbacks) { + cb: &dyn PlatformCallbacks) { match modal.id { - Self::EXIT_MODAL_ID => self.exit_modal_content(ui, modal), + Self::EXIT_CONFIRMATION_MODAL => self.exit_modal_content(ui, modal), Self::SETTINGS_MODAL => self.settings_modal_ui(ui, modal), Self::ANDROID_INTEGRATED_NODE_WARNING_MODAL => self.android_warning_modal_ui(ui, modal), + Self::CRASH_REPORT_MODAL => self.crash_report_modal_ui(ui, modal, cb), _ => {} } } @@ -91,12 +94,13 @@ impl ModalContainer for Content { impl Content { /// Identifier for exit confirmation [`Modal`]. - pub const EXIT_MODAL_ID: &'static str = "exit_confirmation_modal"; + pub const EXIT_CONFIRMATION_MODAL: &'static str = "exit_confirmation_modal"; /// Identifier for wallet opening [`Modal`]. pub const SETTINGS_MODAL: &'static str = "settings_modal"; - /// Identifier for integrated node warning [`Modal`] on Android. const ANDROID_INTEGRATED_NODE_WARNING_MODAL: &'static str = "android_node_warning_modal"; + /// Identifier for crash report [`Modal`]. + const CRASH_REPORT_MODAL: &'static str = "crash_report_modal"; /// Default width of side panel at application UI. pub const SIDE_PANEL_WIDTH: f32 = 400.0; @@ -132,16 +136,23 @@ impl Content { self.wallets.ui(ui, cb); }); - // Show integrated node warning on Android if needed. - if self.first_draw && OperatingSystem::from_target_os() == OperatingSystem::Android && - AppConfig::android_integrated_node_warning_needed() { - Modal::new(Self::ANDROID_INTEGRATED_NODE_WARNING_MODAL) - .title(t!("network.node")) - .show(); - } - - // Setup first draw flag. if self.first_draw { + // Show crash report if needed. + if AppConfig::show_crash() { + Modal::new(Self::CRASH_REPORT_MODAL) + .closeable(false) + .position(ModalPosition::Center) + .title(t!("crash_report")) + .show(); + } else { + // Show integrated node warning on Android if needed. + if OperatingSystem::from_target_os() == OperatingSystem::Android && + AppConfig::android_integrated_node_warning_needed() { + Modal::new(Self::ANDROID_INTEGRATED_NODE_WARNING_MODAL) + .title(t!("network.node")) + .show(); + } + } self.first_draw = false; } } @@ -187,9 +198,9 @@ impl Content { NETWORK_PANEL_OPEN.load(Ordering::Relaxed) } - /// Show exit confirmation modal. + /// Show exit confirmation [`Modal`]. pub fn show_exit_modal() { - Modal::new(Self::EXIT_MODAL_ID) + Modal::new(Self::EXIT_CONFIRMATION_MODAL) .title(t!("modal.confirmation")) .show(); } @@ -383,7 +394,7 @@ impl Content { ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network.android_warning")) - .size(15.0) + .size(16.0) .color(Colors::text(false))); }); ui.add_space(8.0); @@ -395,4 +406,38 @@ impl Content { }); ui.add_space(6.0); } + + /// Draw content for integrated node warning [`Modal`] on Android. + fn crash_report_modal_ui(&mut self, + ui: &mut egui::Ui, + modal: &Modal, + cb: &dyn PlatformCallbacks) { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("crash_report_warning")) + .size(16.0) + .color(Colors::text(false))); + ui.add_space(6.0); + // Draw button to share crash report. + let text = format!("{} {}", FILE_X, t!("share")); + View::colored_text_button(ui, text, Colors::blue(), Colors::white_or_black(false), || { + if let Ok(data) = fs::read_to_string(Settings::crash_report_path()) { + cb.share_data(Settings::CRASH_REPORT_FILE_NAME.to_string(), + data.as_bytes().to_vec()).unwrap_or_default() + } + AppConfig::set_show_crash(false); + modal.close(); + }); + }); + ui.add_space(8.0); + View::horizontal_line(ui, Colors::item_stroke()); + ui.add_space(8.0); + ui.vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { + AppConfig::set_show_crash(false); + modal.close(); + }); + }); + ui.add_space(6.0); + } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 957aa07..f50480e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,19 +29,41 @@ fn real_main() { .parse_default_env() .init(); - use grim::gui::platform::Desktop; - use grim::gui::App; + // Setup callback on panic crash. + std::panic::set_hook(Box::new(|info| { + let backtrace = backtrace::Backtrace::new(); + // Format error. + let time = grim::gui::views::View::format_time(chrono::Utc::now().timestamp()); + let target = egui::os::OperatingSystem::from_target_os(); + let ver = grim::VERSION; + let msg = panic_message::panic_info_message(info); + let err = format!("{} - {:?} - v{}\n\n{}\n\n{:?}", time, target, ver, msg, backtrace); + // Save backtrace to file. + let log = grim::Settings::crash_report_path(); + if log.exists() { + std::fs::remove_file(log.clone()).unwrap(); + } + std::fs::write(log, err.as_bytes()).unwrap(); + // Setup flag to show crash after app restart. + grim::AppConfig::set_show_crash(true); + })); + + // Start GUI. + let _ = std::panic::catch_unwind(|| { + start_desktop_gui(); + }); +} + +/// Start GUI with Desktop related setup. +#[allow(dead_code)] +#[cfg(not(target_os = "android"))] +fn start_desktop_gui() { use grim::AppConfig; + use dark_light::Mode; - use std::sync::Arc; - use egui::pos2; - use egui::os::OperatingSystem; - use eframe::icon_data::from_png_bytes; - - let platform = Desktop::default(); + let platform = grim::gui::platform::Desktop::default(); // Setup system theme if not set. - use dark_light::Mode; if let None = AppConfig::dark_theme() { let dark = match dark_light::detect() { Mode::Dark => true, @@ -57,49 +79,46 @@ fn real_main() { let mut viewport = egui::ViewportBuilder::default() .with_min_inner_size([AppConfig::MIN_WIDTH, AppConfig::MIN_HEIGHT]) .with_inner_size([width, height]); - // Setup an icon. - if let Ok(icon) = from_png_bytes(include_bytes!("../img/icon.png")) { - viewport = viewport.with_icon(Arc::new(icon)); + if let Ok(icon) = eframe::icon_data::from_png_bytes(include_bytes!("../img/icon.png")) { + viewport = viewport.with_icon(std::sync::Arc::new(icon)); } - // Setup window position. if let Some((x, y)) = AppConfig::window_pos() { - viewport = viewport.with_position(pos2(x, y)); + viewport = viewport.with_position(egui::pos2(x, y)); } - // Setup window decorations. - let is_mac_os = OperatingSystem::from_target_os() == OperatingSystem::Mac; + let is_mac = egui::os::OperatingSystem::from_target_os() == egui::os::OperatingSystem::Mac; viewport = viewport .with_fullsize_content_view(true) .with_title_shown(false) .with_titlebar_buttons_shown(false) .with_titlebar_shown(false) .with_transparent(true) - .with_decorations(is_mac_os); + .with_decorations(is_mac); let mut options = eframe::NativeOptions { viewport, ..Default::default() }; - // Use Glow renderer for Windows. - let is_windows = OperatingSystem::from_target_os() == OperatingSystem::Windows; - options.renderer = if is_windows { + let win = egui::os::OperatingSystem::from_target_os() == egui::os::OperatingSystem::Windows; + options.renderer = if win { eframe::Renderer::Glow } else { eframe::Renderer::Wgpu }; - match grim::start(options.clone(), grim::app_creator(App::new(platform.clone()))) { + // Start GUI. + match grim::start(options.clone(), grim::app_creator(grim::gui::App::new(platform.clone()))) { Ok(_) => {} Err(e) => { - if is_windows { + if win { panic!("{}", e); } // Start with another renderer on error. options.renderer = eframe::Renderer::Glow; - match grim::start(options, grim::app_creator(App::new(platform))) { + match grim::start(options, grim::app_creator(grim::gui::App::new(platform))) { Ok(_) => {} Err(e) => { panic!("{}", e); diff --git a/src/node/config.rs b/src/node/config.rs index 997d649..b329dfa 100644 --- a/src/node/config.rs +++ b/src/node/config.rs @@ -47,7 +47,7 @@ impl PeersConfig { /// Save peers config to the file. pub fn save(&self) { let chain_type = AppConfig::chain_type(); - let config_path = Settings::get_config_path(Self::FILE_NAME, Some(chain_type.shortname())); + let config_path = Settings::config_path(Self::FILE_NAME, Some(chain_type.shortname())); Settings::write_to_file(self, config_path); // Load changes to node server config. Self::load_to_server_config(); @@ -149,7 +149,7 @@ impl NodeConfig { // Initialize peers config. let peers_config = { let sub_dir = Some(chain_type.shortname()); - let path = Settings::get_config_path(PeersConfig::FILE_NAME, sub_dir); + let path = Settings::config_path(PeersConfig::FILE_NAME, sub_dir); let config = Settings::read_from_file::(path.clone()); if !path.exists() || config.is_err() { Self::save_default_peers_config(chain_type) @@ -161,7 +161,7 @@ impl NodeConfig { // Initialize node config. let node_config = { let sub_dir = Some(chain_type.shortname()); - let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, sub_dir); + let path = Settings::config_path(SERVER_CONFIG_FILE_NAME, sub_dir); let config = Settings::read_from_file::(path.clone()); if !path.exists() || config.is_err() { Self::save_default_node_server_config(chain_type) @@ -176,10 +176,10 @@ impl NodeConfig { /// Save default node config for specified [`ChainTypes`]. fn save_default_node_server_config(chain_type: &ChainTypes) -> ConfigMembers { let sub_dir = Some(chain_type.shortname()); - let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, sub_dir.clone()); + let path = Settings::config_path(SERVER_CONFIG_FILE_NAME, sub_dir.clone()); let mut default_config = GlobalConfig::for_chain(chain_type); - default_config.update_paths(&Settings::get_base_path(sub_dir)); + default_config.update_paths(&Settings::base_path(sub_dir)); let mut config = default_config.members.unwrap(); // Generate random p2p and api ports. @@ -214,7 +214,7 @@ impl NodeConfig { /// Save default peers config for specified [`ChainTypes`]. fn save_default_peers_config(chain_type: &ChainTypes) -> PeersConfig { let sub_dir = Some(chain_type.shortname()); - let path = Settings::get_config_path(PeersConfig::FILE_NAME, sub_dir); + let path = Settings::config_path(PeersConfig::FILE_NAME, sub_dir); let config = PeersConfig::default(); Settings::write_to_file(&config, path); config @@ -223,7 +223,7 @@ impl NodeConfig { /// Save node config to the file. pub fn save(&self) { let sub_dir = Some(self.node.server.chain_type.shortname()); - let config_path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, sub_dir); + let config_path = Settings::config_path(SERVER_CONFIG_FILE_NAME, sub_dir); Settings::write_to_file(&self.node, config_path); } @@ -264,7 +264,7 @@ impl NodeConfig { /// Get path for secret file. fn get_secret_path(chain_type: &ChainTypes, secret_file_name: &str) -> PathBuf { let sub_dir = Some(chain_type.shortname()); - let grin_path = Settings::get_base_path(sub_dir); + let grin_path = Settings::base_path(sub_dir); let mut api_secret_path = grin_path; api_secret_path.push(secret_file_name); api_secret_path diff --git a/src/settings/config.rs b/src/settings/config.rs index 65290a3..445fc23 100644 --- a/src/settings/config.rs +++ b/src/settings/config.rs @@ -49,6 +49,9 @@ pub struct AppConfig { /// Flag to check if dark theme should be used, use system settings if not set. use_dark_theme: Option, + + /// Flag to show crash report when happened. + show_crash: Option } impl Default for AppConfig { @@ -65,6 +68,7 @@ impl Default for AppConfig { y: None, lang: None, use_dark_theme: None, + show_crash: None, } } } @@ -89,7 +93,7 @@ impl AppConfig { /// Save application configuration to the file. pub fn save(&self) { - Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME, None)); + Settings::write_to_file(self, Settings::config_path(Self::FILE_NAME, None)); } /// Change global [`ChainTypes`] and load new [`NodeConfig`]. @@ -241,4 +245,17 @@ impl AppConfig { w_config.use_dark_theme = Some(use_dark); w_config.save(); } + + /// Check if crash report should be shown on application start. + pub fn show_crash() -> bool { + let r_config = Settings::app_config_to_read(); + r_config.show_crash.unwrap_or(false) + } + + /// Setup flag to show crash report on application start. + pub fn set_show_crash(show: bool) { + let mut w_config = Settings::app_config_to_update(); + w_config.show_crash = Some(show); + w_config.save(); + } } \ No newline at end of file diff --git a/src/settings/settings.rs b/src/settings/settings.rs index 6f354e3..0f7e947 100644 --- a/src/settings/settings.rs +++ b/src/settings/settings.rs @@ -33,9 +33,6 @@ lazy_static! { static ref SETTINGS_STATE: Arc = Arc::new(Settings::init()); } -/// Main application directory name. -const MAIN_DIR_NAME: &'static str = ".grim"; - /// Contains initialized configurations. pub struct Settings { /// Application configuration. @@ -49,14 +46,20 @@ pub struct Settings { } impl Settings { + /// Main application directory name. + pub const MAIN_DIR_NAME: &'static str = ".grim"; + + /// Crash report file name. + pub const CRASH_REPORT_FILE_NAME: &'static str = "crash.log"; + /// Initialize settings with app and node configs. fn init() -> Self { // Initialize app config. - let app_config_path = Settings::get_config_path(AppConfig::FILE_NAME, None); + let app_config_path = Settings::config_path(AppConfig::FILE_NAME, None); let app_config = Self::init_config::(app_config_path); // Initialize tor config. - let tor_config_path = Settings::get_config_path(TorConfig::FILE_NAME, None); + let tor_config_path = Settings::config_path(TorConfig::FILE_NAME, None); let tor_config = Self::init_config::(tor_config_path); let chain_type = &app_config.chain_type; @@ -121,13 +124,13 @@ impl Settings { } /// Get base directory path for configuration. - pub fn get_base_path(sub_dir: Option) -> PathBuf { + pub fn base_path(sub_dir: Option) -> PathBuf { // Check if dir exists. let mut path = match dirs::home_dir() { Some(p) => p, None => PathBuf::new(), }; - path.push(MAIN_DIR_NAME); + path.push(Self::MAIN_DIR_NAME); if sub_dir.is_some() { path.push(sub_dir.unwrap()); } @@ -139,10 +142,17 @@ impl Settings { } /// Get configuration file path from provided name and sub-directory if needed. - pub fn get_config_path(config_name: &str, sub_dir: Option) -> PathBuf { - let mut settings_path = Self::get_base_path(sub_dir); - settings_path.push(config_name); - settings_path + pub fn config_path(config_name: &str, sub_dir: Option) -> PathBuf { + let mut path = Self::base_path(sub_dir); + path.push(config_name); + path + } + + /// Get configuration file path from provided name and sub-directory if needed. + pub fn crash_report_path() -> PathBuf { + let mut path = Self::base_path(None); + path.push(Self::CRASH_REPORT_FILE_NAME); + path } /// Read configuration from the file. diff --git a/src/tor/config.rs b/src/tor/config.rs index ab1620b..9b03117 100644 --- a/src/tor/config.rs +++ b/src/tor/config.rs @@ -61,12 +61,12 @@ impl TorConfig { /// Save application configuration to the file. pub fn save(&self) { - Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME, None)); + Settings::write_to_file(self, Settings::config_path(Self::FILE_NAME, None)); } /// Get path from subdirectory name. fn sub_dir_path(name: &str) -> String { - let mut base = Settings::get_base_path(Some(Self::DIR_NAME.to_string())); + let mut base = Settings::base_path(Some(Self::DIR_NAME.to_string())); base.push(name); base.to_str().unwrap().to_string() } diff --git a/src/wallet/config.rs b/src/wallet/config.rs index 13cb3c6..08b262b 100644 --- a/src/wallet/config.rs +++ b/src/wallet/config.rs @@ -125,7 +125,7 @@ impl WalletConfig { /// Get wallets base directory path for provided [`ChainTypes`]. pub fn get_base_path(chain_type: ChainTypes) -> PathBuf { let sub_dir = Some(chain_type.shortname()); - let mut wallets_path = Settings::get_base_path(sub_dir); + let mut wallets_path = Settings::base_path(sub_dir); wallets_path.push(BASE_DIR_NAME); // Create wallets base directory if it doesn't exist. if !wallets_path.exists() { diff --git a/src/wallet/connections/config.rs b/src/wallet/connections/config.rs index 8b54e8a..e790fd5 100644 --- a/src/wallet/connections/config.rs +++ b/src/wallet/connections/config.rs @@ -33,7 +33,7 @@ impl ConnectionsConfig { /// Initialize configuration for provided [`ChainTypes`]. pub fn for_chain_type(chain_type: &ChainTypes) -> Self { - let path = Settings::get_config_path(Self::FILE_NAME, Some(chain_type.shortname())); + let path = Settings::config_path(Self::FILE_NAME, Some(chain_type.shortname())); let parsed = Settings::read_from_file::(path.clone()); if !path.exists() || parsed.is_err() { let default_config = ConnectionsConfig { @@ -57,7 +57,7 @@ impl ConnectionsConfig { pub fn save(&self) { let chain_type = AppConfig::chain_type(); let sub_dir = Some(chain_type.shortname()); - Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME, sub_dir)); + Settings::write_to_file(self, Settings::config_path(Self::FILE_NAME, sub_dir)); } /// Get [`ExternalConnection`] list.