1
0
forked from GRIN/grim

feat: check app updates

Check update using API endpoint: https://code.gri.mw/api/v1/repos/gui/grim/releases/latest

Reviewed-on: https://code.gri.mw/GUI/grim/pulls/54
This commit is contained in:
ardocrat
2026-03-08 19:28:28 +00:00
parent af203b8f9b
commit ae0ff12935
17 changed files with 569 additions and 30 deletions
+3
View File
@@ -36,6 +36,9 @@ max_short: MAX
files_location: Dateistandort
moving_files: Dateien verschieben
wrong_path_error: Falscher Weg angegeben
check_updates: Suchen Sie beim Start nach Updates
update_available: Update ist verfügbar!
changelog: 'Wechselbuch:'
wallets:
await_conf_amount: Erwarte Bestätigung
await_fin_amount: Warten auf die Fertigstellung
+3
View File
@@ -36,6 +36,9 @@ max_short: MAX
files_location: Files location
moving_files: Moving files
wrong_path_error: Wrong path specified
check_updates: Check for updates at startup
update_available: Update is available!
changelog: 'Changelog:'
wallets:
await_conf_amount: Awaiting confirmation
await_fin_amount: Awaiting finalization
+3
View File
@@ -36,6 +36,9 @@ max_short: MAX
files_location: Emplacement du fichier
moving_files: Déplacer des fichiers
wrong_path_error: Chemin incorrect spécifié
check_updates: Vérifiez les mises à jour au démarrage
update_available: Mise à jour disponible!
changelog: 'Journal des modifications:'
wallets:
await_conf_amount: En attente de confirmation
await_fin_amount: En attente de finalisation
+3
View File
@@ -36,6 +36,9 @@ max_short: МАКС
files_location: Расположение файлов
moving_files: Перемещение файлов
wrong_path_error: Указан неправильный путь
check_updates: Проверять обновления при запуске
update_available: Доступно обновление!
changelog: 'Журнал изменений:'
wallets:
await_conf_amount: Ожидает подтверждения
await_fin_amount: Ожидает завершения
+3
View File
@@ -36,6 +36,9 @@ max_short: MAKS
files_location: Dosya konumu
moving_files: Dosyalari Tasima
wrong_path_error: Yanlis yol belirtildi
check_updates: Başlangiçta güncellemeleri kontrol edin
update_available: Güncelleme mevcut!
changelog: 'Değişiklik Günlüğü:'
wallets:
await_conf_amount: Onay bekleniyor
await_fin_amount: Tamamlanma bekleniyor
+3
View File
@@ -36,6 +36,9 @@ max_short: 最大數量
files_location: 檔案位置
moving_files: 檔案移動
wrong_path_error: 指定錯誤路徑
check_updates: 啟動時請查看更新
update_available: 最新消息已发布!
changelog: '更新日誌:'
wallets:
await_conf_amount: 等待确认中
await_fin_amount: 等待确定中
+8
View File
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::AppConfig;
use crate::gui::icons::GLOBE_SIMPLE;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::settings::{InterfaceSettingsContent, NetworkSettingsContent};
@@ -41,6 +42,13 @@ impl Default for SettingsContent {
impl SettingsContent {
/// Draw application settings content.
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
ui.add_space(5.0);
View::checkbox(ui, AppConfig::check_updates(), t!("check_updates"), || {
AppConfig::toggle_check_updates();
});
ui.add_space(6.0);
View::horizontal_line(ui, Colors::stroke());
// Show interface settings.
self.interface_settings.ui(ui, cb);
+118 -10
View File
@@ -14,15 +14,15 @@
use std::time::Duration;
use egui::scroll_area::ScrollBarVisibility;
use egui::{Align, CornerRadius, Id, Layout, Margin, RichText, ScrollArea, StrokeKind};
use egui::{Align, CornerRadius, Id, Layout, Margin, OpenUrl, RichText, ScrollArea, StrokeKind};
use egui::os::OperatingSystem;
use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, FOLDER_OPEN, FOLDER_PLUS, GEAR, GEAR_FINE, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SUITCASE};
use egui_async::Bind;
use crate::gui::icons::{ARROW_LEFT, BOOKMARKS, CALENDAR_CHECK, CARET_RIGHT, CLOUD_ARROW_DOWN, COMPUTER_TOWER, FOLDER_OPEN, FOLDER_PLUS, GEAR, GEAR_FINE, GLOBE, GLOBE_SIMPLE, LOCK_KEY, NOTEPAD, PLUS, SIDEBAR_SIMPLE, SUITCASE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::settings::SettingsContent;
use crate::gui::views::types::{ContentContainer, LinePosition, ModalPosition, TitleContentType, TitleType};
use crate::gui::views::wallets::creation::WalletCreationContent;
use crate::gui::views::wallets::modals::{AddWalletModal, OpenWalletModal, WalletSettingsModal, WalletListModal};
use crate::gui::views::wallets::modals::{AddWalletModal, OpenWalletModal, WalletSettingsModal, WalletListModal, ChangelogContent};
use crate::gui::views::wallets::wallet::types::{wallet_status_text, WalletContentContainer};
use crate::gui::views::wallets::WalletContent;
use crate::gui::views::{Content, Modal, TitlePanel, View};
@@ -31,6 +31,8 @@ use crate::wallet::types::{ConnectionMethod, WalletTask};
use crate::wallet::{Wallet, WalletList};
use crate::AppConfig;
use crate::gui::views::wallets::wallet::RecoverySettings;
use crate::http::{retrieve_release, ReleaseInfo};
use crate::settings::AppUpdate;
/// Wallets content.
pub struct WalletsContent {
@@ -53,6 +55,13 @@ pub struct WalletsContent {
/// Settings content.
settings_content: Option<SettingsContent>,
/// Result of update check
check_update: Bind<ReleaseInfo, String>,
/// Application update information.
update_info: (bool, Option<AppUpdate>),
/// Update changelog [`Modal`] content.
changelog_content: Option<ChangelogContent>
}
/// Identifier for [`Modal`] to add the wallet.
@@ -75,6 +84,9 @@ impl Default for WalletsContent {
wallet_content: WalletContent::default(),
creation_content: None,
settings_content: None,
check_update: Bind::new(false),
update_info: (false, None),
changelog_content: None,
}
}
}
@@ -86,7 +98,8 @@ impl ContentContainer for WalletsContent {
OPEN_WALLET_MODAL,
WALLET_SETTINGS_MODAL,
SELECT_WALLET_MODAL,
Self::DELETE_CONFIRMATION_MODAL
Self::DELETE_CONFIRMATION_MODAL,
ChangelogContent::MODAL_ID
]
}
@@ -137,6 +150,11 @@ impl ContentContainer for WalletsContent {
RecoverySettings::deletion_modal_ui(ui, w);
}
}
ChangelogContent::MODAL_ID => {
if let Some(c) = self.changelog_content.as_mut() {
c.ui(ui);
}
}
_ => {}
}
}
@@ -514,6 +532,31 @@ impl WalletsContent {
View::app_logo_name_version(ui);
ui.add_space(15.0);
// Show result of update check.
if AppConfig::check_updates() {
if let Some(res) = self.check_update.read_or_request(|| async {
retrieve_release().await
}) {
let checked = self.update_info.0;
if !checked {
self.update_info.0 = true;
match res {
Ok(info) => {
if info.is_update() {
AppConfig::save_update(Some(info));
} else {
AppConfig::save_update(None);
}
self.update_info.1 = AppConfig::app_update();
}
Err(_) => AppConfig::save_update(None),
}
}
}
// Show update information.
self.update_info_ui(ui);
}
let list = self.wallets.list().clone();
for w in list.iter() {
let id = w.get_config().id;
@@ -633,9 +676,74 @@ impl WalletsContent {
});
}
/// Draw update information content.
fn update_info_ui(&mut self, ui: &mut egui::Ui) {
if self.update_info.1.is_none() {
return;
}
let update = self.update_info.1.as_ref().unwrap();
ui.add_space(-4.0);
let mut rect = ui.available_rect_before_wrap();
rect.set_height(78.0);
let r = View::item_rounding(0, 1, false);
ui.painter().rect(rect, r, Colors::fill(), View::item_stroke(), StrokeKind::Outside);
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Show button to download the update.
let mut link_clicked = false;
View::item_button(ui, View::item_rounding(0, 1, true), CLOUD_ARROW_DOWN, None, || {
link_clicked = true;
});
if link_clicked {
ui.ctx().open_url(OpenUrl {
url: update.url.clone(),
new_tab: true,
});
}
// Show button to see update information.
View::item_button(ui, CornerRadius::default(), NOTEPAD, None, || {
self.changelog_content = Some(ChangelogContent::new(update.changelog.clone()));
let title = format!("Grim {}", update.version);
Modal::new(ChangelogContent::MODAL_ID)
.position(ModalPosition::Center)
.title(title)
.show();
});
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(6.0);
ui.vertical(|ui| {
ui.add_space(3.0);
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
ui.add_space(1.0);
let update_text = "Update is available!";
View::ellipsize_text(ui, update_text, 18.0, Colors::green());
});
// Show version info.
let ver_text = if let Some(size) = update.size.as_ref() {
format!("{} {} ({} MB)", BOOKMARKS, update.version, size)
} else {
format!("{} {} > {}", BOOKMARKS, crate::VERSION, update.version)
};
View::ellipsize_text(ui, ver_text, 15.0, Colors::text(false));
ui.add_space(1.0);
// Show update date.
let date_text = format!("{} {}", CALENDAR_CHECK, update.date);
View::ellipsize_text(ui, date_text, 15.0, Colors::gray());
ui.add_space(3.0);
});
});
});
ui.add_space(12.0);
View::horizontal_line(ui, Colors::stroke());
ui.add_space(12.0);
}
/// Show [`Modal`] to select and open wallet.
fn show_opening_modal(&mut self, wallet: &Wallet, data: Option<String>, cb: &dyn PlatformCallbacks) {
self.select_wallet(wallet, data, cb);
fn show_opening_modal(&mut self, w: &Wallet, data: Option<String>, cb: &dyn PlatformCallbacks) {
self.select_wallet(w, data, cb);
self.open_wallet_content = OpenWalletModal::new();
Modal::new(OPEN_WALLET_MODAL)
.position(ModalPosition::CenterTop)
@@ -644,12 +752,12 @@ impl WalletsContent {
}
/// Select wallet to make some actions on it.
fn select_wallet(&mut self, wallet: &Wallet, data: Option<String>, cb: &dyn PlatformCallbacks) {
fn select_wallet(&mut self, w: &Wallet, data: Option<String>, cb: &dyn PlatformCallbacks) {
self.wallet_content.account_content.close_qr_scan(cb);
if let Some(data) = data {
wallet.task(WalletTask::OpenMessage(data));
w.task(WalletTask::OpenMessage(data));
}
self.wallets.select(Some(wallet.get_config().id));
self.wallets.select(Some(w.get_config().id));
}
}
+137
View File
@@ -0,0 +1,137 @@
// Copyright 2026 The Grim Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::scroll_area::ScrollBarVisibility;
use egui::{Id, OpenUrl, RichText, ScrollArea};
use crate::gui::views::{Modal, View};
use crate::gui::Colors;
use crate::gui::icons::{BRACKETS_CURLY, GITHUB_LOGO, TELEGRAM_LOGO};
/// Application release changelog content.
pub struct ChangelogContent {
/// Changelog text.
changelog: String,
}
/// Endpoint for GitHub repository.
const GITHUB_URL: &'static str = "https://github.com/GetGrin/grim";
/// Endpoint for Telegram releases channel.
const TELEGRAM_URL: &'static str = "https://t.me/grim_releases";
/// Endpoint for git repository.
const GIT_URL: &'static str = "https://code.gri.mw/GUI/grim";
impl ChangelogContent {
/// Create new content instance.
pub fn new(changelog: String) -> Self {
Self { changelog }
}
/// Identifier for [`Modal`].
pub const MODAL_ID: &'static str = "release_changelog_modal";
/// Draw changelog [`Modal`] content.
pub fn ui(&mut self, ui: &mut egui::Ui) {
ui.add_space(6.0);
ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("changelog")).size(16.0).color(Colors::gray()));
});
ui.add_space(6.0);
// Show changelog text.
ui.vertical_centered(|ui| {
let scroll_id = Id::from("release_changelog");
View::horizontal_line(ui, Colors::item_stroke());
ui.add_space(3.0);
ScrollArea::vertical()
.id_salt(scroll_id)
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
.max_height(128.0)
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.add_space(7.0);
let input_id = scroll_id.with("_input");
egui::TextEdit::multiline(&mut self.changelog)
.id(input_id)
.font(egui::TextStyle::Small)
.desired_rows(5)
.interactive(false)
.desired_width(f32::INFINITY)
.show(ui);
ui.add_space(6.0);
});
});
ui.add_space(2.0);
View::horizontal_line(ui, Colors::item_stroke());
ui.add_space(8.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
ui.columns(3, |columns| {
columns[0].vertical_centered_justified(|ui| {
// Draw button to open GitHub link.
let mut github_clicked = false;
View::button(ui, GITHUB_LOGO, Colors::white_or_black(false), || {
github_clicked = true;
});
if github_clicked {
ui.ctx().open_url(OpenUrl {
url: GITHUB_URL.into(),
new_tab: true,
});
}
});
columns[1].vertical_centered_justified(|ui| {
// Draw button to open Telegram link.
let mut tg_clicked = false;
View::button(ui, TELEGRAM_LOGO, Colors::white_or_black(false), || {
tg_clicked = true;
});
if tg_clicked {
ui.ctx().open_url(OpenUrl {
url: TELEGRAM_URL.into(),
new_tab: true,
});
}
});
columns[2].vertical_centered_justified(|ui| {
// Draw button to open repository link.
let mut git_clicked = false;
View::button(ui, BRACKETS_CURLY, Colors::white_or_black(false), || {
git_clicked = true;
});
if git_clicked {
ui.ctx().open_url(OpenUrl {
url: GIT_URL.into(),
new_tab: true,
});
}
});
});
ui.add_space(8.0);
View::horizontal_line(ui, Colors::item_stroke());
ui.add_space(8.0);
// Show button to close modal.
ui.vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::white_or_black(false), || {
Modal::close();
});
});
ui.add_space(6.0);
}
}
+4 -1
View File
@@ -22,4 +22,7 @@ mod open;
pub use open::*;
mod add;
pub use add::*;
pub use add::*;
mod changelog;
pub use changelog::*;
+22 -11
View File
@@ -12,14 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use bytes::Bytes;
use http_body_util::Full;
use hyper::body::Incoming;
use hyper::body::{Body, Incoming};
use hyper::{Request, Response};
use hyper_proxy2::{Intercept, Proxy, ProxyConnector};
use hyper_tls::HttpsConnector;
use hyper_util::client::legacy::{Client, Error};
use hyper_util::rt::TokioExecutor;
use serde::de::StdError;
use crate::AppConfig;
@@ -29,7 +28,11 @@ pub struct HttpClient {
impl HttpClient {
/// Send request.
pub async fn send(req: Request<Full<Bytes>>) -> Result<Response<Incoming>, Error> {
pub async fn send<B>(req: Request<B>) -> Result<Response<Incoming>, Error>
where B: Body + Send + 'static + Unpin, <B as Body>::Data: Send,
B::Data: Send,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
{
if AppConfig::use_proxy() {
if let Some(url) = AppConfig::socks_proxy_url() {
Self::send_socks_proxy(url, req).await
@@ -38,14 +41,18 @@ impl HttpClient {
}
} else {
let client = Client::builder(TokioExecutor::new())
.build::<_, Full<Bytes>>(HttpsConnector::new());
.build::<_, B>(HttpsConnector::new());
client.request(req).await
}
}
/// Create socks proxy client.
pub async fn send_socks_proxy(proxy_url: String, req: Request<Full<Bytes>>)
-> Result<Response<Incoming>, Error> {
pub async fn send_socks_proxy<B>(proxy_url: String, req: Request<B>)
-> Result<Response<Incoming>, Error>
where B: Body + Send + 'static + Unpin, <B as Body>::Data: Send,
B::Data: Send,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
{
let connector = HttpsConnector::new();
let uri = proxy_url.parse().unwrap();
let proxy = hyper_socks2::SocksConnector {
@@ -54,19 +61,23 @@ impl HttpClient {
connector,
}.with_tls().unwrap();
let client = Client::builder(TokioExecutor::new())
.build::<_, Full<Bytes>>(proxy);
.build::<_, B>(proxy);
client.request(req).await
}
/// Create http proxy client.
pub async fn send_http_proxy(proxy_url: String, req: Request<Full<Bytes>>)
-> Result<Response<Incoming>, Error> {
pub async fn send_http_proxy<B>(proxy_url: String, req: Request<B>)
-> Result<Response<Incoming>, Error>
where B: Body + Send + 'static + Unpin, <B as Body>::Data: Send,
B::Data: Send,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
{
let uri = proxy_url.parse().unwrap();
let proxy = Proxy::new(Intercept::All, uri);
let connector = HttpsConnector::new();
let proxy_connector = ProxyConnector::from_proxy(connector, proxy).unwrap();
let client = Client::builder(TokioExecutor::new())
.build::<_, Full<Bytes>>(proxy_connector);
.build::<_, B>(proxy_connector);
client.request(req).await
}
}
+4 -1
View File
@@ -13,4 +13,7 @@
// limitations under the License.
mod client;
pub use client::*;
pub use client::*;
mod release;
pub use release::*;
+178
View File
@@ -0,0 +1,178 @@
// Copyright 2026 The Grim Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use bytes::Bytes;
use chrono::NaiveDateTime;
use egui::os::OperatingSystem;
use http_body_util::{BodyExt, Empty};
use serde_derive::Deserialize;
use crate::gui::views::View;
use crate::http::HttpClient;
#[derive(Deserialize)]
pub struct ReleaseAsset {
pub name: String,
pub browser_download_url: String,
pub size: u64,
}
#[derive(Deserialize)]
pub struct ReleaseInfo {
pub tag_name: String,
pub body: String,
pub published_at: String,
pub assets: Vec<ReleaseAsset>,
}
#[cfg(target_arch = "x86_64")]
/// x86 CPU architecture.
const X86_ARCH: &str = "x86_64";
#[cfg(target_arch = "x86_64")]
const ARCH: &'static str = X86_ARCH;
/// ARM CPU architecture.
const ARM_ARCH: &str = "arm";
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
const ARCH: &'static str = ARM_ARCH;
/// Base endpoint to download the release.
const BASE_DOWNLOAD_URL: &'static str = "https://code.gri.mw/GUI/grim/releases/download/";
impl ReleaseInfo {
/// Get version number.
pub fn version(&self) -> String {
self.tag_name.replace("v", "")
}
/// Get artifact release name based on current platform.
fn name(&self) -> Option<String> {
let os = OperatingSystem::from_target_os();
match os {
OperatingSystem::Unknown => None,
OperatingSystem::Android => {
let name = if ARCH == ARM_ARCH {
format!("grim-{}-android.apk", self.tag_name)
} else {
format!("grim-{}-android-x86_64.apk", self.tag_name)
};
Some(name)
},
OperatingSystem::IOS => None,
OperatingSystem::Nix => {
let name = if ARCH == ARM_ARCH {
format!("grim-{}-linux-arm.AppImage", self.tag_name)
} else {
format!("grim-{}-linux-x86_64.AppImage", self.tag_name)
};
Some(name)
}
OperatingSystem::Mac => {
Some(format!("grim-{}-macos-universal.zip", self.tag_name))
},
OperatingSystem::Windows => {
if ARCH == ARM_ARCH {
None
} else {
Some(format!("grim-{}-win-x86_64.msi", self.tag_name))
}
}
}
}
/// Get link to download the release.
pub fn url(&self) -> Option<String> {
let base_url = format!("{}{}/", BASE_DOWNLOAD_URL, self.tag_name);
if let Some(name) = self.name() {
return Some(format!("{}{}", base_url, name));
}
None
}
/// Get formatted release date.
pub fn date(&self) -> String {
let date = self.published_at.clone().replace("T", " ").replace("Z", "");
let date_format = NaiveDateTime::parse_from_str(date.as_str(), "%Y-%m-%d %H:%M:%S");
if let Ok(date) = date_format {
return View::format_time(date.and_utc().timestamp());
}
date
}
/// Get release size in megabytes.
pub fn size(&self) -> Option<String> {
let name = self.name()?;
for a in &self.assets {
if a.name == name {
let size_mb = a.size as f64 / 1000000.0;
return Some(format!("{:.2}", size_mb));
}
}
None
}
/// Check if release is update.
pub fn is_update(&self) -> bool {
let cur = crate::VERSION;
let ver = self.version();
if cur == ver {
return false;
}
let cur_numbers: Vec<i32> = cur
.split(".")
.filter_map(|s| s.parse::<i32>().ok())
.collect();
let ver_numbers: Vec<i32> = ver
.split(".")
.filter_map(|s| s.parse::<i32>().ok())
.collect();
if cur_numbers.len() != ver_numbers.len() {
return true;
}
for (i, num) in ver_numbers.iter().enumerate() {
if num > &cur_numbers.get(i).unwrap() {
if i == 0 {
return true;
} else if i == 1 && cur_numbers.get(0).unwrap() == ver_numbers.get(0).unwrap() {
return true;
} else if i == 2 && cur_numbers.get(1).unwrap() == ver_numbers.get(1).unwrap() {
return true;
}
}
}
false
}
}
/// API endpoint to check last release.
const REQUEST_URL: &'static str = "https://code.gri.mw/api/v1/repos/gui/grim/releases/latest";
pub async fn retrieve_release() -> Result<ReleaseInfo, String> {
let req = hyper::Request::builder()
.method(hyper::Method::GET)
.uri(REQUEST_URL)
.body(Empty::<Bytes>::new())
.unwrap();
if let Ok(resp) = HttpClient::send(req).await {
let status = resp.status().as_u16();
if status == 200 {
if let Ok(body) = resp.into_body().collect().await {
let body_bytes = body.to_bytes();
if let Ok(update_info) = serde_json::from_slice::<ReleaseInfo>(&body_bytes) {
return Ok(update_info);
}
}
}
}
Err("Error checking update".to_string())
}
+72 -2
View File
@@ -15,11 +15,27 @@
use grin_core::global;
use grin_core::global::ChainTypes;
use serde_derive::{Deserialize, Serialize};
use crate::gui::views::Content;
use crate::gui::views::Content;
use crate::http::ReleaseInfo;
use crate::node::NodeConfig;
use crate::Settings;
use crate::wallet::ConnectionsConfig;
use crate::Settings;
/// Application update information.
#[derive(Serialize, Deserialize, Clone)]
pub struct AppUpdate {
/// Version of release.
pub version: String,
/// Size of release in megabytes.
pub size: Option<String>,
/// Date of release.
pub date: String,
/// Changes in the release.
pub changelog: String,
/// Link to download the release.
pub url: String,
}
/// Application configuration, stored at toml file.
#[derive(Serialize, Deserialize)]
@@ -61,6 +77,11 @@ pub struct AppConfig {
http_proxy_url: Option<String>,
/// SOCKS5 proxy URL.
socks_proxy_url: Option<String>,
/// Flag to check updates on startup.
check_updates: Option<bool>,
/// Application update information.
app_update: Option<AppUpdate>,
}
impl Default for AppConfig {
@@ -82,6 +103,8 @@ impl Default for AppConfig {
use_socks_proxy: None,
http_proxy_url: None,
socks_proxy_url: None,
check_updates: Some(true),
app_update: None,
}
}
}
@@ -329,4 +352,51 @@ impl AppConfig {
w_config.save();
}
/// Check updates on startup.
pub fn check_updates() -> bool {
let r_config = Settings::app_config_to_read();
r_config.check_updates.unwrap_or(true)
}
/// Disable or enable updates checking.
pub fn toggle_check_updates() {
let check = Self::check_updates();
// Clear update info on disable.
if !check {
Self::save_update(None);
}
let mut w_config = Settings::app_config_to_update();
w_config.check_updates = Some(!check);
w_config.save();
}
/// Get last update information, that includes: version, date and description.
pub fn app_update() -> Option<AppUpdate> {
let r_config = Settings::app_config_to_read();
r_config.app_update.clone()
}
/// Save update information.
pub fn save_update(release: Option<&ReleaseInfo>) {
let mut w_config = Settings::app_config_to_update();
match release {
None => {
w_config.app_update = None;
}
Some(release) => {
let url = release.url();
if let Some(url) = url {
let app_update = AppUpdate {
version: release.version(),
size: release.size(),
date: release.date(),
changelog: release.body.clone(),
url,
};
w_config.app_update = Some(app_update);
}
}
}
w_config.save();
}
}
+1 -1
View File
@@ -16,4 +16,4 @@ mod settings;
pub use settings::Settings;
mod config;
pub use config::AppConfig;
pub use config::*;
+4 -3
View File
@@ -20,7 +20,7 @@ use ed25519_dalek::hazmat::ExpandedSecretKey;
use fs_mistrust::Mistrust;
use futures::task::SpawnExt;
use grin_util::secp::SecretKey;
use http_body_util::BodyExt;
use http_body_util::{BodyExt, Full};
use lazy_static::lazy_static;
use parking_lot::RwLock;
use sha2::Sha512;
@@ -30,6 +30,7 @@ use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use std::{fs, thread};
use bytes::Bytes;
use log::error;
use safelog::DisplayRedacted;
use tls_api::{TlsConnector as TlsConnectorTrait, TlsConnectorBuilder};
@@ -131,10 +132,10 @@ impl Tor {
/// Send post request using Tor.
pub async fn post(body: String, url: String) -> Option<String> {
if let Some(proxy) = TorConfig::get_proxy() {
let req = hyper::Request::builder()
let req: hyper::Request<Full<Bytes>> = hyper::Request::builder()
.method(hyper::Method::POST)
.uri(url)
.body(http_body_util::Full::from(body))
.body(Full::from(body))
.unwrap();
let res = match proxy {
TorProxy::SOCKS5(url) => {
+3 -1
View File
@@ -12,8 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use bytes::Bytes;
use grin_core::global::ChainTypes;
use grin_util::to_base64;
use http_body_util::Full;
use serde_derive::{Deserialize, Serialize};
use crate::http::HttpClient;
@@ -141,7 +143,7 @@ fn check_ext_conn(conn: &ExternalConnection, ui_ctx: &egui::Context) {
req_setup = req_setup
.header(hyper::header::AUTHORIZATION, basic_auth.clone());
}
let req = req_setup.body(http_body_util::Full::from(
let req: hyper::Request<Full<Bytes>> = req_setup.body(Full::from(
r#"{"id":1,"jsonrpc":"2.0","method":"get_version","params":{} }"#)
).unwrap();
// Send request.