diff --git a/src/main.rs b/src/main.rs index d50081f..dba07a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,8 +9,7 @@ use clap::Parser; use native_windows_gui::{self as nwg, NativeUi}; mod stremio_app; use crate::stremio_app::{ - constants::{DEV_ENDPOINT, IPC_PATH, STA_ENDPOINT, WEB_ENDPOINT}, - stremio_server::StremioServer, + constants::{DEV_ENDPOINT, IPC_PATH, STA_ENDPOINT, STREMIO_SERVER_DEV_MODE, WEB_ENDPOINT}, MainWindow, PipeClient, }; @@ -75,9 +74,10 @@ fn main() { } // END IPC - if !opt.development { - StremioServer::new(); - } + std::env::set_var( + STREMIO_SERVER_DEV_MODE, + if opt.development { "true" } else { "false" }, + ); let webui_url = if opt.development && opt.webui_url == WEB_ENDPOINT { DEV_ENDPOINT.to_string() diff --git a/src/stremio_app/app.rs b/src/stremio_app/app.rs index 4e0839f..5e9527b 100644 --- a/src/stremio_app/app.rs +++ b/src/stremio_app/app.rs @@ -27,6 +27,8 @@ use crate::stremio_app::{ PipeServer, }; +use super::stremio_server::StremioServer; + #[derive(Default, NwgUi)] pub struct MainWindow { pub command: String, @@ -50,11 +52,13 @@ pub struct MainWindow { #[nwg_events((tray, MousePressLeftUp): [Self::on_show], (tray_exit, OnMenuItemSelected): [nwg::stop_thread_dispatch()], (tray_show_hide, OnMenuItemSelected): [Self::on_show_hide], (tray_topmost, OnMenuItemSelected): [Self::on_toggle_topmost]) ] pub tray: SystemTray, #[nwg_partial(parent: window)] - pub webview: WebView, + pub splash_screen: SplashImage, + #[nwg_partial(parent: window)] + pub server: StremioServer, #[nwg_partial(parent: window)] pub player: Player, #[nwg_partial(parent: window)] - pub splash_screen: SplashImage, + pub webview: WebView, #[nwg_control] #[nwg_events(OnNotice: [Self::on_toggle_fullscreen_notice] )] pub toggle_fullscreen_notice: nwg::Notice, diff --git a/src/stremio_app/constants.rs b/src/stremio_app/constants.rs index 3c59a6a..e8f2cbb 100644 --- a/src/stremio_app/constants.rs +++ b/src/stremio_app/constants.rs @@ -1,13 +1,16 @@ -pub const APP_NAME: &str = "Stremio"; -pub const IPC_PATH: &str = "//./pipe/com.stremio5."; -pub const DEV_ENDPOINT: &str = "http://127.0.0.1:11470"; -pub const WEB_ENDPOINT: &str = "https://web.stremio.com/"; -pub const STA_ENDPOINT: &str = "https://staging.strem.io/"; -pub const WINDOW_MIN_WIDTH: i32 = 1000; -pub const WINDOW_MIN_HEIGHT: i32 = 600; -pub const UPDATE_INTERVAL: u64 = 12 * 60 * 60; -pub const UPDATE_ENDPOINT: [&str; 3] = [ - "https://www.strem.io/updater/check?product=stremio-shell-ng", - "https://www.stremio.com/updater/check?product=stremio-shell-ng", - "https://www.stremio.net/updater/check?product=stremio-shell-ng", -]; +pub const APP_NAME: &str = "Stremio"; +pub const IPC_PATH: &str = "//./pipe/com.stremio5."; +pub const DEV_ENDPOINT: &str = "http://127.0.0.1:11470"; +pub const WEB_ENDPOINT: &str = "https://web.stremio.com/"; +pub const STA_ENDPOINT: &str = "https://staging.strem.io/"; +pub const WINDOW_MIN_WIDTH: i32 = 1000; +pub const WINDOW_MIN_HEIGHT: i32 = 600; +pub const UPDATE_INTERVAL: u64 = 12 * 60 * 60; +pub const UPDATE_ENDPOINT: [&str; 3] = [ + "https://www.strem.io/updater/check?product=stremio-shell-ng", + "https://www.stremio.com/updater/check?product=stremio-shell-ng", + "https://www.stremio.net/updater/check?product=stremio-shell-ng", +]; +pub const STREMIO_SERVER_DEV_MODE: &str = "STREMIO_SERVER_DEV_MODE"; +pub const SRV_BUFFER_SIZE: usize = 1024; +pub const SRV_LOG_SIZE: usize = 20; diff --git a/src/stremio_app/stremio_server/server.rs b/src/stremio_app/stremio_server/server.rs index 3a1af05..c71ffd1 100644 --- a/src/stremio_app/stremio_server/server.rs +++ b/src/stremio_app/stremio_server/server.rs @@ -1,6 +1,15 @@ -use native_windows_gui as nwg; +use crate::stremio_app::constants::{SRV_BUFFER_SIZE, SRV_LOG_SIZE, STREMIO_SERVER_DEV_MODE}; +use native_windows_gui::{self as nwg, PartialUi}; +use std::io::Write; use std::{ - env, fs, os::windows::process::CommandExt, path, process::Command, thread, time::Duration, + env, fs, + io::Read, + ops::Deref, + os::windows::process::CommandExt, + path, + process::{Command, Stdio}, + sync::{Arc, Mutex}, + thread, }; use winapi::um::{ processthreadsapi::GetCurrentProcess, @@ -12,10 +21,23 @@ use winapi::um::{ }, }; -pub struct StremioServer {} +#[derive(Default)] +pub struct StremioServer { + development: bool, + parent: nwg::ControlHandle, + crash_notice: nwg::Notice, + logs: Arc>, +} impl StremioServer { - pub fn new() -> StremioServer { + pub fn start(&self) { + if self.development { + return; + } + let (tx, rx) = flume::unbounded(); + let logs = self.logs.clone(); + let sender = self.crash_notice.sender(); + thread::spawn(move || { // Use Win32JobObject to kill the child process when the parent process is killed // With the JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK and JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flags @@ -45,31 +67,150 @@ impl StremioServer { .and_then(fs::canonicalize) .expect("Cannot get the current executable path"); path.pop(); - loop { - let runtime_path = path.clone().join(path::Path::new("stremio-runtime")); - let server_path = path.clone().join(path::Path::new("server.js")); - let child = Command::new(runtime_path) - .arg(server_path) - .creation_flags(CREATE_NO_WINDOW) - .spawn(); - match child { - Ok(mut child) => { - // TODO: store somehow last few lines of the child's stdout/stderr instead of just waiting - child.wait().expect("Cannot wait for the server"); - } - Err(err) => { - nwg::error_message( - "Stremio server", - format!("Cannot execute stremio-runtime: {}", &err).as_str(), - ); - break; - } - }; - // TODO: show error message with the child's stdout/stderr - thread::sleep(Duration::from_millis(500)); - dbg!("Trying to restart the server..."); + let lines = Arc::new(Mutex::new(String::new())); + let runtime_path = path.clone().join(path::Path::new("stremio-runtime")); + let server_path = path.clone().join(path::Path::new("server.js")); + let child = Command::new(runtime_path) + .arg(server_path) + .creation_flags(CREATE_NO_WINDOW) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn(); + match child { + Ok(mut child) => { + let mut stdout = child.stdout.take().unwrap(); + let out_lines = lines.clone(); + let tx = tx.clone(); + let out_thread = thread::spawn(move || { + let http_endpoint = String::new(); + loop { + let mut buffer = [0; SRV_BUFFER_SIZE]; + let on = stdout.read(&mut buffer[..]).unwrap_or(!0); + if on > buffer.len() { + continue; + } + std::io::stdout().write(&buffer).ok(); + let string_data = String::from_utf8_lossy(&buffer[..on]); + { + let lines = &mut *out_lines.lock().unwrap(); + *lines += string_data.deref(); + if http_endpoint.is_empty() { + if let Some(http_endpoint) = string_data + .lines() + .find(|line| line.starts_with("EngineFS server started at")) + { + let http_endpoint = + http_endpoint.split_whitespace().last().unwrap(); + println!("HTTP endpoint: {}", http_endpoint); + let endpoint = http_endpoint.to_string(); + tx.send(endpoint.clone()).ok(); + } + } + *lines = lines + .lines() + .rev() + .take(SRV_LOG_SIZE) + .collect::>() + .into_iter() + .rev() + .collect::>() + .join("\n"); + }; + if on == 0 { + // Server terminated + break; + } + } + }); + + let mut stderr = child.stderr.take().unwrap(); + let err_lines = lines.clone(); + let err_thread = thread::spawn(move || { + let mut buffer = [0; SRV_BUFFER_SIZE]; + loop { + let en = stderr.read(&mut buffer[..]).unwrap_or(!0); + if en > buffer.len() { + continue; + } + std::io::stderr().write(&buffer).ok(); + let string_data = String::from_utf8_lossy(&buffer[..en]); + // eprint!("{:?}", &buffer); + { + let lines = &mut *err_lines.lock().unwrap(); + *lines += string_data.deref(); + *lines = lines + .lines() + .rev() + .take(SRV_LOG_SIZE) + .collect::>() + .into_iter() + .rev() + .collect::>() + .join("\n"); + }; + if en == 0 { + // Server terminated + break; + } + } + }); + out_thread.join().ok(); + err_thread.join().ok(); + } + Err(err) => { + nwg::error_message( + "Stremio server", + format!("Cannot execute stremio-runtime: {}", &err).as_str(), + ); + } + }; + + { + let mut logs = logs.lock().unwrap(); + *logs = lines.lock().unwrap().deref().to_string(); } + println!("Server terminated."); + sender.notice(); }); - StremioServer {} + + // Wait for the server to start + rx.recv().unwrap(); + } +} + +impl PartialUi for StremioServer { + fn build_partial>( + data: &mut Self, + parent: Option, + ) -> Result<(), nwg::NwgError> { + if std::env::var(STREMIO_SERVER_DEV_MODE).unwrap_or("false".to_string()) == "true" { + data.development = true; + } + + data.parent = parent.unwrap().into().clone(); + + nwg::Notice::builder() + .parent(data.parent) + .build(&mut data.crash_notice) + .ok(); + data.start(); + println!("Stremio server started"); + Ok(()) + } + fn process_event<'a>( + &self, + evt: nwg::Event, + _evt_data: &nwg::EventData, + handle: nwg::ControlHandle, + ) { + use nwg::Event as E; + if evt == E::OnNotice && handle == self.crash_notice.handle { + nwg::modal_error_message( + self.parent, + "Stremio server crash log", + self.logs.lock().unwrap().deref(), + ); + self.start(); + } } }