app: better panic handling, macos single app instance

This commit is contained in:
ardocrat 2024-10-08 17:11:45 +03:00
parent d371d4368b
commit 846e30cb38
4 changed files with 63 additions and 58 deletions

View file

@ -138,7 +138,7 @@ impl Content {
if self.first_draw { if self.first_draw {
// Show crash report if needed. // Show crash report if needed.
if AppConfig::show_crash() { if Settings::crash_report_path().exists() {
Modal::new(Self::CRASH_REPORT_MODAL) Modal::new(Self::CRASH_REPORT_MODAL)
.closeable(false) .closeable(false)
.position(ModalPosition::Center) .position(ModalPosition::Center)
@ -415,10 +415,10 @@ impl Content {
let text = format!("{} {}", FILE_X, t!("share")); let text = format!("{} {}", FILE_X, t!("share"));
View::colored_text_button(ui, text, Colors::blue(), Colors::white_or_black(false), || { 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()) { if let Ok(data) = fs::read_to_string(Settings::crash_report_path()) {
cb.share_data(Settings::CRASH_REPORT_FILE_NAME.to_string(), let name = Settings::CRASH_REPORT_FILE_NAME.to_string();
data.as_bytes().to_vec()).unwrap_or_default() let _ = cb.share_data(name, data.as_bytes().to_vec());
} }
AppConfig::set_show_crash(false); Settings::delete_crash_report();
modal.close(); modal.close();
}); });
}); });
@ -427,7 +427,7 @@ impl Content {
ui.add_space(8.0); ui.add_space(8.0);
ui.vertical_centered_justified(|ui| { ui.vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
AppConfig::set_show_crash(false); Settings::delete_crash_report();
modal.close(); modal.close();
}); });
}); });

View file

