Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NCGMGR v0.8.0 #9

Merged
merged 11 commits into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ncgmgr",
"version": "0.7.0",
"version": "0.8.0",
"private": true,
"scripts": {
"dev": "vite",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ncgmgr"
version = "0.7.0"
version = "0.8.0"
description = "Helps manage NodeCG installations."
authors = [ "inkfarer" ]
license = ""
Expand Down
59 changes: 28 additions & 31 deletions src-tauri/src/bundles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ use tauri_plugin_shell::ShellExt;
use crate::error::Error;
use crate::git::{get_tag_name_at_head, try_open_repository};
use crate::log::LogEmitter;
use crate::{config, git};
use crate::{log_npm_install, npm};
use crate::{config, git, log, npm};

#[derive(PartialEq, Debug)]
struct ParsedBundleUrl {
Expand All @@ -34,41 +33,38 @@ fn parse_bundle_url(url: String) -> Result<ParsedBundleUrl, Error> {
}
}

#[tauri::command(async)]
pub fn install_bundle(handle: tauri::AppHandle, bundle_url: String) -> Result<(), Error> {
let logger = LogEmitter::with_progress(&handle, "install-bundle", 5);
#[tauri::command]
pub async fn install_bundle(handle: tauri::AppHandle, bundle_url: String) -> Result<(), Error> {
let logger = LogEmitter::stepped(&handle, "install-bundle", 5);
let parsed_url = parse_bundle_url(bundle_url)?;

logger.emit(&format!("Installing {}...", parsed_url.bundle_name));
logger.emit_progress_stepped(0, &format!("Installing {}...", parsed_url.bundle_name));

let install_dir = config::with_config(handle.clone(), |c| Ok(c.nodecg_install_dir))?
.ok_or(Error::MissingInstallDir)?;
let dir_bundles = format!("{}/bundles", install_dir);
if !Path::new(&dir_bundles).exists() {
logger.emit("Creating missing bundles directory");
logger.emit_log("Creating missing bundles directory");
fs::create_dir(dir_bundles)?;
}
logger.emit_progress(1);

logger.emit("Fetching version list...");
logger.emit_progress_stepped(1, "Loading version list...");
let versions = git::fetch_versions_for_url(&parsed_url.bundle_url)?;
logger.emit_progress(2);

logger.emit("Cloning repository...");
logger.emit_progress_stepped(2, "Cloning repository...");
let bundle_path = format!("{}/bundles/{}", install_dir, parsed_url.bundle_name);
let repo = Repository::clone(&parsed_url.bundle_url, bundle_path.clone())?;
logger.emit_progress(3);
if versions.len() > 1 {
let latest_version = versions.first().unwrap();
logger.emit(&format!("Checking out version {}...", latest_version));
logger.emit_progress_stepped(3, &format!("Checking out version {}...", latest_version));

git::checkout_version(&repo, latest_version.to_string())?;
}
logger.emit_progress(4);

logger.emit_progress_stepped(4, "Installing npm dependencies...");
let shell = handle.shell();
let child = npm::install_npm_dependencies(shell, &bundle_path)?;
log_npm_install(logger, child);
log::emit_tauri_process_output(&logger, child).await?;
logger.emit_progress_stepped(5, "Done!");
Ok(())
}

Expand Down Expand Up @@ -99,37 +95,38 @@ pub fn fetch_bundle_versions(
}

#[tauri::command(async)]
pub fn set_bundle_version(
pub async fn set_bundle_version(
handle: tauri::AppHandle,
bundle_name: String,
version: String,
) -> Result<(), Error> {
let install_dir = config::with_config(handle.clone(), |c| Ok(c.nodecg_install_dir))?
.ok_or(Error::MissingInstallDir)?;
let logger = LogEmitter::with_progress(&handle, "change-bundle-version", 4);
let logger = LogEmitter::stepped(&handle, "change-bundle-version", 2);
logger.emit_progress_stepped(0, &format!("Installing {} {}...", bundle_name, version));
let bundle_dir = format!("{}/bundles/{}", install_dir, bundle_name);
let path = Path::new(&bundle_dir);

if !path.exists() {
return Err(Error::MissingBundle(bundle_name));
}

logger.emit_progress(1);
logger.emit(&format!("Installing {} {}...", bundle_name, version));
let repo = Repository::open(path)?;
logger.emit_progress(2);
let mut remote = git::get_remote(&repo)?;
remote.fetch(
&[""],
Some(FetchOptions::new().download_tags(AutotagOption::All)),
None,
)?;
git::checkout_version(&repo, version.clone())?;
logger.emit_progress(3);
{
let repo = Repository::open(path)?;
let mut remote = git::get_remote(&repo)?;
remote.fetch(
&[""],
Some(FetchOptions::new().download_tags(AutotagOption::All)),
None,
)?;
git::checkout_version(&repo, version.clone())?;
}

let shell = handle.shell();
let child = npm::install_npm_dependencies(shell, &bundle_dir)?;
log_npm_install(logger, child);
logger.emit_progress_stepped(1, "Installing npm dependencies...");
log::emit_tauri_process_output(&logger, child).await?;
logger.emit_progress_stepped(2, "Done!");
Ok(())
}

Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub enum Error {
NodeCGInstall(String),
#[error("Error launching NodeCG: {0}")]
NodeCGLaunch(String),
#[error("Error stopping NodeCG: {0}")]
NodeCGStop(String),
#[error("NodeCG install directory is not configured")]
MissingInstallDir,
#[error("Could not determine default install directory for NodeCG. Please select one manually.")]
Expand Down
107 changes: 68 additions & 39 deletions src-tauri/src/log.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::fmt;
use std::fmt::{Display, Formatter};
use tauri::async_runtime::{spawn, JoinHandle, Receiver};
use tauri::Manager;
use tauri_plugin_shell::process::{CommandEvent, TerminatedPayload};
Expand All @@ -19,14 +20,13 @@ impl LogEmitter {
}
}

pub fn with_progress(handle: &tauri::AppHandle, key: &str, max_progress_step: u32) -> LogEmitter {
pub fn stepped(handle: &tauri::AppHandle, key: &str, max_progress_step: u32) -> LogEmitter {
let mut emitter = LogEmitter::new(handle, key);
emitter.max_progress_step = Some(max_progress_step);
emitter.emit_progress(0);
emitter
}

pub fn emit(&self, msg: &str) -> () {
pub fn emit_log(&self, msg: &str) -> () {
self
.handle
.emit(
Expand All @@ -37,31 +37,41 @@ impl LogEmitter {
)
.expect("Failed to emit log message");
}

pub fn emit_progress(&self, message: &str) -> () {
self
.handle
.emit(
&format!("progress:{}", self.key),
ProgressPayload {
message: message.to_string(),
step: None,
max_step: self.max_progress_step
}
)
.expect("Failed to emit progress message");
}

pub fn emit_progress(&self, step: u32) -> () {
if self.max_progress_step.is_some() {
self
.handle
.emit(
&format!("progress:{}", self.key),
ProgressPayload {
step,
max_step: self.max_progress_step.unwrap(),
},
)
.expect("Failed to emit log message");
}
pub fn emit_progress_stepped(&self, step: u32, message: &str) -> () {
self
.handle
.emit(
&format!("progress:{}", self.key),
ProgressPayload {
message: message.to_string(),
step: Some(step),
max_step: self.max_progress_step,
},
)
.expect("Failed to emit progress message");
}

pub fn emit_process_closure(&self, payload: &TerminatedPayload) -> () {
pub fn emit_process_closure(&self, payload: &ProcessResult) -> () {
self
.handle
.emit(
&format!("process-exit:{}", self.key),
ProcessClosurePayload {
code: payload.code,
success: is_process_termination_successful(payload),
},
payload,
)
.expect("Failed to emit log message for process closure");
}
Expand All @@ -73,15 +83,36 @@ struct LogPayload {
}

#[derive(Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct ProgressPayload {
step: u32,
max_step: u32,
message: String,
step: Option<u32>,
max_step: Option<u32>,
}

#[derive(Clone, serde::Serialize)]
struct ProcessClosurePayload {
code: Option<i32>,
success: bool,
pub struct ProcessResult {
pub code: Option<i32>,
pub success: bool,
}

impl ProcessResult {
fn from_terminated_payload(payload: &TerminatedPayload) -> Self {
ProcessResult {
code: payload.code,
success: is_process_termination_successful(payload)
}
}
}

impl Display for ProcessResult {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Some(code) = self.code {
write!(f, "Process exited with code {}", code)
} else {
write!(f, "Process exited")
}
}
}

fn is_process_termination_successful(payload: &TerminatedPayload) -> bool {
Expand All @@ -91,32 +122,30 @@ fn is_process_termination_successful(payload: &TerminatedPayload) -> bool {
}

pub fn emit_tauri_process_output(
logger: LogEmitter,
logger: &LogEmitter,
mut receiver: Receiver<CommandEvent>,
) -> JoinHandle<Option<i32>> {
) -> JoinHandle<Option<ProcessResult>> {
let process_logger = logger.clone();
spawn(async move {
let mut exit_code: Option<i32> = None;
let mut result: Option<ProcessResult> = None;
while let Some(item) = receiver.recv().await {
logger.emit(&*match item {
process_logger.emit_log(&*match item {
CommandEvent::Stderr(msg) => String::from_utf8(msg)
.unwrap_or_else(|e| format!("Failed to decode output: {}", e.to_string())),
CommandEvent::Stdout(msg) => String::from_utf8(msg)
.unwrap_or_else(|e| format!("Failed to decode output: {}", e.to_string())),
CommandEvent::Error(msg) => msg,
CommandEvent::Terminated(payload) => {
logger.emit_process_closure(&payload);
match payload.code {
Some(code) => {
exit_code = Some(code);
format!("Process exited with code {}", code.to_string())
}
None => "Process exited".to_string(),
}
let process_result = ProcessResult::from_terminated_payload(&payload);
process_logger.emit_process_closure(&process_result);
let log_line = process_result.to_string();
result = Some(process_result);
log_line
}
_ => "".to_string(),
})
}
exit_code
result
})
}

Expand Down
38 changes: 21 additions & 17 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
)]

use crate::log::{err_to_string, format_error, LogEmitter};
use tauri::async_runtime::{JoinHandle, Receiver};
use tauri::{Manager, RunEvent};
use tauri_plugin_shell::process::CommandEvent;

mod bundles;
mod config;
Expand All @@ -19,23 +17,29 @@ mod npm;

use nodecg::ManagedNodecg;

fn log_npm_install(
logger: LogEmitter,
receiver: Receiver<CommandEvent>,
) -> JoinHandle<Option<i32>> {
logger.emit("Installing npm dependencies...");
log::emit_tauri_process_output(logger, receiver)
}

#[tauri::command(async)]
fn open_path_in_terminal(path: String) -> Result<(), String> {
if cfg!(target_os = "windows") {
return match std::process::Command::new("cmd")
.args(["/c", "start", "cmd.exe", "/k", &format!("cd /D {}", path)])
.spawn()
{
Ok(_) => Ok(()),
Err(e) => format_error("Failed to open path", e),
/*
Needs to be different when in development mode, or else the new command line will open in the
same command prompt that the application was originally launched from
*/
return if cfg!(debug_assertions) {
match std::process::Command::new("cmd")
.args(["/c", "start", "cmd.exe", "/k", &format!("cd /D {}", path)])
.spawn()
{
Ok(_) => Ok(()),
Err(e) => format_error("Failed to open path", e),
}
} else {
match std::process::Command::new("cmd")
.args(["/k", &format!("cd /D {}", path)])
.spawn()
{
Ok(_) => Ok(()),
Err(e) => format_error("Failed to open path", e),
}
};
} else if cfg!(target_os = "macos") {
return match std::process::Command::new("open")
Expand Down Expand Up @@ -95,7 +99,7 @@ fn main() {
Ok(_) => {}
Err(e) => {
let logger = LogEmitter::new(&handle, "run-nodecg");
logger.emit(&err_to_string("Failed to shut down NodeCG", e));
logger.emit_log(&err_to_string("Failed to shut down NodeCG", e));
api.prevent_exit();
}
}
Expand Down
Loading