Skip to content

Commit e8cbd2b

Browse files
committed
✨ v0.3.0
1 parent 6af5bc5 commit e8cbd2b

File tree

5 files changed

+278
-103
lines changed

5 files changed

+278
-103
lines changed

Cargo.lock

Lines changed: 30 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
[package]
22
name = "ftoc"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
authors = ["Ryan <smbserv@qq.com>"]
55
edition = "2018"
66

77
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
88

99
[dependencies]
1010
clipboard-win="*"
11-
base64="*"
11+
ascii85="*"

README.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
# File Transfer over Clipboard (ftoc)
2-
是个通过剪贴板来传输文件的小工具,例如在VMWare Horizon的客户机和宿主机之间实现文件传输
2+
3+
是个通过剪贴板来传输文件的小工具,例如在 VMWare Horizon 的客户机和宿主机之间实现文件传输
34

45
**当前仅支持 Windows**
6+
57
## 使用
68

7-
接收方先开启ftoc:
9+
接收方先开启 ftoc:
10+
811
```
912
ftoc
1013
```
1114

1215
发送方:
16+
1317
```
1418
ftoc <file>
1519
```
20+
1621
就可以了。
1722

1823
## 发送参数设置
1924

20-
* --size `n`: 设置单个文件块大小,越大传输越快,支持大小后缀,例如 --size 1k / --size 2m 等,但正常剪贴板有大小限制,注意不要超过。否则文件传输会不完整
25+
- --size (-s) `n`: 设置单个文件块大小,越大传输越快,支持大小后缀,例如 --size 1k / --size 2m 等,但正常剪贴板有大小限制,注意不要超过。否则文件传输会不完整
2126

22-
* --skip `n`: 断点续传专用,从第`n`个块开始传输
27+
- --skip (-S) `n`: 断点续传专用,从第`n`个块开始传输
2328

24-
* --send-timeout `n`: 设置块之间的发送间隔,越小传输越快
29+
- --send-timeout (-st) `n`: 设置块之间的发送间隔,越小传输越快
2530

2631
## 接收参数设置
2732

28-
* --recv-timeout `n`: 设置接收(剪贴板检测)超时,正常要比`--send-timeout`
33+
- --recv-timeout (-rt) `n`: 设置接收(剪贴板检测)超时,正常要比`--send-timeout`

src/main.rs

