1
1
mirror of https://github.com/neosubhamoy/pytubepp-helper.git synced 2026-02-04 11:22:22 +05:30

(feat): added dynamic port config for msghost, new settings page and ui improvements

This commit is contained in:
2025-02-05 20:15:19 +05:30
Verified
parent 279f651b1e
commit d8a191e408
24 changed files with 2236 additions and 449 deletions

54
src-tauri/src/config.rs Normal file
View File

@@ -0,0 +1,54 @@
use serde::{Deserialize, Serialize};
use std::fs;
use directories::ProjectDirs;
use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Config {
pub port: u16,
}
impl Default for Config {
fn default() -> Self {
Self {
port: 3030,
}
}
}
pub fn get_config_dir() -> Option<PathBuf> {
ProjectDirs::from("com", "neosubhamoy", "pytubepp-helper")
.map(|proj_dirs| proj_dirs.config_dir().to_path_buf())
}
pub fn get_config_path() -> Option<PathBuf> {
get_config_dir().map(|dir| dir.join("config.json"))
}
pub fn load_config() -> Config {
if let Some(config_path) = get_config_path() {
if let Ok(content) = fs::read_to_string(config_path) {
if let Ok(config) = serde_json::from_str(&content) {
return config;
}
}
}
Config::default()
}
pub fn save_config(config: &Config) -> Result<(), String> {
let config_dir = get_config_dir()
.ok_or_else(|| "Could not determine config directory".to_string())?;
fs::create_dir_all(&config_dir)
.map_err(|e| format!("Failed to create config directory: {}", e))?;
let config_path = config_dir.join("config.json");
let content = serde_json::to_string_pretty(config)
.map_err(|e| format!("Failed to serialize config: {}", e))?;
fs::write(config_path, content)
.map_err(|e| format!("Failed to write config file: {}", e))?;
Ok(())
}

View File