@ -43,25 +43,39 @@ fn real_main() {
// Setup callback on panic crash. // Setup callback on panic crash.
std::panic::set_hook(Box::new(|info| { std::panic::set_hook(Box::new(|info| {
let backtrace = backtrace::Backtrace::new();
// Format error. // Format error.
let backtrace = backtrace::Backtrace::new();
let time = grim::gui::views::View::format_time(chrono::Utc::now().timestamp()); let time = grim::gui::views::View::format_time(chrono::Utc::now().timestamp());
let target = egui::os::OperatingSystem::from_target_os(); let os = egui::os::OperatingSystem::from_target_os();
let ver = grim::VERSION; let ver = grim::VERSION;
let msg = panic_info_message(info); let msg = panic_info_message(info);
let err = format!("{} - {:?} - v{}\n\n{}\n\n{:?}", time, target, ver, msg, backtrace); let loc = if let Some(location) = info.location() {
format!("{}:{}:{}", location.file(), location.line(), location.column())
} else {
"no location found.".parse().unwrap()
};
let err = format!("{} - {:?} - v{}\n{}\n{}\n\n{:?}", time, os, ver, msg, loc, backtrace);
// Save backtrace to file. // Save backtrace to file.
let log = grim::Settings::crash_report_path(); let log = grim::Settings::crash_report_path();
if log.exists() { if log.exists() {
let _ = std::fs::remove_file(log.clone()); use std::io::{Seek, SeekFrom, Write};
let mut file = std::fs::OpenOptions::new()
.write(true)
.append(true)
.open(log)
.unwrap();
if file.seek(SeekFrom::End(0)).is_ok() {
file.write(err.as_bytes()).unwrap_or_default();
} }
std::fs::write(log, err.as_bytes()).unwrap(); } else {
// Setup flag to show crash after app restart. std::fs::write(log, err.as_bytes()).unwrap_or_default();
grim::AppConfig::set_show_crash(true); }
// Print message error.
println!("{}\n{}", msg, loc);
})); }));
// Start GUI. // Start GUI.
match std::panic::catch_unwind(|| { let _ = std::panic::catch_unwind(|| {
if is_app_running(&data) { if is_app_running(&data) {
return; return;
} else if let Some(data) = data { } else if let Some(data) = data {
@ -70,16 +84,13 @@ fn real_main() {
let platform = grim::gui::platform::Desktop::new(); let platform = grim::gui::platform::Desktop::new();
start_app_socket(platform.clone()); start_app_socket(platform.clone());
start_desktop_gui(platform); start_desktop_gui(platform);
}) { });
Ok(_) => {}
Err(e) => println!("{:?}", e)
}
} }
/// Get panic message from crash payload. /// Get panic message from crash payload.
#[allow(dead_code)] #[allow(dead_code)]
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
fn panic_info_message<'pi>(panic_info: &'pi std::panic::PanicInfo<'_>) -> &'pi str { fn panic_info_message<'pi>(panic_info: &'pi std::panic::PanicHookInfo<'_>) -> &'pi str {
let payload = panic_info.payload(); let payload = panic_info.payload();
// taken from: https://github.com/rust-lang/rust/blob/4b9f4b221b92193c7e95b1beb502c6eb32c3b613/library/std/src/panicking.rs#L194-L200 // taken from: https://github.com/rust-lang/rust/blob/4b9f4b221b92193c7e95b1beb502c6eb32c3b613/library/std/src/panicking.rs#L194-L200
match payload.downcast_ref::<&'static str>() { match payload.downcast_ref::<&'static str>() {
@ -114,7 +125,7 @@ fn start_desktop_gui(platform: grim::gui::platform::Desktop) {
.with_min_inner_size([AppConfig::MIN_WIDTH, AppConfig::MIN_HEIGHT]) .with_min_inner_size([AppConfig::MIN_WIDTH, AppConfig::MIN_HEIGHT])
.with_inner_size([width, height]); .with_inner_size([width, height]);
// Setup an icon. // Setup icon.
if let Ok(icon) = eframe::icon_data::from_png_bytes(include_bytes!("../img/icon.png")) { if let Ok(icon) = eframe::icon_data::from_png_bytes(include_bytes!("../img/icon.png")) {
viewport = viewport.with_icon(std::sync::Arc::new(icon)); viewport = viewport.with_icon(std::sync::Arc::new(icon));
} }
@ -176,19 +187,15 @@ fn is_app_running(data: &Option<String>) -> bool {
let res: Result<(), Box<dyn std::error::Error>> = runtime let res: Result<(), Box<dyn std::error::Error>> = runtime
.block_on(async { .block_on(async {
use interprocess::local_socket::{ use interprocess::local_socket::{
tokio::{prelude::*, Stream}, tokio::{prelude::*, Stream}
GenericFilePath, GenericNamespaced
}; };
use tokio::{ use tokio::{
io::AsyncWriteExt, io::AsyncWriteExt,
}; };
let socket_path = grim::Settings::socket_path(); let socket_path = grim::Settings::socket_path();
let name = if GenericNamespaced::is_supported() { let name = grim::Settings::socket_name(&socket_path)?;
grim::Settings::SOCKET_NAME.to_ns_name::<GenericNamespaced>()?
} else {
socket_path.clone().to_fs_name::<GenericFilePath>()?
};
// Connect to running application socket. // Connect to running application socket.
let conn = Stream::connect(name).await?; let conn = Stream::connect(name).await?;
let data = data.clone().unwrap_or("".to_string()); let data = data.clone().unwrap_or("".to_string());
@ -203,7 +210,7 @@ fn is_app_running(data: &Option<String>) -> bool {
drop((rec, sen)); drop((rec, sen));
Ok(()) Ok(())
}); });
return match res { match res {
Ok(_) => true, Ok(_) => true,
Err(_) => false Err(_) => false
} }
@ -220,7 +227,7 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
.block_on(async { .block_on(async {
use interprocess::local_socket::{ use interprocess::local_socket::{
tokio::{prelude::*, Stream}, tokio::{prelude::*, Stream},
GenericFilePath, GenericNamespaced, Listener, ListenerOptions, Listener, ListenerOptions,
}; };
use std::io; use std::io;
use tokio::{ use tokio::{
@ -238,15 +245,12 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
Ok(buffer) Ok(buffer)
} }
// Setup socket name.
let socket_path = grim::Settings::socket_path(); let socket_path = grim::Settings::socket_path();
let name = if GenericNamespaced::is_supported() {
grim::Settings::SOCKET_NAME.to_ns_name::<GenericNamespaced>()?
} else {
socket_path.clone().to_fs_name::<GenericFilePath>()?
};
if socket_path.exists() { if socket_path.exists() {
let _ = std::fs::remove_file(socket_path); let _ = std::fs::remove_file(&socket_path);
} }
let name = grim::Settings::socket_name(&socket_path)?;
// Create listener. // Create listener.
let opts = ListenerOptions::new().name(name); let opts = ListenerOptions::new().name(name);

View file

@ -49,9 +49,6 @@ pub struct AppConfig {
/// Flag to check if dark theme should be used, use system settings if not set. /// Flag to check if dark theme should be used, use system settings if not set.
use_dark_theme: Option<bool>, use_dark_theme: Option<bool>,
/// Flag to show crash report when happened.
show_crash: Option<bool>
} }
impl Default for AppConfig { impl Default for AppConfig {
@ -68,7 +65,6 @@ impl Default for AppConfig {
y: None, y: None,
lang: None, lang: None,
use_dark_theme: None, use_dark_theme: None,
show_crash: None,
} }
} }
} }
@ -241,17 +237,4 @@ impl AppConfig {
w_config.use_dark_theme = Some(use_dark); w_config.use_dark_theme = Some(use_dark);
w_config.save(); 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();
}
} }

View file

@ -13,15 +13,17 @@
// limitations under the License. // limitations under the License.
use std::fs::{self, File}; use std::fs::{self, File};
use std::io;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use egui::os::OperatingSystem;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
use grin_config::ConfigError; use grin_config::ConfigError;
use interprocess::local_socket::{GenericFilePath, GenericNamespaced, Name, NameType, ToFsName, ToNsName};
use crate::node::NodeConfig; use crate::node::NodeConfig;
use crate::settings::AppConfig; use crate::settings::AppConfig;
@ -127,10 +129,7 @@ impl Settings {
/// Get base directory path for configuration. /// Get base directory path for configuration.
pub fn base_path(sub_dir: Option<String>) -> PathBuf { pub fn base_path(sub_dir: Option<String>) -> PathBuf {
// Check if dir exists. // Check if dir exists.
let mut path = match dirs::home_dir() { let mut path = dirs::home_dir().unwrap_or_else(|| PathBuf::new());
Some(p) => p,
None => PathBuf::new(),
};
path.push(Self::MAIN_DIR_NAME); path.push(Self::MAIN_DIR_NAME);
if sub_dir.is_some() { if sub_dir.is_some() {
path.push(sub_dir.unwrap()); path.push(sub_dir.unwrap());
@ -149,20 +148,39 @@ impl Settings {
socket_path socket_path
} }
/// Get configuration file path from provided name and sub-directory if needed. /// Get desktop application socket name from provided path.
pub fn socket_name(path: &PathBuf) -> io::Result<Name> {
let name = if OperatingSystem::Mac != OperatingSystem::from_target_os() &&
GenericNamespaced::is_supported() {
Self::SOCKET_NAME.to_ns_name::<GenericNamespaced>()?
} else {
path.clone().to_fs_name::<GenericFilePath>()?
};
Ok(name)
}
/// Get configuration file path from provided name and subdirectory if needed.
pub fn config_path(config_name: &str, sub_dir: Option<String>) -> PathBuf { pub fn config_path(config_name: &str, sub_dir: Option<String>) -> PathBuf {
let mut path = Self::base_path(sub_dir); let mut path = Self::base_path(sub_dir);
path.push(config_name); path.push(config_name);
path path
} }
/// Get configuration file path from provided name and sub-directory if needed. /// Get configuration file path from provided name and subdirectory if needed.
pub fn crash_report_path() -> PathBuf { pub fn crash_report_path() -> PathBuf {
let mut path = Self::base_path(None); let mut path = Self::base_path(None);
path.push(Self::CRASH_REPORT_FILE_NAME); path.push(Self::CRASH_REPORT_FILE_NAME);
path path
} }
/// Delete crash report file.
pub fn delete_crash_report() {
let log = Self::crash_report_path();
if log.exists() {
let _ = fs::remove_file(log.clone());
}
}
/// Read configuration from the file. /// Read configuration from the file.
pub fn read_from_file<T: DeserializeOwned>(config_path: PathBuf) -> Result<T, ConfigError> { pub fn read_from_file<T: DeserializeOwned>(config_path: PathBuf) -> Result<T, ConfigError> {
let file_content = fs::read_to_string(config_path.clone())?; let file_content = fs::read_to_string(config_path.clone())?;