mirror of
https://github.com/jwinterm/grim.git
synced 2026-10-17 06:11:26 +02:00
Ability to change data location (#50)
Closes https://code.gri.mw/GUI/grim/issues/9 - Change node chain data directory - Change wallet data directory - Check for valid bridge Tor binary Reviewed-on: https://code.gri.mw/GUI/grim/pulls/50
This commit is contained in:
@@ -33,6 +33,9 @@ crash_report_warning: Anwendung wurde beim letzten Mal unerwartet geschlossen, S
|
||||
confirmation: Bestätigung
|
||||
enter_url: URL eingeben
|
||||
max_short: MAX
|
||||
files_location: Dateistandort
|
||||
moving_files: Dateien verschieben
|
||||
wrong_path_error: Falscher Weg angegeben
|
||||
wallets:
|
||||
await_conf_amount: Erwarte Bestätigung
|
||||
await_fin_amount: Warten auf die Fertigstellung
|
||||
|
||||
@@ -33,6 +33,9 @@ crash_report_warning: Application closed unexpectedly last time, you can share c
|
||||
confirmation: Confirmation
|
||||
enter_url: Enter URL
|
||||
max_short: MAX
|
||||
files_location: Files location
|
||||
moving_files: Moving files
|
||||
wrong_path_error: Wrong path specified
|
||||
wallets:
|
||||
await_conf_amount: Awaiting confirmation
|
||||
await_fin_amount: Awaiting finalization
|
||||
|
||||
@@ -33,6 +33,9 @@ crash_report_warning: L'application s'est fermée de manière inattendue la dern
|
||||
confirmation: Confirmation
|
||||
enter_url: Entrez l'URL
|
||||
max_short: MAX
|
||||
files_location: Emplacement du fichier
|
||||
moving_files: Déplacer des fichiers
|
||||
wrong_path_error: Chemin incorrect spécifié
|
||||
wallets:
|
||||
await_conf_amount: En attente de confirmation
|
||||
await_fin_amount: En attente de finalisation
|
||||
|
||||
@@ -33,6 +33,9 @@ crash_report_warning: В прошлый раз приложение неожид
|
||||
confirmation: Подтверждение
|
||||
enter_url: Введите URL-адрес
|
||||
max_short: МАКС
|
||||
files_location: Расположение файлов
|
||||
moving_files: Перемещение файлов
|
||||
wrong_path_error: Указан неправильный путь
|
||||
wallets:
|
||||
await_conf_amount: Ожидает подтверждения
|
||||
await_fin_amount: Ожидает завершения
|
||||
|
||||
@@ -33,6 +33,9 @@ crash_report_warning: Uygulama beklenmedik bir sekilde kapandi son kez, kilitlen
|
||||
confirmation: Onay
|
||||
enter_url: URL'yi girin
|
||||
max_short: MAKS
|
||||
files_location: Dosya konumu
|
||||
moving_files: Dosyalari Tasima
|
||||
wrong_path_error: Yanlis yol belirtildi
|
||||
wallets:
|
||||
await_conf_amount: Onay bekleniyor
|
||||
await_fin_amount: Tamamlanma bekleniyor
|
||||
|
||||
@@ -33,6 +33,9 @@ crash_report_warning: 上次应用程序意外关闭,您可以报告开发人
|
||||
confirmation: 确认
|
||||
enter_url: 输入 URL
|
||||
max_short: 最大數量
|
||||
files_location: 檔案位置
|
||||
moving_files: 檔案移動
|
||||
wrong_path_error: 指定錯誤路徑
|
||||
wallets:
|
||||
await_conf_amount: 等待确认中
|
||||
await_fin_amount: 等待确定中
|
||||
|
||||
@@ -194,7 +194,7 @@ impl Content {
|
||||
/// Draw exit confirmation modal content.
|
||||
fn exit_modal_content(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
if self.show_exit_progress {
|
||||
if !Node::is_running() {
|
||||
if !Node::is_running() && !Node::data_dir_changing() {
|
||||
self.exit_allowed = true;
|
||||
cb.exit();
|
||||
Modal::close();
|
||||
@@ -203,7 +203,12 @@ impl Content {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::small_loading_spinner(ui);
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("sync_status.shutdown"))
|
||||
let exit_status_text = if Node::data_dir_changing() {
|
||||
t!("moving_files")
|
||||
} else {
|
||||
t!("sync_status.shutdown")
|
||||
};
|
||||
ui.label(RichText::new(exit_status_text)
|
||||
.size(17.0)
|
||||
.color(Colors::text(false)));
|
||||
});
|
||||
@@ -228,7 +233,7 @@ impl Content {
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button_ui(ui, t!("modal_exit.exit"), Colors::white_or_black(false), |_| {
|
||||
if !Node::is_running() {
|
||||
if !Node::is_running() && !Node::data_dir_changing() {
|
||||
self.exit_allowed = true;
|
||||
cb.exit();
|
||||
Modal::close();
|
||||
|
||||
+41
-33
@@ -39,6 +39,8 @@ pub struct FilePickContent {
|
||||
/// Flag to check if file is picking.
|
||||
file_picking: Arc<AtomicBool>,
|
||||
|
||||
/// Flag to check if folder should be picked.
|
||||
pick_folder: bool,
|
||||
/// Flag to parse file content after pick.
|
||||
parse_file: bool,
|
||||
/// Flag to check if file is parsing.
|
||||
@@ -54,12 +56,19 @@ impl FilePickContent {
|
||||
content_type,
|
||||
active: false,
|
||||
file_picking: Arc::new(AtomicBool::new(false)),
|
||||
pick_folder: false,
|
||||
parse_file: true,
|
||||
file_parsing: Arc::new(AtomicBool::new(false)),
|
||||
file_parsing_result: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Pick folder.
|
||||
pub fn pick_folder(mut self) -> Self {
|
||||
self.pick_folder = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Do not parse file content.
|
||||
pub fn no_parse(mut self) -> Self {
|
||||
self.parse_file = false;
|
||||
@@ -79,7 +88,11 @@ impl FilePickContent {
|
||||
if let Some(path) = cb.picked_file() {
|
||||
self.file_picking.store(false, Ordering::Relaxed);
|
||||
if !path.is_empty() {
|
||||
self.on_file_pick(path);
|
||||
if self.parse_file {
|
||||
self.parse_file(path);
|
||||
} else {
|
||||
pick(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if self.file_parsing.load(Ordering::Relaxed) {
|
||||
@@ -106,29 +119,15 @@ impl FilePickContent {
|
||||
match self.content_type {
|
||||
FilePickContentType::Button => {
|
||||
let text = format!("{} {}", ARCHIVE_BOX, t!("choose_file"));
|
||||
View::colored_text_button(ui,
|
||||
text,
|
||||
Colors::blue(),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
if let Some(path) = cb.pick_file() {
|
||||
if !self.parse_file {
|
||||
pick(path);
|
||||
return;
|
||||
}
|
||||
self.on_file_pick(path);
|
||||
}
|
||||
});
|
||||
let text_color = Colors::blue();
|
||||
let fill = Colors::white_or_black(false);
|
||||
View::colored_text_button(ui, text, text_color, fill, || {
|
||||
self.on_file_pick(pick, cb);
|
||||
});
|
||||
}
|
||||
FilePickContentType::ItemButton(r) => {
|
||||
View::item_button(ui, r, ARCHIVE_BOX, Some(Colors::blue()), || {
|
||||
if let Some(path) = cb.pick_file() {
|
||||
if !self.parse_file {
|
||||
pick(path);
|
||||
return;
|
||||
}
|
||||
self.on_file_pick(path);
|
||||
}
|
||||
self.on_file_pick(pick, cb);
|
||||
});
|
||||
}
|
||||
FilePickContentType::Tab => {
|
||||
@@ -138,30 +137,39 @@ impl FilePickContent {
|
||||
false => None
|
||||
};
|
||||
View::tab_button(ui, ARCHIVE_BOX, Some(Colors::blue()), active, |_| {
|
||||
if let Some(path) = cb.pick_file() {
|
||||
if !self.parse_file {
|
||||
pick(path);
|
||||
return;
|
||||
}
|
||||
self.on_file_pick(path);
|
||||
}
|
||||
self.on_file_pick(pick, cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle picked file path.
|
||||
fn on_file_pick(&self, path: String) {
|
||||
/// Handle pick file request.
|
||||
fn on_file_pick(&self, on_pick: impl FnOnce(String), cb: &dyn PlatformCallbacks) {
|
||||
let path = if self.pick_folder {
|
||||
cb.pick_folder()
|
||||
} else {
|
||||
cb.pick_file()
|
||||
};
|
||||
if path.is_none() {
|
||||
return;
|
||||
}
|
||||
let path = path.unwrap();
|
||||
// Wait for asynchronous file pick result if path is empty.
|
||||
if path.is_empty() {
|
||||
self.file_picking.store(true, Ordering::Relaxed);
|
||||
return;
|
||||
}
|
||||
// Do not parse result.
|
||||
if !self.parse_file {
|
||||
return;
|
||||
// Parse result if needed.
|
||||
if self.parse_file {
|
||||
self.parse_file(path);
|
||||
} else {
|
||||
on_pick(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle picked file path.
|
||||
fn parse_file(&self, path: String) {
|
||||
self.file_parsing.store(true, Ordering::Relaxed);
|
||||
let result = self.file_parsing_result.clone();
|
||||
thread::spawn(move || {
|
||||
|
||||
@@ -274,7 +274,7 @@ impl NetworkContent {
|
||||
// Setup values for title panel.
|
||||
let title_text = self.node_tab_content.get_type().title();
|
||||
let subtitle_text = Node::get_sync_status_text();
|
||||
let not_syncing = Node::not_syncing();
|
||||
let not_syncing = Node::not_syncing() && !Node::data_dir_changing();
|
||||
let title_content = if show_settings {
|
||||
TitleContentType::Title(t!("settings"))
|
||||
} else if !show_connections {
|
||||
|
||||
@@ -12,21 +12,28 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Id, RichText};
|
||||
use eframe::emath::Align;
|
||||
use eframe::epaint::StrokeKind;
|
||||
use egui::{Id, Layout, RichText};
|
||||
use grin_core::global::ChainTypes;
|
||||
|
||||
use crate::gui::icons::{CLOCK_CLOCKWISE, COMPUTER_TOWER, PLUG, POWER, SHIELD, SHIELD_SLASH};
|
||||
use crate::gui::icons::{CLOCK_CLOCKWISE, COMPUTER_TOWER, FOLDERS, PENCIL, PLUG, POWER, SHIELD, SHIELD_SLASH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::settings::NetworkSettings;
|
||||
use crate::gui::views::network::NetworkContent;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::views::{FilePickContent, FilePickContentType, Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::node::{Node, NodeConfig};
|
||||
use crate::AppConfig;
|
||||
|
||||
/// Integrated node general setup section content.
|
||||
pub struct NodeSetup {
|
||||
/// Data path value value for [`Modal`].
|
||||
data_path_edit: String,
|
||||
/// Button to pick directory for chain data.
|
||||
pick_data_dir: FilePickContent,
|
||||
|
||||
/// IP Addresses available at system.
|
||||
available_ips: Vec<String>,
|
||||
|
||||
@@ -45,20 +52,26 @@ pub struct NodeSetup {
|
||||
ftl_edit: String,
|
||||
}
|
||||
|
||||
/// Identifier for chain data path value [`Modal`].
|
||||
const DATA_PATH_MODAL: &'static str = "node_data_path";
|
||||
/// Identifier for API port value [`Modal`].
|
||||
pub const API_PORT_MODAL: &'static str = "api_port";
|
||||
const API_PORT_MODAL: &'static str = "node_api_port";
|
||||
/// Identifier for API secret value [`Modal`].
|
||||
pub const API_SECRET_MODAL: &'static str = "api_secret";
|
||||
const API_SECRET_MODAL: &'static str = "node_api_secret";
|
||||
/// Identifier for Foreign API secret value [`Modal`].
|
||||
pub const FOREIGN_API_SECRET_MODAL: &'static str = "foreign_api_secret";
|
||||
const FOREIGN_API_SECRET_MODAL: &'static str = "node_foreign_api_secret";
|
||||
/// Identifier for FTL value [`Modal`].
|
||||
pub const FTL_MODAL: &'static str = "ftl";
|
||||
const FTL_MODAL: &'static str = "node_ftl";
|
||||
|
||||
impl Default for NodeSetup {
|
||||
fn default() -> Self {
|
||||
let (api_ip, api_port) = NodeConfig::get_api_ip_port();
|
||||
let is_api_port_available = NodeConfig::is_api_port_available(&api_ip, &api_port);
|
||||
Self {
|
||||
data_path_edit: NodeConfig::get_chain_data_path(),
|
||||
pick_data_dir: FilePickContent::new(
|
||||
FilePickContentType::ItemButton(View::item_rounding(0, 1, true))
|
||||
).no_parse().pick_folder(),
|
||||
available_ips: NodeConfig::get_ip_addrs(),
|
||||
api_port_edit: api_port,
|
||||
api_port_available_edit: is_api_port_available,
|
||||
@@ -72,6 +85,7 @@ impl Default for NodeSetup {
|
||||
impl ContentContainer for NodeSetup {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![
|
||||
DATA_PATH_MODAL,
|
||||
API_PORT_MODAL,
|
||||
API_SECRET_MODAL,
|
||||
FOREIGN_API_SECRET_MODAL,
|
||||
@@ -84,6 +98,7 @@ impl ContentContainer for NodeSetup {
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
DATA_PATH_MODAL => self.data_path_edit_modal_ui(ui, cb),
|
||||
API_PORT_MODAL => self.api_port_modal(ui, modal, cb),
|
||||
API_SECRET_MODAL => self.secret_modal(ui, modal, cb),
|
||||
FOREIGN_API_SECRET_MODAL => self.secret_modal(ui, modal, cb),
|
||||
@@ -92,7 +107,7 @@ impl ContentContainer for NodeSetup {
|
||||
}
|
||||
}
|
||||
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, _: &dyn PlatformCallbacks) {
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
View::sub_title(ui, format!("{} {}", COMPUTER_TOWER, t!("network_settings.server")));
|
||||
View::horizontal_line(ui, Colors::stroke());
|
||||
ui.add_space(6.0);
|
||||
@@ -102,7 +117,8 @@ impl ContentContainer for NodeSetup {
|
||||
ui.add_space(2.0);
|
||||
|
||||
// Show loading indicator or controls to stop/start/restart node.
|
||||
if Node::is_stopping() || Node::is_restarting() || Node::is_starting() {
|
||||
if Node::is_stopping() || Node::is_restarting() || Node::is_starting()
|
||||
|| Node::data_dir_changing() {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(8.0);
|
||||
View::small_loading_spinner(ui);
|
||||
@@ -154,6 +170,13 @@ impl ContentContainer for NodeSetup {
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show data location selection for Desktop when it already started or turned off.
|
||||
if !Node::is_restarting() && !Node::is_stopping() && !Node::is_starting() &&
|
||||
View::is_desktop() {
|
||||
self.pick_data_dir_ui(ui, cb);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
if self.available_ips.is_empty() {
|
||||
// Show message when IP addresses are not available on the system.
|
||||
NetworkSettings::no_ip_address_ui(ui);
|
||||
@@ -212,6 +235,92 @@ impl ContentContainer for NodeSetup {
|
||||
}
|
||||
|
||||
impl NodeSetup {
|
||||
/// Draw content to change chain data directory.
|
||||
fn pick_data_dir_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(56.0);
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(0, 1, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
self.pick_data_dir.ui(ui, cb, |path| {
|
||||
Node::change_data_dir(path);
|
||||
});
|
||||
View::item_button(ui, View::item_rounding(1, 3, true), PENCIL, None, || {
|
||||
self.data_path_edit = NodeConfig::get_chain_data_path();
|
||||
// Show chain data path edit modal.
|
||||
Modal::new(DATA_PATH_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network.node"))
|
||||
.show();
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
let path = NodeConfig::get_chain_data_path();
|
||||
View::ellipsize_text(ui, path, 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let desc = format!("{} {}", FOLDERS, t!("files_location"));
|
||||
ui.label(RichText::new(desc).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw data path input [`Modal`] content.
|
||||
fn data_path_edit_modal_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
let on_save = |path: &String| {
|
||||
Node::change_data_dir(path.clone());
|
||||
Modal::close();
|
||||
};
|
||||
ui.label(RichText::new(format!("{}:", t!("files_location")))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw chain data path text edit.
|
||||
let mut edit = TextEdit::new(Id::from(DATA_PATH_MODAL)).paste();
|
||||
edit.ui(ui, &mut self.data_path_edit, cb);
|
||||
if edit.enter_pressed {
|
||||
on_save(&self.data_path_edit);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Show modal buttons.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
Modal::close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
on_save(&self.data_path_edit);
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw [`ChainTypes`] setup content.
|
||||
pub fn chain_type_ui(ui: &mut egui::Ui) {
|
||||
ui.vertical_centered(|ui| {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::fs;
|
||||
use egui::{Align, Id, Layout, RichText, StrokeKind};
|
||||
use egui::os::OperatingSystem;
|
||||
use url::Url;
|
||||
@@ -412,6 +413,10 @@ impl TorSettingsContent {
|
||||
fn bridge_bin_edit_modal_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let on_save = |c: &mut TorSettingsContent| {
|
||||
let bridge = TorConfig::get_bridge().unwrap();
|
||||
let exists = fs::exists(&c.bridge_bin_path_edit).unwrap_or_default();
|
||||
if !exists {
|
||||
return;
|
||||
}
|
||||
if bridge.binary_path() != c.bridge_bin_path_edit {
|
||||
TorBridge::save_bridge_bin_path(&bridge, c.bridge_bin_path_edit.clone());
|
||||
c.settings_changed = true;
|
||||
|
||||
@@ -542,7 +542,7 @@ impl WalletsContent {
|
||||
ui.painter().rect(rect, rounding, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
if !wallet.is_open() {
|
||||
if !wallet.is_open() && !wallet.files_moving() {
|
||||
// Show button to open closed wallet.
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), FOLDER_OPEN, None, || {
|
||||
self.show_opening_modal(wallet, None, cb);
|
||||
|
||||
@@ -473,7 +473,7 @@ fn sync_ui(ui: &mut egui::Ui, wallet: &Wallet) -> bool {
|
||||
if wallet.is_repairing() && !wallet.sync_error() {
|
||||
sync_progress_ui(ui, wallet);
|
||||
return true;
|
||||
} else if wallet.is_closing() {
|
||||
} else if wallet.is_closing() || wallet.files_moving() {
|
||||
sync_progress_ui(ui, wallet);
|
||||
return true;
|
||||
} else if wallet.get_current_connection() == ConnectionMethod::Integrated {
|
||||
@@ -539,7 +539,9 @@ fn sync_progress_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
let int_ready = Node::get_sync_status() == Some(SyncStatus::NoSync);
|
||||
let info_progress = wallet.info_sync_progress();
|
||||
|
||||
if wallet.is_closing() {
|
||||
if wallet.files_moving() {
|
||||
t!("moving_files")
|
||||
} else if wallet.is_closing() {
|
||||
t!("wallets.wallet_closing")
|
||||
} else if int_node && !int_ready {
|
||||
t!("wallets.node_loading", "settings" => GEAR_FINE)
|
||||
|
||||
@@ -12,14 +12,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Id, RichText};
|
||||
use eframe::emath::Align;
|
||||
use eframe::epaint::StrokeKind;
|
||||
use egui::{Id, Layout, RichText};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CLOCK_COUNTDOWN, PASSWORD, PENCIL};
|
||||
use crate::gui::icons::{CLOCK_COUNTDOWN, FOLDERS, FOLDER_USER, PASSWORD, PENCIL};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::gui::views::wallets::wallet::types::WalletContentContainer;
|
||||
use crate::gui::views::{FilePickContent, FilePickContentType, Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Common wallet settings content.
|
||||
@@ -34,6 +36,11 @@ pub struct CommonSettings {
|
||||
/// New wallet password [`Modal`] value.
|
||||
new_pass_edit: String,
|
||||
|
||||
/// Data path value value for [`Modal`].
|
||||
data_path_edit: String,
|
||||
/// Button to pick directory for wallet data.
|
||||
pick_data_dir: FilePickContent,
|
||||
|
||||
/// Minimum confirmations number value.
|
||||
min_confirmations_edit: String,
|
||||
}
|
||||
@@ -42,6 +49,8 @@ pub struct CommonSettings {
|
||||
const NAME_EDIT_MODAL: &'static str = "wallet_name_edit_modal";
|
||||
/// Identifier for wallet password [`Modal`].
|
||||
const PASS_EDIT_MODAL: &'static str = "wallet_pass_edit_modal";
|
||||
/// Identifier for wallet data path value [`Modal`].
|
||||
const DATA_PATH_MODAL: &'static str = "wallet_data_path";
|
||||
/// Identifier for minimum confirmations [`Modal`].
|
||||
const MIN_CONFIRMATIONS_EDIT_MODAL: &'static str = "wallet_min_conf_edit_modal";
|
||||
|
||||
@@ -50,6 +59,7 @@ impl WalletContentContainer for CommonSettings {
|
||||
vec![
|
||||
NAME_EDIT_MODAL,
|
||||
PASS_EDIT_MODAL,
|
||||
DATA_PATH_MODAL,
|
||||
MIN_CONFIRMATIONS_EDIT_MODAL
|
||||
]
|
||||
}
|
||||
@@ -60,66 +70,25 @@ impl WalletContentContainer for CommonSettings {
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
NAME_EDIT_MODAL => {
|
||||
self.name_modal_ui(ui, wallet, modal, cb);
|
||||
}
|
||||
PASS_EDIT_MODAL => {
|
||||
self.pass_modal_ui(ui, wallet, modal, cb);
|
||||
}
|
||||
MIN_CONFIRMATIONS_EDIT_MODAL => {
|
||||
self.min_conf_modal_ui(ui, wallet, modal, cb);
|
||||
}
|
||||
NAME_EDIT_MODAL => self.name_modal_ui(ui, wallet, modal, cb),
|
||||
PASS_EDIT_MODAL => self.pass_modal_ui(ui, wallet, modal, cb),
|
||||
DATA_PATH_MODAL => self.data_path_modal_ui(ui, wallet, cb),
|
||||
MIN_CONFIRMATIONS_EDIT_MODAL => self.min_conf_modal_ui(ui, wallet, modal, cb),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, _: &dyn PlatformCallbacks) {
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(8.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
let config = wallet.get_config();
|
||||
// Show wallet name.
|
||||
ui.add_space(2.0);
|
||||
ui.label(RichText::new(t!("wallets.name"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray()));
|
||||
ui.add_space(2.0);
|
||||
ui.label(RichText::new(&config.name)
|
||||
.size(16.0)
|
||||
.color(Colors::white_or_black(true)));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Show wallet name setup.
|
||||
let name_text = format!("{} {}", PENCIL, t!("change"));
|
||||
View::button(ui, name_text, Colors::white_or_black(false), || {
|
||||
self.name_edit = config.name;
|
||||
// Show wallet name modal.
|
||||
Modal::new(NAME_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.pass")).size(16.0).color(Colors::gray()));
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show wallet password setup.
|
||||
let pass_text = format!("{} {}", PASSWORD, t!("change"));
|
||||
View::button(ui, pass_text, Colors::white_or_black(false), || {
|
||||
// Setup modal values.
|
||||
self.old_pass_edit = "".to_string();
|
||||
self.new_pass_edit = "".to_string();
|
||||
self.wrong_pass = false;
|
||||
// Show wallet password modal.
|
||||
Modal::new(PASS_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
self.name_ui(ui, config.name);
|
||||
// Show data dir for desktop.
|
||||
if View::is_desktop() {
|
||||
ui.add_space(-4.0);
|
||||
self.data_dir_ui(ui, wallet, cb);
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.min_tx_conf_count")).size(16.0).color(Colors::gray()));
|
||||
ui.add_space(6.0);
|
||||
@@ -135,7 +104,7 @@ impl WalletContentContainer for CommonSettings {
|
||||
.show();
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Setup ability to post wallet transactions with Dandelion.
|
||||
View::checkbox(ui, wallet.can_use_dandelion(), t!("wallets.use_dandelion"), || {
|
||||
@@ -144,7 +113,7 @@ impl WalletContentContainer for CommonSettings {
|
||||
|
||||
ui.add_space(6.0);
|
||||
View::horizontal_line(ui, Colors::stroke());
|
||||
ui.add_space(6.0);
|
||||
ui.add_space(4.0);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -156,12 +125,69 @@ impl Default for CommonSettings {
|
||||
wrong_pass: false,
|
||||
old_pass_edit: "".to_string(),
|
||||
new_pass_edit: "".to_string(),
|
||||
data_path_edit: "".to_string(),
|
||||
pick_data_dir: FilePickContent::new(
|
||||
FilePickContentType::ItemButton(View::item_rounding(1, 2, true))
|
||||
).no_parse().pick_folder(),
|
||||
min_confirmations_edit: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonSettings {
|
||||
/// Draw content to change wallet name and password.
|
||||
fn name_ui(&mut self, ui: &mut egui::Ui, name: String) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(56.0);
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = if View::is_desktop() {
|
||||
View::item_rounding(0, 2, false)
|
||||
} else {
|
||||
View::item_rounding(0, 1, false)
|
||||
};
|
||||
ui.painter().rect(bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
View::item_button(ui, View::item_rounding(0, 2, true), PASSWORD, None, || {
|
||||
self.old_pass_edit = "".to_string();
|
||||
self.new_pass_edit = "".to_string();
|
||||
self.wrong_pass = false;
|
||||
// Show wallet password modal.
|
||||
Modal::new(PASS_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
});
|
||||
View::item_button(ui, View::item_rounding(1, 3, true), PENCIL, None, || {
|
||||
self.name_edit = name.clone();
|
||||
// Show wallet name modal.
|
||||
Modal::new(NAME_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
View::ellipsize_text(ui, name, 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let desc = format!("{} {}", FOLDER_USER, t!("wallets.name").replace(":", ""));
|
||||
ui.label(RichText::new(desc).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw wallet name [`Modal`] content.
|
||||
fn name_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
@@ -307,6 +333,95 @@ impl CommonSettings {
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw content to change wallet data directory.
|
||||
fn data_dir_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(56.0);
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(1, 2, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
self.pick_data_dir.ui(ui, cb, |path| {
|
||||
wallet.change_data_path(path);
|
||||
});
|
||||
View::item_button(ui, View::item_rounding(1, 3, true), PENCIL, None, || {
|
||||
self.data_path_edit = wallet.get_config().data_path.unwrap_or_default();
|
||||
// Show chain data path edit modal.
|
||||
Modal::new(DATA_PATH_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
let path = wallet.get_config().data_path.unwrap_or_default();
|
||||
View::ellipsize_text(ui, path, 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let desc = format!("{} {}", FOLDERS, t!("files_location"));
|
||||
ui.label(RichText::new(desc).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw data path input [`Modal`] content.
|
||||
fn data_path_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
let on_save = |path: &String| {
|
||||
wallet.change_data_path(path.clone());
|
||||
Modal::close();
|
||||
};
|
||||
ui.label(RichText::new(format!("{}:", t!("files_location")))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw chain data path text edit.
|
||||
let mut edit = TextEdit::new(Id::from(DATA_PATH_MODAL)).paste();
|
||||
edit.ui(ui, &mut self.data_path_edit, cb);
|
||||
if edit.enter_pressed {
|
||||
on_save(&self.data_path_edit);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Show modal buttons.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
Modal::close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
on_save(&self.data_path_edit);
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw wallet name [`Modal`] content.
|
||||
fn min_conf_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
|
||||
@@ -107,7 +107,7 @@ impl WalletContentContainer for RecoverySettings {
|
||||
format!("{} {}", LIFEBUOY, t!("wallets.recover")),
|
||||
Colors::green(),
|
||||
Colors::white_or_black(false), || {
|
||||
wallet.delete_db(true);
|
||||
wallet.delete_db();
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.restore_wallet_desc"))
|
||||
|
||||
@@ -119,7 +119,12 @@ impl WalletTransportContent {
|
||||
ui.vertical(|ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Show button to show QR code address.
|
||||
View::item_button(ui, View::item_rounding(1, 2, true), QR_CODE, None, || {
|
||||
let r = if awaiting_balance {
|
||||
View::item_rounding(1, 3, true)
|
||||
} else {
|
||||
View::item_rounding(1, 2, true)
|
||||
};
|
||||
View::item_button(ui, r, QR_CODE, None, || {
|
||||
self.qr_address_content = Some(QrCodeContent::new(addr.clone(), false)
|
||||
.with_max_size(320.0));
|
||||
});
|
||||
|
||||
@@ -304,6 +304,19 @@ impl NodeConfig {
|
||||
true
|
||||
}
|
||||
|
||||
/// Get chain data path.
|
||||
pub fn get_chain_data_path() -> String {
|
||||
let r_config = Settings::node_config_to_read();
|
||||
r_config.node.server.db_root.clone()
|
||||
}
|
||||
|
||||
/// Save chain data path.
|
||||
pub fn save_chain_data_path(path: String) {
|
||||
let mut w_config = Settings::node_config_to_update();
|
||||
w_config.node.server.db_root = path;
|
||||
w_config.save();
|
||||
}
|
||||
|
||||
/// Get stratum server IP address and port.
|
||||
pub fn get_stratum_address() -> (String, String) {
|
||||
let r_config = Settings::node_config_to_read();
|
||||
|
||||
+57
-10
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
use std::{fs, thread};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
@@ -59,6 +59,8 @@ pub struct Node {
|
||||
exit_after_stop: AtomicBool,
|
||||
/// Flag to reset peers data and restart the [`Server`].
|
||||
reset_peers: AtomicBool,
|
||||
/// Flag to change data directory and restart the [`Server`].
|
||||
change_data_dir: AtomicBool,
|
||||
|
||||
/// An error occurred on [`Server`] start.
|
||||
error: Arc<RwLock<Option<Error>>>
|
||||
@@ -77,6 +79,7 @@ impl Default for Node {
|
||||
start_stratum_needed: AtomicBool::new(false),
|
||||
error: Arc::new(RwLock::new(None)),
|
||||
reset_peers: AtomicBool::new(false),
|
||||
change_data_dir: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,10 +190,10 @@ impl Node {
|
||||
|
||||
/// Check if [`Server`] is not syncing (disabled or just running after synchronization).
|
||||
pub fn not_syncing() -> bool {
|
||||
return match Node::get_sync_status() {
|
||||
match Node::get_sync_status() {
|
||||
None => true,
|
||||
Some(ss) => ss == SyncStatus::NoSync
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Get synchronization status, empty when [`Server`] is not running.
|
||||
@@ -218,7 +221,7 @@ impl Node {
|
||||
let r_err = NODE_STATE.error.read();
|
||||
if r_err.is_some() {
|
||||
let e = r_err.as_ref().unwrap();
|
||||
// Setup a flag to show an error to clean up data.
|
||||
// Flag setup to show an error to clean up data.
|
||||
let store_err = match e {
|
||||
Error::Store(_) => true,
|
||||
Error::Chain(_) => true,
|
||||
@@ -228,7 +231,7 @@ impl Node {
|
||||
return Some(NodeError::Storage);
|
||||
}
|
||||
|
||||
// Setup a flag to show P2P or API server error.
|
||||
// Flag setup to show P2P or API server error.
|
||||
let p2p_api_err = match e {
|
||||
Error::P2P(_) => Some(NodeError::P2P),
|
||||
Error::API(_) => Some(NodeError::API),
|
||||
@@ -238,7 +241,7 @@ impl Node {
|
||||
return p2p_api_err;
|
||||
}
|
||||
|
||||
// Setup a flag to show configuration error.
|
||||
// Flag setup to show configuration error.
|
||||
let config_err = match e {
|
||||
Error::Configuration(_) => true,
|
||||
_ => false
|
||||
@@ -282,7 +285,7 @@ impl Node {
|
||||
NODE_STATE.restart_needed.store(false, Ordering::Relaxed);
|
||||
}
|
||||
Err(e) => {
|
||||
// Setup an error.
|
||||
// Set an error.
|
||||
{
|
||||
let mut w_err = NODE_STATE.error.write();
|
||||
*w_err = Some(e);
|
||||
@@ -336,7 +339,7 @@ impl Node {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// Setup an error.
|
||||
// Set an error.
|
||||
{
|
||||
let mut w_err = NODE_STATE.error.write();
|
||||
*w_err = Some(e);
|
||||
@@ -372,6 +375,48 @@ impl Node {
|
||||
}
|
||||
}
|
||||
|
||||
/// Change chain data directory.
|
||||
pub fn change_data_dir(path: String) {
|
||||
if Self::data_dir_changing() || NodeConfig::get_chain_data_path() == path {
|
||||
return;
|
||||
}
|
||||
NODE_STATE.change_data_dir.store(true, Ordering::Relaxed);
|
||||
thread::spawn(move || {
|
||||
let running = Node::is_running();
|
||||
if running {
|
||||
Node::stop(false);
|
||||
// Wait node to stop before moving files.
|
||||
while Node::is_running() {
|
||||
thread::sleep(Self::STATS_UPDATE_DELAY);
|
||||
}
|
||||
}
|
||||
let cfg_path = NodeConfig::get_chain_data_path();
|
||||
let old = Path::new(cfg_path.as_str());
|
||||
let new = Path::new(path.as_str());
|
||||
if !old.exists() {
|
||||
NodeConfig::save_chain_data_path(path);
|
||||
} else {
|
||||
fs::create_dir_all(&new).unwrap_or_default();
|
||||
if let Ok(_) = fs::rename(old, new) {
|
||||
NodeConfig::save_chain_data_path(path);
|
||||
} else {
|
||||
fs::remove_dir_all(old).unwrap();
|
||||
NodeConfig::save_chain_data_path(path);
|
||||
}
|
||||
}
|
||||
NODE_STATE.change_data_dir.store(false, Ordering::Relaxed);
|
||||
// Restart node after migration.
|
||||
if running && !Node::is_stopping() {
|
||||
Node::start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Check if chain data directory is changing.
|
||||
pub fn data_dir_changing() -> bool {
|
||||
NODE_STATE.change_data_dir.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Clean-up [`Server`] data if server is not running.
|
||||
pub fn clean_up_data() {
|
||||
if Self::is_running() {
|
||||
@@ -412,14 +457,16 @@ impl Node {
|
||||
|
||||
/// Get synchronization status i18n text.
|
||||
pub fn get_sync_status_text() -> String {
|
||||
if Node::data_dir_changing() {
|
||||
return t!("moving_files");
|
||||
}
|
||||
|
||||
if Node::is_stopping() {
|
||||
return t!("sync_status.shutdown");
|
||||
};
|
||||
|
||||
if Node::is_starting() {
|
||||
return t!("sync_status.initial");
|
||||
};
|
||||
|
||||
if Node::is_restarting() {
|
||||
return t!("sync_status.node_restarting");
|
||||
}
|
||||
|
||||
+58
-11
@@ -48,14 +48,18 @@ pub struct WalletConfig {
|
||||
pub api_port: Option<u16>,
|
||||
/// Delay in blocks before another transaction broadcasting attempt.
|
||||
pub tx_broadcast_timeout: Option<u64>,
|
||||
|
||||
/// Wallet data directory path.
|
||||
pub data_path: Option<String>,
|
||||
|
||||
/// Config version.
|
||||
ver: Option<i32>
|
||||
}
|
||||
|
||||
/// Base wallets directory name.
|
||||
const BASE_DIR_NAME: &'static str = "wallets";
|
||||
/// Base wallets directory name.
|
||||
const DB_DIR_NAME: &'static str = "db";
|
||||
/// Wallet data directory name.
|
||||
const DATA_DIR_NAME: &'static str = "wallet_data";
|
||||
/// Wallet configuration file name.
|
||||
const CONFIG_FILE_NAME: &'static str = "grim-wallet.toml";
|
||||
/// Slatepacks directory name.
|
||||
@@ -66,7 +70,12 @@ const SEED_FILE: &str = "wallet.seed";
|
||||
/// Default value of minimal amount of confirmations.
|
||||
const MIN_CONFIRMATIONS_DEFAULT: u64 = 10;
|
||||
|
||||
const WALLET_CONFIG_VERSION: i32 = 1;
|
||||
|
||||
impl WalletConfig {
|
||||
/// Wallet data directory name.
|
||||
pub const DATA_DIR_NAME: &'static str = "wallet_data";
|
||||
|
||||
/// Default account name value.
|
||||
pub const DEFAULT_ACCOUNT_LABEL: &'static str = "default";
|
||||
|
||||
@@ -94,6 +103,8 @@ impl WalletConfig {
|
||||
enable_tor_listener: Some(false),
|
||||
api_port: Some(rand::rng().random_range(10000..30000)),
|
||||
tx_broadcast_timeout: Some(Self::BROADCASTING_TIMEOUT_DEFAULT),
|
||||
data_path: Some(Self::wallet_path(id.to_string())),
|
||||
ver: Some(WALLET_CONFIG_VERSION),
|
||||
};
|
||||
Settings::write_to_file(&config, config_path);
|
||||
config
|
||||
@@ -103,7 +114,8 @@ impl WalletConfig {
|
||||
pub fn load(wallet_dir: PathBuf) -> Option<WalletConfig> {
|
||||
let mut config_path: PathBuf = wallet_dir.clone();
|
||||
config_path.push(CONFIG_FILE_NAME);
|
||||
if let Ok(config) = Settings::read_from_file::<WalletConfig>(config_path) {
|
||||
if let Ok(mut config) = Settings::read_from_file::<WalletConfig>(config_path) {
|
||||
config.migrate();
|
||||
return Some(config)
|
||||
}
|
||||
None
|
||||
@@ -169,24 +181,32 @@ impl WalletConfig {
|
||||
config_path
|
||||
}
|
||||
|
||||
/// Get current wallet path.
|
||||
pub fn get_wallet_path(&self) -> String {
|
||||
/// Get wallet path from provided identifier.
|
||||
fn wallet_path(id: String) -> String {
|
||||
let chain_type = AppConfig::chain_type();
|
||||
let mut data_path = Self::get_base_path(chain_type);
|
||||
data_path.push(self.id.to_string());
|
||||
data_path.push(id);
|
||||
data_path.to_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
/// Get current wallet path.
|
||||
pub fn get_wallet_path(&self) -> String {
|
||||
Self::wallet_path(self.id.to_string())
|
||||
}
|
||||
|
||||
/// Get wallet data path.
|
||||
pub fn get_data_path(&self) -> String {
|
||||
let mut data_path = PathBuf::from(self.get_wallet_path());
|
||||
data_path.push(DATA_DIR_NAME);
|
||||
data_path.to_str().unwrap().to_string()
|
||||
if let Some(path) = &self.data_path {
|
||||
path.clone()
|
||||
} else {
|
||||
self.get_wallet_path()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get wallet seed path.
|
||||
pub fn seed_path(&self) -> String {
|
||||
let mut path = PathBuf::from(self.get_data_path());
|
||||
path.push(Self::DATA_DIR_NAME);
|
||||
path.push(SEED_FILE);
|
||||
path.to_str().unwrap().to_string()
|
||||
}
|
||||
@@ -194,13 +214,15 @@ impl WalletConfig {
|
||||
/// Get wallet database data path.
|
||||
pub fn get_db_path(&self) -> String {
|
||||
let mut path = PathBuf::from(self.get_data_path());
|
||||
path.push(Self::DATA_DIR_NAME);
|
||||
path.push(DB_DIR_NAME);
|
||||
path.to_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
/// Get Slatepack file path for transaction.
|
||||
pub fn get_tx_slate_path(&self, tx: &WalletTransaction) -> PathBuf {
|
||||
let mut path = PathBuf::from(self.get_wallet_path());
|
||||
let mut path = PathBuf::from(self.get_data_path());
|
||||
path.push(Self::DATA_DIR_NAME);
|
||||
path.push(SLATEPACKS_DIR_NAME);
|
||||
if !path.exists() {
|
||||
let _ = fs::create_dir_all(path.clone());
|
||||
@@ -212,7 +234,8 @@ impl WalletConfig {
|
||||
|
||||
/// Get Slatepack file path for Slate.
|
||||
pub fn get_slate_path(&self, slate: &Slate) -> PathBuf {
|
||||
let mut path = PathBuf::from(self.get_wallet_path());
|
||||
let mut path = PathBuf::from(self.get_data_path());
|
||||
path.push(Self::DATA_DIR_NAME);
|
||||
path.push(SLATEPACKS_DIR_NAME);
|
||||
if !path.exists() {
|
||||
let _ = fs::create_dir_all(path.clone());
|
||||
@@ -231,4 +254,28 @@ impl WalletConfig {
|
||||
}
|
||||
path.to_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
/// Check config version to migrate if needed.
|
||||
fn migrate(&mut self) {
|
||||
match self.ver {
|
||||
None => {
|
||||
// Migrate Slatepack data.
|
||||
let mut old_slate_path = PathBuf::from(self.get_wallet_path());
|
||||
old_slate_path.push(SLATEPACKS_DIR_NAME);
|
||||
if old_slate_path.exists() {
|
||||
let mut new_slate_path = PathBuf::from(self.get_data_path());
|
||||
new_slate_path.push(SLATEPACKS_DIR_NAME);
|
||||
let _ = fs::rename(&old_slate_path, &new_slate_path);
|
||||
}
|
||||
// Write data path to config.
|
||||
if self.data_path.is_none() {
|
||||
self.data_path = Some(self.get_wallet_path());
|
||||
}
|
||||
// Migrate to 1st version.
|
||||
self.ver = Some(1);
|
||||
}
|
||||
Some(_) => {}
|
||||
}
|
||||
self.save();
|
||||
}
|
||||
}
|
||||
+62
-13
@@ -93,7 +93,7 @@ pub struct Wallet {
|
||||
|
||||
/// Running wallet foreign API server and port.
|
||||
foreign_api_server: Arc<RwLock<Option<(ApiServer, u16)>>>,
|
||||
/// Running wallet foreign API server and port.
|
||||
/// Wallet secret key for transport service.
|
||||
secret_key: Arc<RwLock<Option<SecretKey>>>,
|
||||
|
||||
/// Flag to check if wallet repairing and restoring missing outputs is needed.
|
||||
@@ -101,6 +101,9 @@ pub struct Wallet {
|
||||
/// Wallet repair progress in percents.
|
||||
repair_progress: Arc<AtomicU8>,
|
||||
|
||||
/// Flag to check if wallet files are moving.
|
||||
files_moving: Arc<AtomicBool>,
|
||||
|
||||
/// Flag to check if Slatepack message file is opening.
|
||||
message_opening: Arc<AtomicBool>,
|
||||
|
||||
@@ -142,6 +145,7 @@ impl Wallet {
|
||||
syncing: Arc::new(AtomicBool::new(false)),
|
||||
repair_needed: Arc::new(AtomicBool::new(false)),
|
||||
repair_progress: Arc::new(AtomicU8::new(0)),
|
||||
files_moving: Arc::new(AtomicBool::new(false)),
|
||||
message_opening: Arc::new(AtomicBool::from(false)),
|
||||
send_creating: Arc::new(AtomicBool::new(false)),
|
||||
fee_calculating: Arc::new(AtomicU8::new(0)),
|
||||
@@ -190,7 +194,7 @@ impl Wallet {
|
||||
|
||||
/// Initialize [`Wallet`] from provided data path.
|
||||
pub fn init(data_path: PathBuf) -> Option<Wallet> {
|
||||
let wallet_config = WalletConfig::load(data_path.clone());
|
||||
let wallet_config = WalletConfig::load(data_path);
|
||||
if let Some(config) = wallet_config {
|
||||
return Some(Wallet::new(config));
|
||||
}
|
||||
@@ -284,7 +288,7 @@ impl Wallet {
|
||||
let mut wallet = Box::new(DefaultWalletImpl::<'static, C>::new(node_client).unwrap())
|
||||
as Box<dyn WalletInst<'static, L, C, K>>;
|
||||
let lc = wallet.lc_provider()?;
|
||||
lc.set_top_level_directory(config.get_wallet_path().as_str())?;
|
||||
lc.set_top_level_directory(config.get_data_path().as_str())?;
|
||||
Ok(Arc::new(Mutex::new(wallet)))
|
||||
}
|
||||
|
||||
@@ -1089,28 +1093,73 @@ impl Wallet {
|
||||
self.repair_progress.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Deleting wallet database files.
|
||||
pub fn delete_db(&self, reopen: bool) {
|
||||
let wallet_delete = self.clone();
|
||||
/// Change wallet data path, migrating all files to new directory.
|
||||
pub fn change_data_path(&self, path: String) {
|
||||
let wallet = self.clone();
|
||||
wallet.files_moving.store(true, Ordering::Relaxed);
|
||||
// Close wallet if open.
|
||||
if self.is_open() {
|
||||
self.close();
|
||||
}
|
||||
thread::spawn(move || {
|
||||
// Wait wallet to be closed.
|
||||
if wallet_delete.is_open() {
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
while wallet.is_open() || wallet.syncing() {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
// Move wallet db files.
|
||||
if let Some(old_path) = wallet.get_config().data_path {
|
||||
let mut old = PathBuf::from(old_path.as_str());
|
||||
old.push(WalletConfig::DATA_DIR_NAME);
|
||||
let mut new = PathBuf::from(path.as_str());
|
||||
new.push(WalletConfig::DATA_DIR_NAME);
|
||||
if old.exists() {
|
||||
fs::create_dir_all(&new).unwrap_or_default();
|
||||
if let Ok(_) = fs::rename(old.as_path(), new.as_path()) {
|
||||
// Save new path to config.
|
||||
let mut w_config = wallet.config.write();
|
||||
w_config.data_path = Some(path);
|
||||
w_config.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
wallet.files_moving.store(false, Ordering::Relaxed);
|
||||
// Mark wallet to reopen.
|
||||
if !wallet.is_open() {
|
||||
wallet.set_reopen(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Deleting wallet database files.
|
||||
pub fn delete_db(&self) {
|
||||
let wallet = self.clone();
|
||||
wallet.files_moving.store(true, Ordering::Relaxed);
|
||||
// Close wallet if open.
|
||||
if self.is_open() {
|
||||
self.close();
|
||||
}
|
||||
thread::spawn(move || {
|
||||
// Wait wallet to be closed.
|
||||
while wallet.is_open() || wallet.syncing() {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
// Remove wallet db files.
|
||||
let _ = fs::remove_dir_all(wallet_delete.get_config().get_db_path());
|
||||
// Start sync to close thread.
|
||||
wallet_delete.sync();
|
||||
wallet_delete.repair();
|
||||
let _ = fs::remove_dir_all(wallet.get_config().get_db_path());
|
||||
wallet.files_moving.store(false, Ordering::Relaxed);
|
||||
// Mark wallet to repair.
|
||||
wallet.repair();
|
||||
// Mark wallet to reopen.
|
||||
wallet_delete.set_reopen(reopen);
|
||||
if !wallet.is_open() {
|
||||
wallet.set_reopen(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Check if data files are moving.
|
||||
pub fn files_moving(&self) -> bool {
|
||||
self.files_moving.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Get recovery phrase.
|
||||
pub fn get_recovery(&self, password: String) -> Result<ZeroingString, Error> {
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
|
||||
Reference in New Issue
Block a user