Skip to content

Commit f0ea498

Browse files
authored
Merge pull request #56 from spieglt/mirroring
Folder sending
2 parents b3901d2 + f7e54f2 commit f0ea498

File tree

11 files changed

+123
-27
lines changed

11 files changed

+123
-27
lines changed

Cargo.lock

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

Flying Carpet/src-tauri/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
[package]
22
name = "flying-carpet"
3-
version = "7.1.0"
3+
version = "8.0.0"
44
description = "Encrypted file transfer over ad hoc WiFi between Android, iOS, Linux, macOS, and Windows"
55
authors = ["Theron Spiegl"]
6-
license = "BSD-3-clause"
6+
license = "GPL-3.0-only"
77
repository = "https://github.com/spieglt/flyingcarpet"
88
edition = "2021"
99
rust-version = "1.57"

Flying Carpet/src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
},
99
"package": {
1010
"productName": "FlyingCarpet",
11-
"version": "7.1.0"
11+
"version": "8.0.0"
1212
},
1313
"tauri": {
1414
"allowlist": {

Flying Carpet/src/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<div class="horizontalContainer" style="flex-grow: 0;">
1616
<div>
1717
<h1 style="margin-bottom: 0px; padding-bottom: 0px;">Flying Carpet</h1>
18-
<small style="margin-top: 0px; padding-top: 0px;">Version 7</small>
18+
<small style="margin-top: 0px; padding-top: 0px;">Version 8</small>
1919
<small style="margin-top: 0px; padding-top: 0px; color: blue; float: right; cursor: pointer;" id="aboutButton">
2020
About
2121
</small>

Flying Carpet/src/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ window.modeChange = modeChange;
383383
window.peerChange = peerChange;
384384

385385
const aboutMessage = `https://flyingcarpet.spiegl.dev
386-
Version: 7.1
386+
Version: 8.0
387387
388388
Copyright (c) 2023, Theron Spiegl
389389
All rights reserved.

README.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
## Version 7 supports Android and iOS!
1+
## Version 8 adds the option to send folders from Android and iOS
22

33
Android version is available [here](https://play.google.com/store/apps/details?id=dev.spiegl.flyingcarpet), or if you prefer to sideload, `android_FlyingCarpet.apk` is available on the [releases](https://github.com/spieglt/FlyingCarpet/releases) page.
44

55
iOS version [here](https://apps.apple.com/us/app/flying-carpet-file-transfer/id1637377410) or search the App Store for "Flying Carpet File Transfer".
66

7-
Linux, macOS, and Windows versions available on the [releases](https://github.com/spieglt/FlyingCarpet/releases) page. Installers and standalone executable versions available.
7+
Linux, macOS, and Windows versions are available on the [releases](https://github.com/spieglt/FlyingCarpet/releases) page. Installers and standalone executable versions available.
88

99
# Flying Carpet
1010

@@ -44,10 +44,6 @@ Don't have a flash drive? Don't have access to a wireless network? Need to move
4444

4545
+ macOS sometimes switches back to a wireless network with internet connectivity during particularly long transfers. I haven't been able to replicate this reliably and am not sure if a fix is possible. Please file an issue if you experience this.
4646

47-
+ Flying Carpet should rejoin you to your previous wireless network after a completed or canceled transfer. This may not happen if the program freezes, crashes, or if the windows is closed during operation.
48-
49-
+ Flying Carpet no longer preserves directory structure when sending a folder. This became too complicated once the mobile versions entered the picture. If this feature is desired, please email me at [email protected] or file an issue. Or, as a workaround, just zip the directory before sending and unzip on the other device.
50-
5147
## Planned Features
5248

5349
+ Add Flying Carpet shortcut to iOS Share menu.

core/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
[package]
22
name = "flying-carpet-core"
3-
version = "0.1.0"
3+
version = "8.0.0"
44
edition = "2021"
5+
license = "GPL-3.0-only"
56

67
[dependencies]
78
aes-gcm = "0.10"

core/src/lib.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use tokio::{
2020
};
2121

2222
const CHUNKSIZE: usize = 1_000_000; // 1 MB
23-
const MAJOR_VERSION: u64 = 7;
23+
const MAJOR_VERSION: u64 = 8;
2424

2525
pub trait UI: Clone + Send + 'static {
2626
fn output(&self, msg: &str);
@@ -177,6 +177,17 @@ pub async fn start_transfer<T: UI>(
177177
return Some(stream);
178178
}
179179
}
180+
// find folder common to all files
181+
let mut common_folder = files[0].parent().expect("file has no parent");
182+
if files.len() > 1 {
183+
for file in &files[1..] {
184+
let current = file.parent().expect("file has no parent");
185+
if current.components().collect::<Vec<_>>().len() < common_folder.components().collect::<Vec<_>>().len() {
186+
common_folder = current;
187+
}
188+
}
189+
}
190+
ui.output(&format!("common folder: {}", common_folder.display()));
180191
// send files
181192
for (i, file) in files.iter().enumerate() {
182193
let file_name = file
@@ -190,7 +201,7 @@ pub async fn start_transfer<T: UI>(
190201
files.len(),
191202
file_name
192203
));
193-
match sending::send_file(file, &key, &mut stream, ui).await {
204+
match sending::send_file(file, common_folder, &key, &mut stream, ui).await {
194205
Ok(_) => (),
195206
Err(e) => {
196207
ui.output(&format!("Error sending file: {}", e));
@@ -372,11 +383,10 @@ async fn confirm_version(
372383
}
373384

374385
// TODO:
386+
// folder send check box? or just rely on drag and drop? if so, disable it, store/restore on refresh.
375387
// fix tests
376-
// add license to file and cargo.toml
377388
// code signing for windows?
378389
// fix bug where multiple start/cancel clicks stack while waiting for transfer to cancel, at least on linux
379-
// wifi networks not being deleted on linux (when hosting)?
380390
// update screenshots?
381391
// show qr code after refresh
382392
// test pulling wifi card, quitting program, etc.

core/src/receiving.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,35 @@ pub async fn receive_file<T: UI>(
2929

3030
// receive file details
3131
let (filename, file_size) = receive_file_details(stream).await?;
32+
// TODO: convert forward slashes to backslashes before receiving if mirroring?
3233
ui.output(&format!("Filename: {}", filename));
3334
ui.output(&format!(
3435
"File size: {}",
3536
utils::make_size_readable(file_size)
3637
));
3738
let mut bytes_left = file_size;
3839

39-
// check if file being received already exists. if so, find new filename.
40+
// see if we already have the file being sent
4041
let mut full_path = folder.clone();
4142
full_path.push(&filename);
43+
let need_transfer = check_for_file(&full_path, file_size, stream).await?;
44+
if !need_transfer {
45+
ui.output("Recipient already has this file, skipping.");
46+
return Ok(())
47+
}
48+
49+
// make parent directories if necessary
50+
utils::make_parent_directories(&full_path)?;
51+
52+
// check if file being received already exists. if so, find new filename.
4253
let mut i = 1;
4354
while full_path.is_file() {
44-
let new_name = format!("({}) ", i) + &filename;
55+
let file_name = full_path
56+
.file_name()
57+
.expect("could not get filename from full path")
58+
.to_str()
59+
.expect("could not convert filename to str");
60+
let new_name = format!("({}) ", i) + file_name;
4561
full_path.pop();
4662
full_path.push(new_name);
4763
i += 1;
@@ -143,3 +159,33 @@ async fn receive_file_details(stream: &mut TcpStream) -> std::io::Result<(String
143159
let file_size = stream.read_u64().await?;
144160
Ok((filename, file_size))
145161
}
162+
163+
// returns Ok(true) if we need to perform the transfer
164+
async fn check_for_file(filename: &Path, size: u64, stream: &mut TcpStream) -> Result<bool, Box<dyn Error>> {
165+
// check if file by this name and size exists
166+
if filename.is_file() {
167+
// check size
168+
let metadata = fs::metadata(filename)?;
169+
let local_size = metadata.len();
170+
if size == local_size {
171+
stream.write_u64(1).await?;
172+
let mut hashes_match = true;
173+
let local_hash = utils::hash_file(filename)?;
174+
let mut peer_hash = vec![0; 32];
175+
stream.read_exact(&mut peer_hash).await?;
176+
for i in 0..local_hash.len() {
177+
if local_hash[i] != peer_hash[i] {
178+
hashes_match = false;
179+
}
180+
}
181+
stream.write_u64(if hashes_match { 1 } else { 0 }).await?;
182+
Ok(!hashes_match)
183+
} else {
184+
stream.write_u64(0).await?;
185+
Ok(true)
186+
}
187+
} else {
188+
stream.write_u64(0).await?;
189+
Ok(true)
190+
}
191+
}

core/src/sending.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use tokio::{
1414

1515
pub async fn send_file<T: UI>(
1616
file: &Path,
17+
prefix: &Path,
1718
key: &[u8],
1819
stream: &mut TcpStream,
1920
ui: &T,
@@ -27,16 +28,27 @@ pub async fn send_file<T: UI>(
2728
ui.output(&format!("File size: {}", utils::make_size_readable(size)));
2829

2930
// send file details
30-
let filename = file
31-
.file_name()
32-
.expect("could not extract filename from path");
31+
let mut filename = file
32+
.strip_prefix(prefix)?
33+
.to_string_lossy()
34+
.to_string();
35+
if cfg!(windows) {
36+
filename = filename.replace("\\", "/");
37+
}
3338
send_file_details(
34-
filename.to_str().expect("couldn't convert filename to str"),
39+
&filename,
3540
size,
3641
stream,
3742
)
3843
.await?;
3944

45+
// check to see if receiving end already has the file
46+
let need_transfer = check_for_file(&file, stream).await?;
47+
if !need_transfer {
48+
ui.output("Recipient already has this file, skipping.");
49+
return Ok(())
50+
}
51+
4052
// show progress bar
4153
ui.show_progress_bar();
4254

@@ -122,6 +134,21 @@ async fn send_file_details(
122134
Ok(())
123135
}
124136

137+
// returns Ok(true) if we need to perform the transfer
138+
async fn check_for_file(filename: &Path, stream: &mut TcpStream) -> Result<bool, Box<dyn Error>> {
139+
let has_file = stream.read_u64().await?;
140+
if has_file == 1 {
141+
let hash = utils::hash_file(filename)?;
142+
stream.write(&hash).await?;
143+
let hashes_match = stream.read_u64().await?;
144+
Ok(hashes_match != 1) // if hashes match, return false because we don't need transfer
145+
} else {
146+
Ok(true)
147+
}
148+
}
149+
150+
151+
125152
/*
126153
mod tests {
127154
use tokio::io::AsyncReadExt;

core/src/utils.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use rand::Rng;
2+
use sha2::{Sha256, Digest};
23
use std::{
4+
error::Error,
35
ffi::{c_char, CString},
46
fs,
5-
path::PathBuf,
7+
io,
8+
path::{PathBuf, Path},
69
process,
710
};
811

@@ -36,6 +39,20 @@ pub fn expand_dir(dir: PathBuf) -> (Vec<String>, Vec<PathBuf>) {
3639
(files_found, dirs_to_search)
3740
}
3841

42+
pub fn make_parent_directories(full_path: &Path) -> io::Result<()> {
43+
if let Some(dirs) = full_path.parent() {
44+
fs::create_dir_all(dirs)?;
45+
}
46+
Ok(())
47+
}
48+
49+
pub fn hash_file(filename: &Path) -> Result<Vec<u8>, Box<dyn Error>> {
50+
let mut file = fs::File::open(filename)?;
51+
let mut hasher = Sha256::new();
52+
io::copy(&mut file, &mut hasher)?;
53+
Ok(hasher.finalize().to_vec())
54+
}
55+
3956
pub fn generate_password() -> String {
4057
let mut rng = rand::thread_rng();
4158
let chars: Vec<char> = "23456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
@@ -77,8 +94,7 @@ pub fn format_time(seconds: f64) -> String {
7794
}
7895

7996
pub fn is_compatible(peer_version: u64) -> bool {
80-
// for version 7, we will only be compatible with version 7
81-
// a future version 8 might be compatible with version 7, and would need to make that decision here.
97+
// version 8 is not compatible with previous versions
8298
peer_version == MAJOR_VERSION
8399
}
84100

0 commit comments

Comments
 (0)