Skip to content

Commit

Permalink
clean up the code (#14)
Browse files Browse the repository at this point in the history
* introduce logger
* add support for aborting task
* add auto-scroll to console
* refactor LogConsole as separate component
* add rotating esp-rs logo to Rust installation
* add esp-prog picture for monitoring
* bump up version to 0.0.13
  • Loading branch information
georgik authored Aug 10, 2023
1 parent e032b69 commit 9b7319e
Show file tree
Hide file tree
Showing 19 changed files with 496 additions and 382 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "esp-helm",
"private": true,
"version": "0.0.12",
"version": "0.0.13",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ tauri-build = { version = "1.4", features = [] }

[dependencies]
dirs = "5.0.1"
fern = "0.6.2"
futures = "0.3.28"
log = "0.4.19"
reqwest = { version = "0.11", features = ["blocking"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
57 changes: 57 additions & 0 deletions src-tauri/src/console.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

use tauri::Window;
use tauri::Manager;
use log::{ Log, Record, Metadata };

#[derive(Clone, serde::Serialize)]
struct ConsoleEvent {
message: String,
}

pub struct TauriLogger {
window: Window,
}

impl TauriLogger {
pub fn new(window: Window) -> Self {
Self { window }
}
}

impl Log for TauriLogger {
fn enabled(&self, _metadata: &Metadata) -> bool {
true
}

fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let event = ConsoleEvent {
message: format!("{}", record.args()),
};
self.window.emit("rust-console", &event).unwrap();
println!("{}", record.args()); // Also log to stdout
}
}

fn flush(&self) {}
}

use fern::Dispatch;
use log::LevelFilter;

pub fn setup_logging(app: &tauri::App) {
if let Some(window) = app.get_window("main") {
let tauri_logger = TauriLogger::new(window);

// Set the TauriLogger as the global logger.
log::set_boxed_logger(Box::new(tauri_logger)).expect("Failed to set logger");
log::set_max_level(log::LevelFilter::Info); // Set max level of logging

// Fern setup for stdout logging
let _ = Dispatch::new()
.chain(std::io::stdout())
.level(LevelFilter::Info) // Set desired log level here
.apply();
}
}

15 changes: 4 additions & 11 deletions src-tauri/src/download.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use std::path::Path;
use reqwest::StatusCode;
use futures::StreamExt;
use tokio::fs::OpenOptions;
use tokio::io::AsyncWriteExt; // Add this line

use tauri::{Window, Manager};

use std::sync::{Mutex};
use std::sync::Mutex;
use crate::app_state::{AppState, BuilderState};
use log::info;

const PROGRESS_EVENT: &str = "progress";

Expand Down Expand Up @@ -46,15 +45,9 @@ pub async fn download_file(window: Window, app: tauri::AppHandle, url: &str, des
dest.write_all(&chunk).await?;
downloaded += chunk.len() as u64;
let percentage = downloaded as f64 / total_size as f64 * 100.0;
let payload = Payload {
pct: format!("Download progress: {:.2}%", percentage).to_string(),
};
window.emit(PROGRESS_EVENT, payload).unwrap();
info!("Download progress: {:.2}%", percentage);
if is_abort_state(app.clone()) {
let payload = Payload {
pct: format!("Download aborted at: {:.2}%", percentage).to_string(),
};
window.emit(PROGRESS_EVENT, payload).unwrap();
info!("Download aborted at: {:.2}%", percentage);
break;
}
}
Expand Down
93 changes: 20 additions & 73 deletions src-tauri/src/esp_idf.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,19 @@
use std::process::{Command, Child, Stdio};
use std::io::{BufReader, BufRead};
use std::time::Duration;
use std::thread;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

use tauri::{Window, Manager};

use crate::app_state::{AppState, BuilderState};
use std::sync::{Mutex};
use log::info;

use tauri::{Window, Manager};
use std::path::Path;
use crate::download::download_file;

use crate::external_command::run_external_command_with_progress;

#[derive(Clone, serde::Serialize)]
struct Payload {
pct: String,
}

const PROGRESS_EVENT: &str = "progress";


fn is_abort_state(app: tauri::AppHandle) -> bool {
let state_mutex = app.state::<Mutex<AppState>>();
let mut state = state_mutex.lock().unwrap();
match state.builder {
BuilderState::Abort => true,
_ => false
}
}

#[cfg(unix)]
const INSTALL_SCRIPT_NAME: &str = "install.sh";

Expand All @@ -43,78 +27,41 @@ pub fn run_install_script(
esp_idf_path: String) -> Result<String, ()>
{
let file_path = Path::new(&esp_idf_path).join(INSTALL_SCRIPT_NAME);
println!("Running install script: {:?}", file_path);
let child_handle = thread::spawn(move || {
// Launch the script
#[cfg(unix)]
let mut child = Command::new("bash")
.arg(file_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to launch script");
#[cfg(windows)]
let mut child = Command::new("cmd")
.arg("/c")
.arg(file_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to launch script");

let stdout = child.stdout.take().unwrap();
let reader = BufReader::new(stdout);

// Read the script's output line by line
for line in reader.lines() {
let line = line.expect("Failed to read line");
println!("{}", line);
let payload = Payload {
pct: line,
};
window.emit(PROGRESS_EVENT, payload).unwrap();

// If is_abort_state is true, kill the script
if is_abort_state(app.clone()) {
child.kill().expect("Failed to kill script");
break;
}
}

let payload = Payload {
pct: "Done".to_string(),
};
window.emit(PROGRESS_EVENT, payload).unwrap();
info!("Running install script: {:?}", file_path);

// Wait for the child to exit completely
child.wait().expect("Failed to wait on child");
});
#[cfg(unix)]
{
let args = vec![file_path.to_str().unwrap()];
run_external_command_with_progress(window.clone(), app.clone(), "bash", &args, PROGRESS_EVENT);
}

// Wait for the child process to finish
child_handle.join().unwrap();
#[cfg(windows)]
{
let args = vec!["/c", file_path.to_str().unwrap()];
run_external_command_with_progress(window.clone(), app.clone(), "cmd", &args, PROGRESS_EVENT);
}

Ok("Success".to_string())

}

pub async fn download_esp_idf(window: Window,
app: tauri::AppHandle,
version: String,
dest_path: String) -> Result<(), ()> {
let url = format!("https://github.com/espressif/esp-idf/releases/download/{}/esp-idf-{}.zip", version, version);
println!("Downloading ESP-IDF from {}", url);
info!("Downloading ESP-IDF from {}", url);
let dest_path = Path::new(&dest_path);

// If the file exists, check if it is not corrupted
if dest_path.exists() {
let is_file_corrupted = {
match check_zip(&dest_path) {
Ok(()) => {
println!("ESP-IDF already downloaded and the file is not corrupted");
info!("ESP-IDF already downloaded.");
return Ok(());
},
Err(err) => {
eprintln!("The file is corrupted: {}", err);
info!("The file is corrupted: {}", err);
true
}
}
Expand All @@ -132,11 +79,11 @@ pub async fn download_esp_idf(window: Window,

match download_file(window, app, &url, dest_path).await {
Ok(_) => {
println!("ESP-IDF downloaded successfully");
info!("ESP-IDF downloaded successfully");
Ok(())
}
Err(err) => {
eprintln!("Failed to download ESP-IDF: {}", err);
info!("Failed to download ESP-IDF: {}", err);
Err(())
}
}
Expand Down
102 changes: 49 additions & 53 deletions src-tauri/src/external_command.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
use std::process::Stdio;
use std::thread;
use std::path::Path;
use std::sync::{Mutex};
use std::sync::Mutex;

use tauri::Manager;
use tauri::Window;
use crate::app_state::{AppState, BuilderState};

#[derive(Clone, serde::Serialize)]
struct ConsoleEvent {
message: String,
}
use log::info;

fn is_abort_state(app: tauri::AppHandle) -> bool {
let state_mutex = app.state::<Mutex<AppState>>();
Expand All @@ -21,15 +16,8 @@ fn is_abort_state(app: tauri::AppHandle) -> bool {
}
}

pub fn emit_rust_console(window: &Window, message: String) {
let event = ConsoleEvent {
message: message,
};
window.emit("rust-console", event).unwrap();
}

use tokio::process::{Command, ChildStdout, ChildStderr};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;
use tokio::io::AsyncBufReadExt;

pub async fn run_external_command_with_progress(
window: Window,
Expand All @@ -41,7 +29,7 @@ pub async fn run_external_command_with_progress(
let cmd_name_owned = cmd_name.to_string();
let cmd_args_owned: Vec<String> = cmd_args.iter().map(|&s| s.to_string()).collect();

emit_rust_console(&window.clone(), format!("Command: {} {}", cmd_name_owned, cmd_args_owned.join(" ")));
info!("Command: {} {}", cmd_name_owned, cmd_args_owned.join(" "));

let mut child = Command::new(&cmd_name_owned)
.args(&cmd_args_owned)
Expand All @@ -50,45 +38,53 @@ pub async fn run_external_command_with_progress(
.spawn()
.expect("Failed to launch command");

let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();

let window_clone_std = window.clone();
let window_clone_err = window.clone();

let stdout_task = tokio::spawn(async move {
let reader = BufReader::new(stdout);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await.expect("Failed to read line from stdout") {
emit_rust_console(&window_clone_std, line);
}
});

let stderr_task = tokio::spawn(async move {
let reader = BufReader::new(stderr);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await.expect("Failed to read line from stderr") {
emit_rust_console(&window_clone_err, line);
}
});

let child_task = tokio::spawn(async move {
child.wait().await.expect("Child process encountered an error")
});

tokio::select! {
_ = stdout_task => {},
_ = stderr_task => {},
status = child_task => {
if let Err(err) = status {
emit_rust_console(&window, format!("Child process exited with {:?}", err));
return Err(());
let mut stdout = tokio::io::BufReader::new(child.stdout.take().unwrap());
let mut stderr = tokio::io::BufReader::new(child.stderr.take().unwrap());

let mut stdout_buf = String::new();
let mut stderr_buf = String::new();

let poll_interval = tokio::time::Duration::from_millis(100); // adjust as necessary

loop {
tokio::select! {
_ = stdout.read_line(&mut stdout_buf) => {
if !stdout_buf.is_empty() {
info!("{}", stdout_buf);
stdout_buf.clear();
}
},
_ = stderr.read_line(&mut stderr_buf) => {
if !stderr_buf.is_empty() {
info!("{}", stderr_buf);
stderr_buf.clear();
}
},
status = child.wait() => {
match status {
Ok(status) if status.success() => {
info!("Done");
return Ok("Child process completed successfully".to_string());
},
Ok(_) => {
info!("Child process exited with an error");
return Err(());
},
Err(err) => {
info!("Child process encountered an error: {:?}", err);
return Err(());
},
}
},
_ = tokio::time::sleep(poll_interval) => {
if is_abort_state(app.clone()) {
info!("Aborting command due to external signal.");
let _ = child.kill();
return Err(());
}
}
}
}

emit_rust_console(&window, "Done".to_string());
Ok("Child process completed successfully".to_string())
}


Expand Down
Loading

0 comments on commit 9b7319e

Please sign in to comment.