mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Add UI logging output to RustIced sample
This commit is contained in:
1
samples/RustIced/Cargo.lock
generated
1
samples/RustIced/Cargo.lock
generated
@@ -3698,6 +3698,7 @@ dependencies = [
|
|||||||
"async-std",
|
"async-std",
|
||||||
"directories-next",
|
"directories-next",
|
||||||
"iced",
|
"iced",
|
||||||
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ serde_json = "1.0"
|
|||||||
uuid = { version = "1.0", features = ["v4", "fast-rng", "serde"] }
|
uuid = { version = "1.0", features = ["v4", "fast-rng", "serde"] }
|
||||||
directories-next = "2.0"
|
directories-next = "2.0"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
velopack = { path = "../../src/lib-rust", features = ["async"] }
|
velopack = { path = "../../src/lib-rust", features = ["async"] }
|
||||||
|
log = "0.4"
|
||||||
|
|||||||
55
samples/RustIced/src/logger.rs
Normal file
55
samples/RustIced/src/logger.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use crate::{AppState, Message};
|
||||||
|
use iced::futures::{channel::mpsc, sink::SinkExt, StreamExt};
|
||||||
|
use iced::{stream, Subscription};
|
||||||
|
use log::{Level, Log, Metadata, Record};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
static LOG_RECEIVER: Mutex<Option<mpsc::UnboundedReceiver<String>>> = Mutex::new(None);
|
||||||
|
|
||||||
|
pub struct IcedLogger {
|
||||||
|
sender: mpsc::UnboundedSender<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IcedLogger {
|
||||||
|
pub fn init()
|
||||||
|
{
|
||||||
|
let (sender, receiver) = mpsc::unbounded();
|
||||||
|
log::set_boxed_logger(Box::new(IcedLogger { sender })).unwrap();
|
||||||
|
log::set_max_level(log::LevelFilter::Info);
|
||||||
|
|
||||||
|
let mut log_receiver = LOG_RECEIVER.lock().unwrap();
|
||||||
|
*log_receiver = Some(receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take_receiver() -> mpsc::UnboundedReceiver<String> {
|
||||||
|
let mut log_receiver = LOG_RECEIVER.lock().unwrap();
|
||||||
|
log_receiver.take().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscription(_: &AppState) -> Subscription<Message> {
|
||||||
|
Subscription::run(|| {
|
||||||
|
let mut log_receiver = Self::take_receiver();
|
||||||
|
stream::channel(100, |mut output| async move {
|
||||||
|
loop {
|
||||||
|
let message = log_receiver.select_next_some().await;
|
||||||
|
let _ = output.send(Message::LogReceived(message)).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Log for IcedLogger {
|
||||||
|
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||||
|
metadata.level() <= Level::Info
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&self, record: &Record) {
|
||||||
|
if self.enabled(record.metadata()) {
|
||||||
|
let log_msg = format!("{}", record.args());
|
||||||
|
let _ = self.sender.unbounded_send(log_msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
#![windows_subsystem = "windows"]
|
#![windows_subsystem = "windows"]
|
||||||
use anyhow::Result;
|
|
||||||
use iced::widget::{Button, Column, Text};
|
|
||||||
|
|
||||||
|
mod logger;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use iced::widget::{button, column, container, scrollable, text, vertical_space};
|
||||||
|
use iced::Task;
|
||||||
use velopack::*;
|
use velopack::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -12,18 +15,45 @@ pub enum Message {
|
|||||||
DownloadProgress(i16),
|
DownloadProgress(i16),
|
||||||
DownloadComplete,
|
DownloadComplete,
|
||||||
Restart,
|
Restart,
|
||||||
|
LogReceived(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GUI {
|
pub struct AppState {
|
||||||
update_manager: Option<UpdateManager>,
|
update_manager: Option<UpdateManager>,
|
||||||
state: GUIState,
|
status: AppStatus,
|
||||||
current_version: Option<String>,
|
current_version: Option<String>,
|
||||||
update_info: Option<UpdateInfo>,
|
update_info: Option<UpdateInfo>,
|
||||||
download_progress: i16,
|
download_progress: i16,
|
||||||
|
logs: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
pub fn new() -> (Self, Task<Message>) {
|
||||||
|
let source = sources::FileSource::new(env!("RELEASES_DIR"));
|
||||||
|
let um = UpdateManager::new(source, None, None);
|
||||||
|
let mut version: Option<String> = None;
|
||||||
|
let mut state = AppStatus::NotInstalled;
|
||||||
|
if um.is_ok() {
|
||||||
|
state = AppStatus::Idle;
|
||||||
|
version = Some(um.as_ref().unwrap().current_version().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
AppState {
|
||||||
|
logs: Vec::new(),
|
||||||
|
update_manager: um.ok(),
|
||||||
|
status: state,
|
||||||
|
current_version: version,
|
||||||
|
update_info: None,
|
||||||
|
download_progress: 0,
|
||||||
|
},
|
||||||
|
Task::none(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum GUIState {
|
pub enum AppStatus {
|
||||||
NotInstalled,
|
NotInstalled,
|
||||||
Idle,
|
Idle,
|
||||||
Checking,
|
Checking,
|
||||||
@@ -33,38 +63,30 @@ pub enum GUIState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
logger::IcedLogger::init();
|
||||||
VelopackApp::build().run();
|
VelopackApp::build().run();
|
||||||
|
|
||||||
let source = sources::FileSource::new(env!("RELEASES_DIR"));
|
iced::application("Velopack Rust Sample", update, view)
|
||||||
let um = UpdateManager::new(source, None, None);
|
.window_size(iced::Size::new(600.0, 400.0))
|
||||||
let mut version: Option<String> = None;
|
.centered()
|
||||||
let mut state = GUIState::NotInstalled;
|
.subscription(logger::IcedLogger::subscription)
|
||||||
if um.is_ok() {
|
.run_with(|| AppState::new())?;
|
||||||
state = GUIState::Idle;
|
|
||||||
version = Some(um.as_ref().unwrap().current_version().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
let gui = GUI { update_manager: um.ok(), state, current_version: version, update_info: None, download_progress: 0 };
|
|
||||||
|
|
||||||
iced::application("A cool application", update, view)
|
|
||||||
.window_size(iced::Size::new(400.0, 200.0))
|
|
||||||
.run_with(move || (gui, iced::Task::none()))?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(gui: &mut GUI, message: Message) -> iced::Task<Message> {
|
fn update(state: &mut AppState, message: Message) -> Task<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::CheckForUpdates => {
|
Message::CheckForUpdates => {
|
||||||
gui.state = GUIState::Checking;
|
state.status = AppStatus::Checking;
|
||||||
iced::Task::perform(gui.update_manager.as_ref().unwrap().check_for_updates_async(), |result| match result {
|
Task::perform(state.update_manager.as_ref().unwrap().check_for_updates_async(), |result| match result {
|
||||||
Ok(update_info) => {
|
Ok(update_info) => {
|
||||||
match update_info {
|
match update_info {
|
||||||
UpdateCheck::RemoteIsEmpty => Message::UpdatesFound(None),
|
UpdateCheck::RemoteIsEmpty => Message::UpdatesFound(None),
|
||||||
UpdateCheck::NoUpdateAvailable => Message::UpdatesFound(None),
|
UpdateCheck::NoUpdateAvailable => Message::UpdatesFound(None),
|
||||||
UpdateCheck::UpdateAvailable(updates) => Message::UpdatesFound(Some(updates)),
|
UpdateCheck::UpdateAvailable(updates) => Message::UpdatesFound(Some(updates)),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Handle the error case, perhaps by logging or setting an error state
|
// Handle the error case, perhaps by logging or setting an error state
|
||||||
// For simplicity, we're sending a None update here, but you should handle errors appropriately
|
// For simplicity, we're sending a None update here, but you should handle errors appropriately
|
||||||
@@ -73,55 +95,84 @@ fn update(gui: &mut GUI, message: Message) -> iced::Task<Message> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
Message::UpdatesFound(update) => {
|
Message::UpdatesFound(update) => {
|
||||||
gui.update_info = update;
|
state.update_info = update;
|
||||||
gui.state = match gui.update_info {
|
state.status = match state.update_info {
|
||||||
Some(_) => GUIState::UpdatesAvailable,
|
Some(_) => AppStatus::UpdatesAvailable,
|
||||||
None => GUIState::Idle,
|
None => AppStatus::Idle,
|
||||||
};
|
};
|
||||||
iced::Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::DownloadUpdates => {
|
Message::DownloadUpdates => {
|
||||||
gui.state = GUIState::Downloading;
|
state.status = AppStatus::Downloading;
|
||||||
let update_info = gui.update_info.clone().unwrap(); // Ensure you handle this safely in your actual code
|
let update_info = state.update_info.clone().unwrap(); // Ensure you handle this safely in your actual code
|
||||||
iced::Task::perform(gui.update_manager.as_ref().unwrap().download_updates_async(&update_info, None), |_| Message::DownloadComplete)
|
Task::perform(state.update_manager.as_ref().unwrap().download_updates_async(&update_info, None), |_| Message::DownloadComplete)
|
||||||
}
|
}
|
||||||
Message::DownloadProgress(progress) => {
|
Message::DownloadProgress(progress) => {
|
||||||
gui.download_progress = progress;
|
state.download_progress = progress;
|
||||||
iced::Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::DownloadComplete => {
|
Message::DownloadComplete => {
|
||||||
gui.state = GUIState::ReadyToRestart;
|
state.status = AppStatus::ReadyToRestart;
|
||||||
iced::Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Restart => {
|
Message::Restart => {
|
||||||
let update_info = gui.update_info.clone().unwrap(); // Ensure you handle this safely in your actual code
|
let update_info = state.update_info.clone().unwrap(); // Ensure you handle this safely in your actual code
|
||||||
gui.update_manager.as_ref().unwrap().apply_updates_and_restart(update_info).unwrap();
|
state.update_manager.as_ref().unwrap().apply_updates_and_restart(update_info).unwrap();
|
||||||
iced::Task::none()
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::LogReceived(log) => {
|
||||||
|
state.logs.push(log);
|
||||||
|
Task::none()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(gui: &GUI) -> iced::Element<Message> {
|
fn view(state: &AppState) -> iced::Element<Message> {
|
||||||
let content = match gui.state {
|
let content = match state.status {
|
||||||
GUIState::NotInstalled => Column::new()
|
AppStatus::NotInstalled =>
|
||||||
.push(Text::new("Can't check for updates if not installed")),
|
column![text("Can't check for updates if not installed")],
|
||||||
GUIState::Idle => Column::new()
|
AppStatus::Idle =>
|
||||||
.push(Text::new(format!("Current version: {}", gui.current_version.as_ref().unwrap_or(&"Unknown".to_string()))))
|
column![
|
||||||
.push(Button::new(Text::new("Check for updates")).on_press(Message::CheckForUpdates)),
|
text(format!("Current version: {}", state.current_version.as_ref().unwrap_or(&"Unknown".to_string()))),
|
||||||
GUIState::Checking => Column::new()
|
button("Check for updates").on_press(Message::CheckForUpdates),
|
||||||
.push(Text::new("Checking for updates...")),
|
],
|
||||||
GUIState::UpdatesAvailable => {
|
AppStatus::Checking =>
|
||||||
let update_version = gui.update_info.as_ref().map_or("Unknown", |info| &info.TargetFullRelease.Version);
|
column![text("Checking for updates...")],
|
||||||
Column::new()
|
AppStatus::UpdatesAvailable => {
|
||||||
.push(Text::new(format!("Update available: {}", update_version)))
|
let update_version = state.update_info.as_ref().map_or("Unknown", |info| &info.TargetFullRelease.Version);
|
||||||
.push(Button::new(Text::new("Download updates")).on_press(Message::DownloadUpdates))
|
column![
|
||||||
|
text(format!("Update available: {}", update_version)),
|
||||||
|
button("Download updates").on_press(Message::DownloadUpdates),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
GUIState::Downloading => Column::new()
|
AppStatus::Downloading =>
|
||||||
.push(Text::new(format!("Downloading updates... Progress: {}%", gui.download_progress))),
|
column![text(format!("Downloading updates... Progress: {}%", state.download_progress))],
|
||||||
GUIState::ReadyToRestart => Column::new()
|
AppStatus::ReadyToRestart =>
|
||||||
.push(Text::new("Updates downloaded. Ready to restart."))
|
column![
|
||||||
.push(Button::new(Text::new("Restart")).on_press(Message::Restart)),
|
text("Updates downloaded. Ready to restart."),
|
||||||
|
button("Restart").on_press(Message::Restart),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let log_area = scrollable(text(state.logs.join("\n")))
|
||||||
|
.height(iced::Length::Fill)
|
||||||
|
.width(iced::Length::Fill);
|
||||||
|
|
||||||
|
let log_container = container(log_area)
|
||||||
|
.padding(10)
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.height(iced::Length::Fill)
|
||||||
|
.style(|_| {
|
||||||
|
container::Style {
|
||||||
|
background: Some(iced::Color::from_rgb8(0x77, 0x1d, 0x1d).into()),
|
||||||
|
text_color: Some(iced::Color::WHITE),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
content.into()
|
column![
|
||||||
|
content,
|
||||||
|
vertical_space().height(20),
|
||||||
|
log_container,
|
||||||
|
].into()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user