237 lines
8.1 KiB
Rust
237 lines
8.1 KiB
Rust
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<h1>404 Not Found</h1>";
|
|
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",
|
|
}
|
|
} |