quick-file-transfer 0.10.2

Transfer files quickly, safely, and painlessly between hosts on a local network
Documentation
use crate::{
    config::{
        transfer::{
            command::{ServerCommand, ServerResult},
            listen::ListenArgs,
        },
        Config,
    },
    util::{read_server_cmd, server_handshake},
};
use anyhow::{bail, Result};
use std::{
    net::{IpAddr, TcpListener},
    path::PathBuf,
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc,
    },
};

mod path;
use path::validate_remote_path;

pub mod util;
use util::{join_all_threads, send_result, spawn_child_on_new_port};

pub mod child;

pub fn listen(_cfg: &Config, listen_args: &ListenArgs) -> Result<()> {
    let ListenArgs {
        ip,
        port,
        output: _,
        decompression: _,
        output_dir: _,
        remote: _,
    } = listen_args;

    let stop_flag: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
    let ip: IpAddr = ip.parse()?;
    let initial_listener = TcpListener::bind((ip, *port))?;
    run_server(&initial_listener, listen_args, &stop_flag)
}

fn run_server(
    initial_listener: &TcpListener,
    args: &ListenArgs,
    stop_flag: &Arc<AtomicBool>,
) -> anyhow::Result<()> {
    let mut thread_handles = vec![];
    match initial_listener.accept() {
        Ok((mut socket, addr)) => {
            tracing::info!("Client accepted at: {addr:?}");
            server_handshake(&mut socket)?;
            let mut root_dest: Option<PathBuf> = None; // Used as root destination if invoked through ssh/scp mode
            let mut cmd_buf: [u8; 256] = [0; 256];
            loop {
                if let Some(cmd) = read_server_cmd(&mut socket, &mut cmd_buf)? {
                    tracing::trace!("Received command: {cmd:?}");
                    match cmd {
                        ServerCommand::GetFreePort(_) => {
                            let child_thread_handle = spawn_child_on_new_port(
                                &mut socket,
                                args,
                                &Arc::clone(stop_flag),
                                &cmd,
                                root_dest.clone(),
                            )?;
                            thread_handles.push(child_thread_handle);
                        }
                        ServerCommand::EndOfTransfer => {
                            tracing::trace!("Received command: {cmd:?}, stopping all threads...");
                            stop_flag.store(true, Ordering::Relaxed);
                            match join_all_threads(thread_handles) {
                                Ok(_) => {
                                    send_result(&mut socket, &ServerResult::Ok)?;
                                    return Ok(());
                                }
                                Err(th_errs) => {
                                    let err_res = ServerResult::err(th_errs.clone());
                                    send_result(&mut socket, &err_res)?;
                                    bail!(th_errs);
                                }
                            }
                        }
                        ServerCommand::IsDestinationValid(mode, dest) => {
                            let dest = PathBuf::from(dest);
                            tracing::info!("Checking validity of remote path: {dest:?}");
                            match validate_remote_path(&mode, &dest) {
                                Ok(remote_dest) => {
                                    send_result(&mut socket, &ServerResult::Ok)?;
                                    root_dest = Some(remote_dest);
                                }
                                Err(e) => {
                                    tracing::error!("Invalid remote path: {e}");
                                    send_result(
                                        &mut socket,
                                        &ServerResult::Err(e.to_string().into()),
                                    )?;
                                }
                            }
                        }
                        // For child threads
                        ServerCommand::Prealloc(_, _) => todo!(),
                        ServerCommand::ReceiveData(_, _, _) => todo!(),
                    }
                } else {
                    tracing::debug!("Main Client disconnected...");
                    break;
                }
            }
        }
        Err(e) => bail!(e),
    }
    Ok(())
}