Huakun Shen c8d5de2ce3
fix: file transfer ip bug
When sending to non-localhost host, will fail.
grpc server somehow cannot get client ip.
Modified grpc proto to send a src ip.
2025-01-10 20:55:23 -05:00

278 lines
8.7 KiB
Rust

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use grpc::file_transfer::file_transfer_server::FileTransfer;
use grpc::file_transfer::{FileNode, FileType, StartTransferRequest, StartTransferResponse};
use serde::{Deserialize, Serialize};
use tauri::{AppHandle, Emitter};
use tonic::{Request, Response, Status};
use uuid::Uuid;
#[derive(Debug)]
pub struct MyFileTransfer {
pub app_handle: AppHandle,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileTransferPayload {
pub port: String,
pub code: String,
pub total_bytes: u128,
pub total_files: usize,
pub root: FileNode,
pub ip: String,
pub ssl_cert: String,
}
#[tonic::async_trait]
impl FileTransfer for MyFileTransfer {
async fn start_transfer(
&self,
request: Request<StartTransferRequest>, // Accept request of type StartTransferRequest
) -> Result<Response<StartTransferResponse>, Status> {
let reply = StartTransferResponse {};
let payload = request.into_inner();
println!("start_transfer payload: {:?}", payload);
let src_ip = payload.ip;
println!("src_ip: {:?}", src_ip);
let root = if let Some(root) = payload.root {
root
} else {
return Err(Status::invalid_argument("root is required"));
};
let total_bytes = compute_total_size(&root);
let total_files = count_file_nodes(&root);
self.app_handle
.emit(
"file-transfer-request",
FileTransferPayload {
port: payload.port,
code: payload.code,
root,
total_bytes,
total_files,
ip: src_ip,
ssl_cert: payload.ssl_cert,
},
)
.map_err(|e| Status::internal(e.to_string()))?;
Ok(Response::new(reply))
}
}
pub fn construct_file_node(path: &Path) -> anyhow::Result<FileNode> {
if !path.exists() {
return Err(anyhow::anyhow!("path not exists"));
}
if !path.is_file() {
return Err(anyhow::anyhow!("path is not a file"));
}
Ok(FileNode {
filename: path
.file_name()
.expect("Fail to get file name")
.to_string_lossy()
.to_string(),
file_size: path.metadata()?.len(),
id: Uuid::new_v4().to_string(),
r#type: FileType::File as i32,
children: vec![],
})
}
pub fn construct_directory_node(path: &Path) -> anyhow::Result<FileNode> {
if !path.exists() {
return Err(anyhow::anyhow!("path not exists"));
}
if !path.is_dir() {
return Err(anyhow::anyhow!("path is not a directory"));
}
// construct children
let children = path
.read_dir()?
.filter_map(|entry| construct_node(&entry.ok()?.path()).ok())
.collect();
Ok(FileNode {
filename: path
.file_name()
.expect("Fail to get file name")
.to_string_lossy()
.to_string(),
file_size: path.metadata()?.len(),
id: Uuid::new_v4().to_string(),
r#type: FileType::Directory as i32,
children,
})
}
pub fn construct_node(path: &Path) -> anyhow::Result<FileNode> {
if path.is_file() {
construct_file_node(path)
} else {
construct_directory_node(path)
}
}
pub fn compute_file_node_total_size(node: &FileNode) -> u64 {
if node.r#type == FileType::File as i32 {
node.file_size
} else {
node.children
.iter()
.map(|child| compute_file_node_total_size(child))
.sum()
}
}
/// Flatten the file node tree to a vector of (id, path), path should be absolute path
/// `root_path` should be the directory containing
pub fn get_id_path_array(node: &FileNode, root_path: &Path) -> Vec<(String, PathBuf)> {
let mut vec: Vec<(String, PathBuf)> = Vec::new();
let dir_path = root_path.join(&node.filename);
if node.r#type == FileType::File as i32 {
vec.push((node.id.clone(), dir_path));
} else {
for child in node.children.iter() {
vec.extend(get_id_path_array(child, &dir_path));
}
}
vec
}
/// the returned root node will have empty filename because `files` passed in are not necessarily in the same directory
/// The root dir filename is left empty to avoid confusion, once the receiver receives the root node, it can fill in a custom directory name
pub fn build_file_node_and_id_path_map(
files: &Vec<PathBuf>,
) -> anyhow::Result<(HashMap<String, PathBuf>, FileNode)> {
let mut id_path_array: Vec<(String, PathBuf)> = Vec::new();
let mut children: Vec<FileNode> = vec![];
for file in files.iter() {
let node = construct_node(file).unwrap();
id_path_array.extend(get_id_path_array(&node, file.parent().unwrap()));
children.push(node);
}
let root = FileNode {
filename: "".to_string(),
file_size: 0,
id: Uuid::new_v4().to_string(),
r#type: FileType::Directory as i32,
children,
};
let map: HashMap<String, PathBuf> = id_path_array.into_iter().collect();
Ok((map, root))
}
pub fn count_file_nodes(node: &FileNode) -> usize {
if node.r#type == FileType::File as i32 {
1
} else {
node.children
.iter()
.map(|child| count_file_nodes(child))
.sum()
}
}
pub fn compute_total_size(node: &FileNode) -> u128 {
if node.r#type == FileType::File as i32 {
node.file_size as u128
} else {
node.children
.iter()
.map(|child| compute_total_size(child))
.sum()
}
}
mod test {
use super::*;
#[test]
fn test_construct_node() {
let manifest_path = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let src_path = PathBuf::from(manifest_path).join("src");
// manifest_path is pointing to grpc crate
let node = construct_node(src_path.as_path()).unwrap();
println!("{:#?}", node);
// println!("total size: {}", compute_total_size(&node));
}
#[test]
fn test_build_file_node_and_id_path_map() {
let manifest_path = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let src_path = PathBuf::from(manifest_path).join("src");
let (map, node) = build_file_node_and_id_path_map(&vec![src_path]).unwrap();
println!("{:#?}", node);
// check if all paths are absolute and exists
println!("{:#?}", map);
for (_, path) in map.iter() {
if !path.exists() {
panic!("path not exists: {}", path.to_string_lossy());
}
}
}
#[test]
fn test_get_id_path_array() {
let manifest_path = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let src_path = PathBuf::from(manifest_path)
.join("src")
.canonicalize()
.unwrap();
assert!(src_path.exists());
// let src_path = PathBuf::from("/Users/hk/Dev/kunkun/packages/grpc/src");
let node = construct_node(&src_path).unwrap();
println!("{:#?}", node);
let array = get_id_path_array(&node, src_path.parent().unwrap());
println!("{:#?}", array);
// check if all paths are absolute and exists
for (_, path) in array.iter() {
if !path.exists() {
panic!("path not exists: {}", path.to_string_lossy());
}
// assert!(path.is_absolute());
// assert!(path.exists());
}
}
#[test]
fn test_count_file_nodes() {
let manifest_path = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let src_path = PathBuf::from(manifest_path).join("src");
let node = construct_node(&src_path).unwrap();
// println!("{:#?}", node);
let count = count_file_nodes(&node);
// run "find . -type f | wc -l" to get a ground truth
if cfg!(target_os = "windows") {
return;
}
let stdout = String::from_utf8(
std::process::Command::new("find")
.arg(&src_path)
.arg("-type")
.arg("f")
.output()
.unwrap()
.stdout,
)
.unwrap();
let count2 = stdout.lines().count();
assert_eq!(count, count2);
}
#[test]
fn test_compute_total_size() {
let manifest_path = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let src_path = PathBuf::from(manifest_path).join("src");
println!("src_path: {}", src_path.to_string_lossy());
let node = construct_node(&src_path).unwrap();
let size = compute_total_size(&node);
println!("size: {}", size);
}
}