mirror of
https://github.com/localsend/localsend.git
synced 2026-08-07 07:14:52 +00:00
156 lines
4.9 KiB
Rust
156 lines
4.9 KiB
Rust
//! The sending side: picking a target device and files, and tracking the
|
|
//! transfer driven by the [`crate::send_task`].
|
|
|
|
use super::App;
|
|
use crate::devices::Device;
|
|
use crate::picker::{Picker, PickerOutcome};
|
|
use crate::send_task;
|
|
use crate::ui::Category;
|
|
use crate::util::SpeedMeter;
|
|
use crossterm::event::KeyEvent;
|
|
use localsend::model::transfer::FileDto;
|
|
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::AtomicU64;
|
|
use uuid::Uuid;
|
|
|
|
/// An outgoing transfer driven by the send task.
|
|
pub(super) struct SendState {
|
|
pub(super) session_id: Option<String>,
|
|
pub(super) alias: String,
|
|
pub(super) host: String,
|
|
pub(super) total_bytes: u64,
|
|
pub(super) sent: Arc<AtomicU64>,
|
|
pub(super) cancel: send_task::SendCancel,
|
|
pub(super) speed: SpeedMeter,
|
|
}
|
|
|
|
impl App {
|
|
pub(super) fn start_picking(&mut self, slot: u8) {
|
|
let Some(device) = self.registry.by_slot(slot).cloned() else {
|
|
self.ui
|
|
.log(Category::Send, &format!("No device on [{slot}]"));
|
|
return;
|
|
};
|
|
if !self.preselected.is_empty() {
|
|
self.start_send(&device.fingerprint, self.preselected.clone());
|
|
return;
|
|
}
|
|
self.open_picker(device);
|
|
}
|
|
|
|
pub(super) fn open_picker(&mut self, device: Device) {
|
|
if self.send.is_some() {
|
|
self.ui.log(Category::Send, "A send is already in progress");
|
|
return;
|
|
}
|
|
match Picker::open(device.fingerprint, device.alias.clone()) {
|
|
Ok(picker) => {
|
|
self.ui.suspend();
|
|
self.picker = Some(picker);
|
|
}
|
|
Err(err) => {
|
|
self.ui.log(
|
|
Category::Send,
|
|
&format!("{}: could not open the file picker: {err}", device.alias),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_picker_key(&mut self, key: KeyEvent) {
|
|
let Some(picker) = &mut self.picker else {
|
|
return;
|
|
};
|
|
match picker.handle_key(key) {
|
|
PickerOutcome::Open => {}
|
|
PickerOutcome::Picked(files) => {
|
|
let picker = self.picker.take().unwrap();
|
|
let fingerprint = picker.fingerprint.clone();
|
|
picker.close();
|
|
self.ui.resume();
|
|
self.start_send(&fingerprint, files);
|
|
}
|
|
PickerOutcome::Cancelled => {
|
|
let picker = self.picker.take().unwrap();
|
|
picker.close();
|
|
self.ui.resume();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn start_send(&mut self, fingerprint: &str, picked: Vec<PathBuf>) {
|
|
if self.send.is_some() {
|
|
self.ui.log(Category::Send, "A send is already in progress");
|
|
return;
|
|
}
|
|
let Some(device) = self.registry.by_fingerprint(fingerprint).cloned() else {
|
|
return;
|
|
};
|
|
|
|
let mut files = HashMap::new();
|
|
let mut paths = HashMap::new();
|
|
let mut total_bytes = 0u64;
|
|
for path in picked {
|
|
let metadata = match std::fs::metadata(&path) {
|
|
Ok(metadata) if metadata.is_file() => metadata,
|
|
_ => {
|
|
self.ui.log(
|
|
Category::Send,
|
|
&format!("Skipping unreadable file: {}", path.display()),
|
|
);
|
|
continue;
|
|
}
|
|
};
|
|
let id = Uuid::new_v4().to_string();
|
|
let file_name = path
|
|
.file_name()
|
|
.map(|name| name.to_string_lossy().into_owned())
|
|
.unwrap_or_else(|| "unnamed".to_string());
|
|
total_bytes += metadata.len();
|
|
files.insert(
|
|
id.clone(),
|
|
FileDto {
|
|
id: id.clone(),
|
|
file_name,
|
|
size: metadata.len(),
|
|
file_type: mime_guess::from_path(&path)
|
|
.first_or_octet_stream()
|
|
.to_string(),
|
|
sha256: None,
|
|
preview: None,
|
|
metadata: None,
|
|
},
|
|
);
|
|
paths.insert(id, path);
|
|
}
|
|
if files.is_empty() {
|
|
self.ui.log(Category::Send, "No files selected");
|
|
return;
|
|
}
|
|
|
|
let progress = Arc::new(AtomicU64::new(0));
|
|
let cancel = send_task::SendCancel::new();
|
|
self.send = Some(SendState {
|
|
session_id: None,
|
|
alias: device.alias.clone(),
|
|
host: device.host.clone(),
|
|
total_bytes,
|
|
sent: progress.clone(),
|
|
cancel: cancel.clone(),
|
|
speed: SpeedMeter::new(),
|
|
});
|
|
|
|
tokio::spawn(send_task::run_send(
|
|
self.storage.identity.clone(),
|
|
device,
|
|
files,
|
|
paths,
|
|
progress,
|
|
cancel,
|
|
self.events_tx.clone(),
|
|
));
|
|
}
|
|
}
|