recipe-sync-server-rs/src/main.rs

148 lines
3.0 KiB
Rust
Raw Normal View History

2026-02-21 01:55:41 +02:00
use std::{
fs,
2026-02-21 01:55:41 +02:00
io::Write,
net::{SocketAddr, TcpListener, TcpStream},
2026-02-28 15:14:02 +02:00
path::{Path, PathBuf},
2026-02-21 01:55:41 +02:00
result,
str::FromStr,
};
use clap::Parser;
use crate::{buffer::ByteBuffer, cli::CliArgs};
mod buffer;
mod cli;
2026-02-21 01:55:41 +02:00
type Result<T> = result::Result<T, ()>;
fn main() -> Result<()> {
env_logger::init();
let args = CliArgs::parse();
let ip = args.ip.unwrap_or_default();
let port = args.port.unwrap_or_default();
2026-02-28 15:14:02 +02:00
let export = fs::canonicalize(args.export).expect("failed to get absolute export path");
2026-02-22 13:49:24 +02:00
let allowed = args.allowed_devices;
let socket = SocketAddr::from_str(&format!("{ip}:{port}")).unwrap();
2026-02-21 01:55:41 +02:00
let listener = TcpListener::bind(socket).unwrap();
for stream in listener.incoming() {
match stream {
2026-02-22 13:49:24 +02:00
Ok(conn) => handle_connection(conn, export.clone(), allowed.clone())?,
2026-02-21 01:55:41 +02:00
Err(e) => eprintln!("Something went wrong while listening {e}"),
}
}
Ok(())
}
2026-02-22 13:49:24 +02:00
fn handle_connection(
mut conn: TcpStream,
2026-02-28 15:14:02 +02:00
export: PathBuf,
2026-02-22 13:49:24 +02:00
allowed_devices: Option<Vec<String>>,
) -> Result<()> {
let mut paths = vec![];
let mut buffer = ByteBuffer::default();
let remote_ip = conn
.peer_addr()
.expect("Could not get remote IP address")
.ip();
2026-02-22 13:49:24 +02:00
if let Some(allowed_devices) = allowed_devices {
2026-02-22 13:49:24 +02:00
for allowed in allowed_devices {
let allowed_net = ipnet::IpNet::from_str(&allowed).unwrap();
let is_allowed = allowed_net.contains(&remote_ip);
if !is_allowed {
log::error!("{remote_ip} tried to connect but is not allowed");
return Ok(());
}
}
}
walk_dir(&export, &mut paths);
2026-02-28 15:14:02 +02:00
let export_path = export
.as_path()
.to_str()
.expect("invalid export path, aborting");
let files_sent = paths.len();
log::info!("Sending {files_sent} files to {remote_ip}");
buffer.write_usize(files_sent);
for file in paths {
2026-02-28 15:14:02 +02:00
let mut path = file.replace(export_path, "");
if path.starts_with("/") {
path.remove(0);
}
log::debug!("Sending {path}");
buffer.write_string(&path);
match fs::read(file) {
2026-02-21 01:55:41 +02:00
Ok(data) => {
buffer.write_usize(data.len());
buffer.write_bytes(&data);
2026-02-21 01:55:41 +02:00
}
Err(_) => {
buffer.write_usize(0);
eprintln!("No file found");
}
2026-02-21 01:55:41 +02:00
};
}
let _ = conn.write_all(&buffer);
let _ = conn.flush();
2026-02-21 01:55:41 +02:00
Ok(())
}
fn walk_dir<P: AsRef<Path>>(path: P, file_paths: &mut Vec<String>) {
for entry in fs::read_dir(path).unwrap().flatten() {
if entry.path().is_dir() {
walk_dir(entry.path(), file_paths);
continue;
}
let file_path = entry.path().to_str().unwrap().to_string();
file_paths.push(file_path);
}
}
2026-02-21 01:55:41 +02:00
#[cfg(test)]
mod tests {
use std::io::Read;
use super::*;
const IP: &str = "0.0.0.0";
const PORT: &str = "9696";
2026-02-21 01:55:41 +02:00
#[test]
fn test_connection() {
let conn = TcpStream::connect(format!("{IP}:{PORT}"));
2026-02-21 01:55:41 +02:00
assert!(conn.is_ok());
let mut conn = conn.unwrap();
let mut buffer = ByteBuffer::default();
2026-02-21 01:55:41 +02:00
let bytes_read = conn.read_to_end(&mut buffer);
assert!(bytes_read.is_ok());
let file_name = buffer.read_string();
2026-02-21 01:55:41 +02:00
assert_eq!(file_name, "examples/test.md");
// let content = buffer.read_bytes(buffer.unread_len());
// println!("{}", String::from_utf8(content).unwrap());
2026-02-21 01:55:41 +02:00
}
}