first commit

This commit is contained in:
liyp
2026-08-25 22:13:25 -04:00
commit 846ed07922
9 changed files with 236 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
# 编译输出目录
/target/
# 依赖锁文件
Cargo.lock
# 本地环境配置文件
.env
# 编辑器和 IDE 产生的文件
.idea/
.vscode/
*.iml
*.ipr
*.iws
# 日志文件
*.log
# 临时文件
temp/
tmp/
# 覆盖配置文件
Cargo.toml.orig
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "VoiletTftp"
version = "1.0.0"
edition = "2024"
[dependencies]
tokio = { version = "1.47.1", features = ["full"] }
async-tftp = "0.3.6"
slint = "1.12.1"
rfd = "0.15.4"
[build-dependencies]
slint-build = "1.12"
+3
View File
@@ -0,0 +1,3 @@
fn main() {
slint_build::compile("components/Window.slint").unwrap();
}
+87
View File
@@ -0,0 +1,87 @@
import { Button, VerticalBox } from "std-widgets.slint";
export global Globals {
//启动按钮状态
in-out property <bool> StartEnable: false;
//停止按钮状态
in-out property <bool> StopEnable: false;
}
export component MainWindow inherits Window {
title: "VoileTftpTools";
icon: @image-url("../tftp.png");
height: 180px;
width: 320px;
default-font-family: "Segoe UI";
default-font-size: 16px;
default-font-weight: 700;
in-out property <bool> StartEnable <=> Globals.StartEnable;
in-out property <bool> StopEnable <=> Globals.StopEnable;
//选择路径回调
callback BrowserFolder() -> string;
property <string> SelectFolder: "";
//启动回调
callback StartTftp();
//停止回调
callback StopTftp();
//选择路径按钮
Rectangle {
height: 35px;
width: 95px;
x: 10px;
y: 10px;
Button {
height: parent.height;
width: parent.width;
text: "选择路径";
clicked => {
SelectFolder = BrowserFolder();
}
}
}
Rectangle {
height: 35px;
width: 95px;
x: 110px;
y: 10px;
Button {
height: parent.height;
width: parent.width;
text: "启动";
enabled: StartEnable;
clicked => {
StartTftp();
}
}
}
Rectangle {
height: 35px;
width: 95px;
x: 210px;
y: 10px;
Button {
height: parent.height;
width: parent.width;
text: "停止";
enabled: StopEnable;
clicked => {
StopTftp();
}
}
}
Text {
x: 13px;
y: 55px;
text: "路径:";
}
Text {
x: 60px;
y: 55px;
width: 200px;
wrap: word-wrap; // 自动换行关键属性
text: SelectFolder;
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

+1
View File
@@ -0,0 +1 @@
0001 ICON "logo.ico"
BIN
View File
Binary file not shown.
+107
View File
@@ -0,0 +1,107 @@
#![windows_subsystem = "windows"]
use async_tftp::server::TftpServerBuilder;
use async_tftp::Result;
use rfd::FileDialog;
use slint::SharedString;
use std::path::PathBuf;
use tokio::task::{AbortHandle, JoinHandle};
use std::sync::{Arc, Mutex};
slint::include_modules!();
struct ServerState {
abort_handle: Option<AbortHandle>,
}
async fn start_tftp_server(dir: PathBuf) -> Result<(JoinHandle<()>, AbortHandle)> {
let task = tokio::spawn(async move {
println!("Starting TFTP server at: {:?}", dir);
let tftpd = TftpServerBuilder::with_dir_rw(&dir)
.expect("Failed to create server")
.build()
.await
.expect("Failed to bind socket");
if let Err(e) = tftpd.serve().await {
eprintln!("Server error: {}", e);
}
});
let abort_handle = task.abort_handle();
Ok((task, abort_handle))
}
#[tokio::main]
async fn main() {
let ui = MainWindow::new().unwrap();
let state = Arc::new(Mutex::new(ServerState { abort_handle: None }));
let folder_path = Arc::new(Mutex::new(String::new()));
let path_clone = folder_path.clone();
let ui_weak = ui.as_weak();
//选择路径按钮的回调函数
ui.on_BrowserFolder(move || {
let result = FileDialog::new()
.set_directory("/")
.pick_folder();
let mut path = result.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default();
if path == "" {
println!("TFTP Dir Empty !");
if let Some(ui) = ui_weak.upgrade() {
ui.set_StartEnable(false);
}
path = String::from("");
}
else {
if let Some(ui) = ui_weak.upgrade() {
ui.set_StartEnable(true);
}
}
*path_clone.lock().unwrap() = path.clone(); // 存储到变量
SharedString::from(path)
});
//启动按钮的回调函数
let state_clone = state.clone();
let path_clone = Arc::clone(&folder_path);
let ui_weak = ui.as_weak();
ui.on_StartTftp(move || {
let dir = PathBuf::from(&*path_clone.lock().unwrap());
if dir.iter().count() == 0 {
println!("TFTP Dir Empty !");
if let Some(ui) = ui_weak.upgrade() {
ui.set_StartEnable(true);
ui.set_StopEnable(false);
}
}
else {
if let Some(ui) = ui_weak.upgrade() {
ui.set_StartEnable(false);
ui.set_StopEnable(true);
}
println!("{:?}",dir);
let state = state_clone.clone();
tokio::spawn(async move {
match start_tftp_server(dir).await {
Ok((_handle, abort_handle)) => {
println!("Server started");
// 存储 abort_handle 以便停止
state.lock().unwrap().abort_handle = Some(abort_handle);
}
Err(e) => eprintln!("Error: {}", e),
}
});
}
});
//停止按钮的回调函数
let ui_weak = ui.as_weak();
let state_clone = state.clone();
ui.on_StopTftp(move || {
if let Some(ui) = ui_weak.upgrade() {
ui.set_StartEnable(true);
ui.set_StopEnable(false);
}
if let Some(handle) = state_clone.lock().unwrap().abort_handle.take() {
handle.abort();
println!("Server stopped");
}
});
ui.run().unwrap();
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB