use std::fs; use std::io::prelude::*; use std::net::{TcpListener, TcpStream}; use std::path::Path; use std::process::Command; fn main() { println!("🚀 Starting FileBrowser Widget Example Server..."); println!(); // Check if we have the built widget files in dist/ directory let dist_dir = Path::new("dist"); let widget_files = [ "file_browser_widget.js", "file_browser_widget_bg.wasm", "uppy.min.js", "uppy.min.css" ]; let mut missing_files = Vec::new(); for file in &widget_files { let file_path = dist_dir.join(file); if !file_path.exists() { missing_files.push(*file); } } // Check if we have the HTML file in examples/ directory let examples_dir = Path::new("examples"); let html_file = examples_dir.join("index.html"); if !html_file.exists() { missing_files.push("examples/index.html"); } if !missing_files.is_empty() { println!("❌ Error: Missing required files:"); for file in &missing_files { println!(" - {}", file); } println!(); println!("💡 Run the build script first: ./build.sh"); println!(" This will generate the required widget files in dist/."); std::process::exit(1); } println!("✅ All required files found"); println!(); // Create compressed versions for optimized serving create_compressed_assets(); let listener = TcpListener::bind("127.0.0.1:8081").unwrap(); println!("🌐 FileBrowser Widget Example Server"); println!("📡 Serving on http://localhost:8081"); println!("🔗 Open http://localhost:8081 in your browser to test the widget"); println!("âšī¸ Press Ctrl+C to stop the server"); println!(); for stream in listener.incoming() { let stream = stream.unwrap(); handle_connection(stream); } } fn handle_connection(mut stream: TcpStream) { let mut buffer = [0; 1024]; stream.read(&mut buffer).unwrap(); let request = String::from_utf8_lossy(&buffer[..]); let request_line = request.lines().next().unwrap_or(""); if let Some(path) = request_line.split_whitespace().nth(1) { let file_path = match path { "/" => "index.html", // Serve the HTML file from examples/ path if path.starts_with('/') => { let clean_path = &path[1..]; // Remove leading slash // Check if this is a request for a static asset if is_static_asset(clean_path) { clean_path } else { // For all non-asset routes, serve index.html to support client-side routing "index.html" } }, _ => "index.html", }; serve_file(&mut stream, file_path); } else { serve_404(&mut stream); } } fn is_static_asset(path: &str) -> bool { // Check if the path is for a static asset (widget files) matches!(path, "file_browser_widget.js" | "file_browser_widget_bg.wasm" | "file_browser_widget.d.ts" | "uppy.min.js" | "uppy.min.css" | "favicon.ico" ) } fn create_compressed_assets() { println!("🗜 Creating compressed assets for optimized serving..."); // Create examples/compressed directory let compressed_dir = Path::new("examples/compressed"); if !compressed_dir.exists() { fs::create_dir_all(compressed_dir).expect("Failed to create compressed directory"); } // List of files to compress from dist/ let files_to_compress = [ "file_browser_widget.js", "file_browser_widget_bg.wasm", "uppy.min.js", "uppy.min.css", ]; for file in &files_to_compress { let source_path = format!("dist/{}", file); let compressed_path = format!("examples/compressed/{}.gz", file); // Check if source exists and compressed version needs updating if Path::new(&source_path).exists() { let needs_compression = !Path::new(&compressed_path).exists() || fs::metadata(&source_path).unwrap().modified().unwrap() > fs::metadata(&compressed_path).unwrap_or_else(|_| fs::metadata(&source_path).unwrap()).modified().unwrap(); if needs_compression { let output = Command::new("gzip") .args(&["-9", "-c", &source_path]) .output() .expect("Failed to execute gzip"); if output.status.success() { fs::write(&compressed_path, output.stdout) .expect("Failed to write compressed file"); let original_size = fs::metadata(&source_path).unwrap().len(); let compressed_size = fs::metadata(&compressed_path).unwrap().len(); let ratio = (compressed_size as f64 / original_size as f64 * 100.0) as u32; println!(" ✅ {} compressed: {} → {} bytes ({}%)", file, original_size, compressed_size, ratio); } else { println!(" âš ī¸ Failed to compress {}", file); } } } } println!("đŸŽ¯ Compressed assets ready in examples/compressed/"); println!(); } fn serve_file(stream: &mut TcpStream, file_path: &str) { let current_dir = std::env::current_dir().unwrap(); // Determine which directory to serve from based on file type let (full_path, use_gzip) = match file_path { "index.html" => (current_dir.join("examples").join(file_path), false), _ => { let base_path = current_dir.join("dist").join(file_path); let gzip_path = current_dir.join("examples/compressed").join(format!("{}.gz", file_path)); // Prefer gzipped version from examples/compressed if it exists if gzip_path.exists() { (gzip_path, true) } else { (base_path, false) } } }; if full_path.exists() && full_path.is_file() { match fs::read(&full_path) { Ok(contents) => { let content_type = get_content_type(file_path); let mut response = format!( "HTTP/1.1 200 OK\r\nContent-Type: {}\r\nContent-Length: {}", content_type, contents.len() ); // Add gzip encoding header if serving compressed content if use_gzip { response.push_str("\r\nContent-Encoding: gzip"); } response.push_str("\r\n\r\n"); let _ = stream.write_all(response.as_bytes()); let _ = stream.write_all(&contents); let compression_info = if use_gzip { " (gzipped)" } else { "" }; println!("📄 Served: {}{} ({} bytes)", file_path, compression_info, contents.len()); } Err(_) => serve_404(stream), } } else { serve_404(stream); } } fn serve_404(stream: &mut TcpStream) { let response = "HTTP/1.1 404 NOT FOUND\r\nContent-Type: text/html\r\n\r\n

404 Not Found

"; stream.write_all(response.as_bytes()).unwrap(); stream.flush().unwrap(); println!("❌ 404 Not Found"); } fn get_content_type(file_path: &str) -> &'static str { let extension = Path::new(file_path) .extension() .and_then(|ext| ext.to_str()) .unwrap_or(""); match extension { "html" => "text/html; charset=utf-8", "js" => "application/javascript", "css" => "text/css", "wasm" => "application/wasm", "json" => "application/json", "png" => "image/png", "jpg" | "jpeg" => "image/jpeg", "gif" => "image/gif", "svg" => "image/svg+xml", "ico" => "image/x-icon", "ts" => "application/typescript", "md" => "text/markdown", _ => "text/plain", } }