app: better panic handling, macos single app instance
This commit is contained in:
parent
d371d4368b
commit
846e30cb38
4 changed files with 63 additions and 58 deletions
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
62
src/main.rs
62
src/main.rs
|
@ -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);
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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())?;
|
||||||
|
|
Loading…
Reference in a new issue