@@ -1,6 +1,8 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod config;
use config::{Config, load_config, save_config, get_config_path};
use std::{process::Command, sync::Arc, env, time::Duration};
use serde_json::Value;
use tauri::{CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu};
@@ -15,22 +17,134 @@ struct ResponseChannel {
struct WebSocketState {
sender: Option<futures_util::stream::SplitSink<tokio_tungstenite::WebSocketStream<TcpStream>, tokio_tungstenite::tungstenite::Message>>,
response_channel: ResponseChannel,
server_abort: Option<tokio::sync::oneshot::Sender<()>>,
config: Config,
}
async fn is_another_instance_running() -> bool {
match connect_async("ws://127.0.0.1:3030").await {
async fn is_another_instance_running(port: u16) -> bool {
match connect_async(format!("ws://127.0.0.1:{}", port)).await {
Ok(_) => true,
Err(_) => false
}
}
async fn try_bind_ws_port() -> Option<TcpListener> {
match TcpListener::bind("127.0.0.1:3030").await {
async fn try_bind_ws_port(port: u16) -> Option<TcpListener> {
match TcpListener::bind(format!("127.0.0.1:{}", port)).await {
Ok(listener) => Some(listener),
Err(_) => None
}
}
async fn start_websocket_server(app_handle: tauri::AppHandle, port: u16) -> Result<(), String> {
let addr = format!("127.0.0.1:{}", port);
// First ensure any existing server is stopped
{
let state = app_handle.state::<Arc<Mutex<WebSocketState>>>();
let mut state = state.lock().await;
if let Some(old_abort) = state.server_abort.take() {
let _ = old_abort.send(());
// Give it a moment to shut down
sleep(Duration::from_millis(200)).await;
}
}
// Now try to bind to the port
let listener = TcpListener::bind(&addr).await
.map_err(|e| format!("Failed to bind to port {}: {}", port, e))?;
let (abort_sender, mut abort_receiver) = tokio::sync::oneshot::channel();
// Store the new abort sender
{
let state = app_handle.state::<Arc<Mutex<WebSocketState>>>();
let mut state = state.lock().await;
state.server_abort = Some(abort_sender);
}
// Spawn the server task
tokio::spawn(async move {
println!("Starting WebSocket server on port {}", port);
loop {
tokio::select! {
accept_result = listener.accept() => {
match accept_result {
Ok((stream, _)) => {
let app_handle = app_handle.clone();
tokio::spawn(handle_connection(stream, app_handle));
}
Err(e) => {
println!("Error accepting connection: {}", e);
break;
}
}
}
_ = &mut abort_receiver => {
println!("WebSocket server shutting down on port {}...", port);
break;
}
}
}
});
// Wait a moment to ensure the server has started
sleep(Duration::from_millis(100)).await;
Ok(())
}
#[tauri::command]
async fn get_config(state: tauri::State<'_, Arc<Mutex<WebSocketState>>>) -> Result<Config, String> {
let state = state.lock().await;
Ok(state.config.clone())
}
#[tauri::command]
fn get_config_file_path() -> Result<String, String> {
match get_config_path() {
Some(path) => Ok(path.to_string_lossy().into_owned()),
None => Err("Could not determine config path".to_string()),
}
}
#[tauri::command]
async fn update_config(
new_config: Config,
state: tauri::State<'_, Arc<Mutex<WebSocketState>>>,
app_handle: tauri::AppHandle,
) -> Result<Config, String> {
// Save the new config first
save_config(&new_config)?;
// Update the state with new config
{
let mut state = state.lock().await;
state.config = new_config.clone();
}
// Start the new server (this will also handle stopping the old one)
start_websocket_server(app_handle, new_config.port).await?;
Ok(new_config)
}
#[tauri::command]
async fn reset_config(
state: tauri::State<'_, Arc<Mutex<WebSocketState>>>,
app_handle: tauri::AppHandle,
) -> Result<Config, String> {
let config = Config::default();
save_config(&config)?;
{
let mut state = state.lock().await;
state.config = config.clone();
}
start_websocket_server(app_handle, config.port).await?;
Ok(config)
}
#[tauri::command]
async fn send_to_extension(
message: String,
@@ -184,39 +298,41 @@ fn download_stream(url: String, stream: String) {
#[tokio::main]
async fn main() {
let _ = fix_path_env::fix();
let config = load_config();
let port = config.port;
let websocket_state = Arc::new(Mutex::new(WebSocketState {
sender: None,
response_channel: ResponseChannel { sender: None },
server_abort: None,
config,
}));
// Check if another instance is running
if is_another_instance_running().await {
if is_another_instance_running(port).await {
println!("Another instance is already running. Exiting...");
std::process::exit(0);
}
// Try to bind to the WebSocket port with a few retries
let mut listener = None;
let mut port_available = false;
for _ in 0..3 {
if let Some(l) = try_bind_ws_port().await {
listener = Some(l);
if let Some(_) = try_bind_ws_port(port).await {
port_available = true;
break;
}
sleep(Duration::from_millis(100)).await;
}
// If we couldn't bind to the port after retries, assume another instance is running
let listener = match listener {
Some(l) => l,
None => {
println!("Could not bind to WebSocket port. Another instance might be running. Exiting...");
std::process::exit(0);
}
};
if !port_available {
println!("Could not bind to WebSocket port. Another instance might be running. Exiting...");
std::process::exit(0);
}
let args: Vec<String> = env::args().collect();
let start_hidden = args.contains(&"--hidden".to_string());
let websocket_state = Arc::new(Mutex::new(WebSocketState {
sender: None,
response_channel: ResponseChannel { sender: None },
}));
let tray_menu = SystemTrayMenu::new()
.add_item(CustomMenuItem::new("show".to_string(), "Show"))
@@ -257,17 +373,14 @@ async fn main() {
window.hide().unwrap();
}
// Start the initial WebSocket server
let app_handle = app.handle();
let ws_state = websocket_state.clone();
tokio::spawn(async move {
println!("WebSocket server listening on ws://127.0.0.1:3030");
while let Ok((stream, _)) = listener.accept().await {
let app_handle = app_handle.clone();
let ws_state = ws_state.clone();
tokio::spawn(handle_connection(stream, app_handle, ws_state));
if let Err(e) = start_websocket_server(app_handle, port).await {
println!("Failed to start initial WebSocket server: {}", e);
}
});
Ok(())
})
.invoke_handler(tauri::generate_handler![
@@ -275,19 +388,24 @@ async fn main() {
fetch_video_info,
install_program,
download_stream,
receive_frontend_response
receive_frontend_response,
get_config,
update_config,
reset_config,
get_config_file_path
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
async fn handle_connection(stream: TcpStream, app_handle: tauri::AppHandle, ws_state: Arc<Mutex<WebSocketState>>) {
async fn handle_connection(stream: TcpStream, app_handle: tauri::AppHandle) {
let ws_stream = accept_async(stream).await.unwrap();
let (ws_sender, mut ws_receiver) = ws_stream.split();
// Store the sender in the shared state
{
let mut state = ws_state.lock().await;
let state = app_handle.state::<Arc<Mutex<WebSocketState>>>();
let mut state = state.lock().await;
state.sender = Some(ws_sender);
}
@@ -303,7 +421,8 @@ async fn handle_connection(stream: TcpStream, app_handle: tauri::AppHandle, ws_s
// Create a new channel for this request
let (response_sender, response_receiver) = oneshot::channel();
{
let mut state = ws_state.lock().await;
let state = app_handle.state::<Arc<Mutex<WebSocketState>>>();
let mut state = state.lock().await;
state.response_channel.sender = Some(response_sender);
}
@@ -315,7 +434,8 @@ async fn handle_connection(stream: TcpStream, app_handle: tauri::AppHandle, ws_s
.unwrap_or_else(|e| format!("Error receiving response: {:?}", e));
// Send the response back through WebSocket
let mut state = ws_state.lock().await;
let state = app_handle.state::<Arc<Mutex<WebSocketState>>>();
let mut state = state.lock().await;
if let Some(sender) = &mut state.sender {
let _ = sender.send(tokio_tungstenite::tungstenite::Message::Text(response)).await;
}
@@ -327,6 +447,7 @@ async fn handle_connection(stream: TcpStream, app_handle: tauri::AppHandle, ws_s
println!("WebSocket connection closed");
// Remove the sender from the shared state when the connection closes
let mut state = ws_state.lock().await;
let state = app_handle.state::<Arc<Mutex<WebSocketState>>>();
let mut state = state.lock().await;
state.sender = None;
}