Lines changed: 77 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use base64::{decode_config, encode_config};
2-
use clipboard_win::{get_clipboard_string, set_clipboard_string};
1+
mod protocol;
2+
use protocol::{Packet, PacketData, PacketStart, Protocol};
33
use std::{
44
env::Args,
55
fs::File,
@@ -53,16 +53,16 @@ fn parse_args(a: Args) -> Result<AppSetting, String> {
5353

5454
for x in a {
5555
match x.as_str() {
56-
"--size" => {
56+
"--size" | "-s" => {
5757
state = ArgState::Size;
5858
}
59-
"--skip" => {
59+
"--skip" | "-S" => {
6060
state = ArgState::Skip;
6161
}
62-
"--send-timeout" => {
62+
"--send-timeout" | "-st" => {
6363
state = ArgState::SendTimeout;
6464
}
65-
"--recv-timeout" => {
65+
"--recv-timeout" | "-rt" => {
6666
state = ArgState::RecvTimeout;
6767
}
6868
"--dry-run" => {
@@ -148,103 +148,91 @@ fn main() -> Result<(), String> {
148148
}
149149
})
150150
}
151-
enum RecvState {
152-
Wait,
153-
Start,
154-
End,
155-
}
151+
156152
fn sleep_ms(ms: u64) {
157153
std::thread::sleep(std::time::Duration::from_millis(ms))
158154
}
159155
fn recv_file(s: &AppRecvSetting) -> Result<(), io::Error> {
160-
let _ = set_clipboard_string("---")?;
161156
let mut writer: Option<BufWriter<File>> = None;
162-
let mut state = RecvState::Wait;
163157
let mut last_index = 0;
164158
let mut has_started = false;
165159
let mut time_wait_ms = 0;
160+
let mut timeout_ms = s.timeout;
166161
let mut total_len = 0u64;
167162
let mut recved_len = 0u64;
163+
let protocol = Protocol::new();
168164
println!("waiting for file");
169165
loop {
170-
match state {
171-
RecvState::Wait => {
172-
if let Ok(x) = get_clipboard_string() {
173-
if x.starts_with("ftoc-start") {
174-
if has_started {
175-
continue;
166+
if let Ok(x) = protocol.recv_decoded() {
167+
match x {
168+
Packet::Noop => {
169+
sleep_ms(1000);
170+
}
171+
Packet::Start(x) => {
172+
if has_started {
173+
continue;
174+
}
175+
has_started = true;
176+
177+
match File::create(Path::new(&x.name)) {
178+
Ok(f) => {
179+
println!("start recv file: {}", x.name);
180+
writer = Some(BufWriter::new(f));
181+
total_len = x.length;
182+
timeout_ms = (x.timeout - 150) as u64;
183+
println!("reset timeout from sender side to {} ms", timeout_ms);
176184
}
177-
has_started = true;
178-
let x: Vec<&str> = x.split(":").collect();
179-
match File::create(Path::new(x[1])) {
180-
Ok(f) => {
181-
println!("start recv file: {}", x[1]);
182-
writer = Some(BufWriter::new(f));
183-
state = RecvState::Start;
184-
total_len = x[2].parse().expect("can't read total length of file");
185-
}
186-
Err(e) => {
187-
dbg!(e);
188-
break;
189-
}
185+
Err(e) => {
186+
dbg!(e);
187+
break;
190188
}
191-
} else {
192-
sleep_ms(1000);
193189
}
194-
} else {
195-
sleep_ms(100);
196-
continue;
197190
}
198-
}
199-
RecvState::Start => {
200-
if let Ok(x) = get_clipboard_string() {
201-
if x.starts_with("ftoc-end") {
202-
state = RecvState::End;
203-
} else if x.starts_with("ftoc-start") {
204-
sleep_ms(100);
191+
Packet::Data(x) => {
192+
if !has_started {
205193
continue;
206-
} else if x.starts_with("ftoc") {
207-
let x: Vec<&str> = x.split(":").collect();
208-
let idx: i32 = x[1].parse().expect("invalid index");
194+
}
195+
let idx = x.index;
209196

210-
if last_index == idx - 1 {
211-
if let Ok(v) = decode_config(x[2], base64::URL_SAFE_NO_PAD) {
212-
if let Some(x) = &mut writer {
213-
time_wait_ms = 0;
214-
last_index = idx;
215-
recved_len += v.len() as u64;
197+
if last_index == idx - 1 {
198+
if let Some(w) = &mut writer {
199+
time_wait_ms = 0;
200+
last_index = idx;
201+
recved_len += x.data.len() as u64;
216202

217-
let percentage: f32 = (recved_len as f32) / (total_len as f32);
218-
println!("recv block {} ({:.2}%)", idx, percentage * 100f32);
219-
let _ = x.write(v.as_ref());
220-
} else {
221-
println!("warn: block {} write failed", idx)
222-
}
223-
} else {
224-
println!("warn: block {} decode failed", idx)
203+
let percentage: f32 = (recved_len as f32) / (total_len as f32);
204+
println!("recv block {} ({:.2}%)", idx, percentage * 100f32);
205+
if let Err(_) = w.write(x.data.as_ref()) {
206+
println!("warning: can't write to destination file");
225207
}
226208
} else {
227-
// wait for missed block or retransmission
228-
time_wait_ms += s.timeout;
229-
if time_wait_ms > 10000 {
230-
println!("warning: recv staled, last_index={}", last_index);
231-
time_wait_ms = 0;
232-
}
209+
println!("warning: block {} write failed", idx)
210+
}
211+
} else {
212+
// wait for missed block or retransmission
213+
time_wait_ms += s.timeout;
214+
if time_wait_ms > 10000 {
215+
println!("warning: recv staled, last_index={}", last_index);
216+
time_wait_ms = 0;
233217
}
234218
}
235-
sleep_ms(s.timeout);
236-
} else {
237-
sleep_ms(100);
238-
continue;
219+
sleep_ms(timeout_ms);
239220
}
240-
}
241-
RecvState::End => {
242-
if let Some(x) = &mut writer {
243-
let _ = x.flush();
244-
println!("file saved")
221+
Packet::End => {
222+
if recved_len != total_len {
223+
println!("[warn] recved end but data is incomplete");
224+
continue;
225+
}
226+
if let Some(x) = &mut writer {
227+
let _ = x.flush();
228+
println!("file saved");
229+
break;
230+
}
245231
}
246-
break;
247232
}
233+
} else {
234+
sleep_ms(100);
235+
continue;
248236
}
249237
}
250238
Ok(())
@@ -253,6 +241,7 @@ fn send_file(s: &AppSendSetting) -> Result<(), io::Error> {
253241
let p = Path::new(&s.file_path);
254242
let file = File::open(p)?;
255243
let mut reader = BufReader::new(file);
244+
let protocol = Protocol::new();
256245

257246
let mut eof = false;
258247

@@ -268,10 +257,12 @@ fn send_file(s: &AppSendSetting) -> Result<(), io::Error> {
268257
reader.seek(io::SeekFrom::End(0))?;
269258
let len = reader.stream_position()?;
270259
reader.seek(io::SeekFrom::Start(0))?;
271-
let x = format!("ftoc-start:{}:{}", filename, len);
272260
println!("sending file : {} with {} bytes long", filename, len);
273-
274-
let _ = set_clipboard_string(x.as_str());
261+
let _ = protocol.send_encoded(Packet::Start(PacketStart {
262+
timeout: s.timeout as u32,
263+
name: filename.to_owned(),
264+
length: len,
265+
}));
275266

276267
sleep_ms(2000);
277268

@@ -288,14 +279,16 @@ fn send_file(s: &AppSendSetting) -> Result<(), io::Error> {
288279
eof = true;
289280
} else {
290281
index += 1;
291-
let s = encode_config(&v[0..s], base64::URL_SAFE_NO_PAD);
292-
let text = format!("ftoc:{}:{}", index, s);
293-
let _ = set_clipboard_string(text.as_str());
282+
let packet = Packet::Data(PacketData {
283+
index: index,
284+
data: v[0..s].to_vec(),
285+
});
286+
let _ = protocol.send_encoded(packet);
294287
println!("sending block {}", index);
295288
}
296289
});
297290
if eof {
298-
let _ = set_clipboard_string("ftoc-end")?;
291+
let _ = protocol.send_encoded(Packet::End);
299292
println!("file sent");
300293
break;
301294
}

0 commit comments

Comments
 (0)