fix(tui): removed a ton of use_username_dirs references
feat(tui): username is now appended to download_dir before passed as parameter fix(tui): username is sanitized before appended to path
This commit is contained in:
@@ -1148,13 +1148,9 @@ fn main() {
|
|||||||
let completed = completed.clone();
|
let completed = completed.clone();
|
||||||
workers.push(std::thread::spawn(move || {
|
workers.push(std::thread::spawn(move || {
|
||||||
Metrics::bump(&metrics.downloads_issued);
|
Metrics::bump(&metrics.downloads_issued);
|
||||||
let Ok((_download, status_rx)) = client.download(
|
let Ok((_download, status_rx)) =
|
||||||
filename.clone(),
|
client.download(filename.clone(), peer.clone(), size, dir)
|
||||||
peer.clone(),
|
else {
|
||||||
size,
|
|
||||||
dir,
|
|
||||||
false,
|
|
||||||
) else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let deadline = run_deadline;
|
let deadline = run_deadline;
|
||||||
|
|||||||
@@ -99,14 +99,12 @@ impl Client {
|
|||||||
username: String,
|
username: String,
|
||||||
size: u64,
|
size: u64,
|
||||||
download_directory: String,
|
download_directory: String,
|
||||||
use_username_dirs: bool,
|
|
||||||
) -> Result<(Download, Receiver<DownloadStatus>)> {
|
) -> Result<(Download, Receiver<DownloadStatus>)> {
|
||||||
self.download_with_metadata(
|
self.download_with_metadata(
|
||||||
filename,
|
filename,
|
||||||
username,
|
username,
|
||||||
size,
|
size,
|
||||||
download_directory,
|
download_directory,
|
||||||
use_username_dirs,
|
|
||||||
DownloadMetadata::default(),
|
DownloadMetadata::default(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -117,7 +115,6 @@ impl Client {
|
|||||||
username: String,
|
username: String,
|
||||||
size: u64,
|
size: u64,
|
||||||
download_directory: String,
|
download_directory: String,
|
||||||
use_username_dirs: bool,
|
|
||||||
metadata: DownloadMetadata,
|
metadata: DownloadMetadata,
|
||||||
) -> Result<(Download, Receiver<DownloadStatus>)> {
|
) -> Result<(Download, Receiver<DownloadStatus>)> {
|
||||||
info!("[client] Downloading {} from {}", filename, username);
|
info!("[client] Downloading {} from {}", filename, username);
|
||||||
@@ -135,7 +132,6 @@ impl Client {
|
|||||||
token,
|
token,
|
||||||
size,
|
size,
|
||||||
download_directory,
|
download_directory,
|
||||||
use_username_dirs,
|
|
||||||
status: DownloadStatus::Queued,
|
status: DownloadStatus::Queued,
|
||||||
sender: download_sender,
|
sender: download_sender,
|
||||||
queue_position: None,
|
queue_position: None,
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ fn download(
|
|||||||
token,
|
token,
|
||||||
size: 100,
|
size: 100,
|
||||||
download_directory: "test".to_string(),
|
download_directory: "test".to_string(),
|
||||||
use_username_dirs: false,
|
|
||||||
status,
|
status,
|
||||||
sender,
|
sender,
|
||||||
queue_position: None,
|
queue_position: None,
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ fn download_without_a_connection_resolves_failed() {
|
|||||||
"peer".to_string(),
|
"peer".to_string(),
|
||||||
100,
|
100,
|
||||||
"test".to_string(),
|
"test".to_string(),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.expect("download() should return a handle");
|
.expect("download() should return a handle");
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|||||||
@@ -242,7 +242,6 @@ mod tests {
|
|||||||
token,
|
token,
|
||||||
size: 100,
|
size: 100,
|
||||||
download_directory: "test".to_string(),
|
download_directory: "test".to_string(),
|
||||||
use_username_dirs: false,
|
|
||||||
status,
|
status,
|
||||||
sender: mpsc::channel().0,
|
sender: mpsc::channel().0,
|
||||||
queue_position: None,
|
queue_position: None,
|
||||||
|
|||||||
@@ -110,10 +110,6 @@ fn resolve_download_path(download: &Download) -> Result<String, DownloadError> {
|
|||||||
.to_path_buf();
|
.to_path_buf();
|
||||||
}
|
}
|
||||||
|
|
||||||
if download.use_username_dirs {
|
|
||||||
expanded_path = expanded_path.join(download.username.as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
let final_path =
|
let final_path =
|
||||||
expanded_path.join(extract_filename_from_path(&download.filename));
|
expanded_path.join(extract_filename_from_path(&download.filename));
|
||||||
|
|
||||||
@@ -639,7 +635,6 @@ mod tests {
|
|||||||
token: 1,
|
token: 1,
|
||||||
size,
|
size,
|
||||||
download_directory: dir.display().to_string(),
|
download_directory: dir.display().to_string(),
|
||||||
use_username_dirs: false,
|
|
||||||
status: DownloadStatus::Queued,
|
status: DownloadStatus::Queued,
|
||||||
sender: mpsc::channel().0,
|
sender: mpsc::channel().0,
|
||||||
queue_position: None,
|
queue_position: None,
|
||||||
|
|||||||
@@ -479,7 +479,6 @@ mod tests {
|
|||||||
token,
|
token,
|
||||||
size: 100,
|
size: 100,
|
||||||
download_directory: std::env::temp_dir().display().to_string(),
|
download_directory: std::env::temp_dir().display().to_string(),
|
||||||
use_username_dirs: false,
|
|
||||||
status: DownloadStatus::Queued,
|
status: DownloadStatus::Queued,
|
||||||
sender: mpsc::channel().0,
|
sender: mpsc::channel().0,
|
||||||
queue_position: None,
|
queue_position: None,
|
||||||
|
|||||||
@@ -185,7 +185,6 @@ pub struct Download {
|
|||||||
pub token: u32,
|
pub token: u32,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub download_directory: String,
|
pub download_directory: String,
|
||||||
pub use_username_dirs: bool,
|
|
||||||
pub status: DownloadStatus,
|
pub status: DownloadStatus,
|
||||||
pub sender: Sender<DownloadStatus>,
|
pub sender: Sender<DownloadStatus>,
|
||||||
pub queue_position: Option<u32>,
|
pub queue_position: Option<u32>,
|
||||||
|
|||||||
@@ -240,9 +240,6 @@
|
|||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"use_username_dirs": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"username": {
|
"username": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -253,7 +250,6 @@
|
|||||||
"token",
|
"token",
|
||||||
"size",
|
"size",
|
||||||
"download_directory",
|
"download_directory",
|
||||||
"use_username_dirs",
|
|
||||||
"status"
|
"status"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|||||||
+1
-13
@@ -102,7 +102,6 @@ pub trait SessionApi: Send + Sync {
|
|||||||
username: String,
|
username: String,
|
||||||
size: u64,
|
size: u64,
|
||||||
download_directory: String,
|
download_directory: String,
|
||||||
use_username_dirs: bool,
|
|
||||||
) -> Result<(Download, Receiver<DownloadStatus>)>;
|
) -> Result<(Download, Receiver<DownloadStatus>)>;
|
||||||
fn download_with_metadata(
|
fn download_with_metadata(
|
||||||
&self,
|
&self,
|
||||||
@@ -110,7 +109,6 @@ pub trait SessionApi: Send + Sync {
|
|||||||
username: String,
|
username: String,
|
||||||
size: u64,
|
size: u64,
|
||||||
download_directory: String,
|
download_directory: String,
|
||||||
use_username_dirs: bool,
|
|
||||||
metadata: DownloadMetadata,
|
metadata: DownloadMetadata,
|
||||||
) -> Result<(Download, Receiver<DownloadStatus>)>;
|
) -> Result<(Download, Receiver<DownloadStatus>)>;
|
||||||
fn get_all_downloads(&self) -> Vec<Download>;
|
fn get_all_downloads(&self) -> Vec<Download>;
|
||||||
@@ -273,16 +271,8 @@ impl SessionApi for Client {
|
|||||||
username: String,
|
username: String,
|
||||||
size: u64,
|
size: u64,
|
||||||
download_directory: String,
|
download_directory: String,
|
||||||
use_username_dirs: bool,
|
|
||||||
) -> Result<(Download, Receiver<DownloadStatus>)> {
|
) -> Result<(Download, Receiver<DownloadStatus>)> {
|
||||||
Self::download(
|
Self::download(self, filename, username, size, download_directory)
|
||||||
self,
|
|
||||||
filename,
|
|
||||||
username,
|
|
||||||
size,
|
|
||||||
download_directory,
|
|
||||||
use_username_dirs,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn download_with_metadata(
|
fn download_with_metadata(
|
||||||
@@ -291,7 +281,6 @@ impl SessionApi for Client {
|
|||||||
username: String,
|
username: String,
|
||||||
size: u64,
|
size: u64,
|
||||||
download_directory: String,
|
download_directory: String,
|
||||||
use_username_dirs: bool,
|
|
||||||
metadata: DownloadMetadata,
|
metadata: DownloadMetadata,
|
||||||
) -> Result<(Download, Receiver<DownloadStatus>)> {
|
) -> Result<(Download, Receiver<DownloadStatus>)> {
|
||||||
Self::download_with_metadata(
|
Self::download_with_metadata(
|
||||||
@@ -300,7 +289,6 @@ impl SessionApi for Client {
|
|||||||
username,
|
username,
|
||||||
size,
|
size,
|
||||||
download_directory,
|
download_directory,
|
||||||
use_username_dirs,
|
|
||||||
metadata,
|
metadata,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -518,7 +518,6 @@ fn download_one(
|
|||||||
request.user.clone(),
|
request.user.clone(),
|
||||||
request.size,
|
request.size,
|
||||||
download_dir.to_string(),
|
download_dir.to_string(),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.map_err(|e| CliError::transfer(format!("cannot start: {e}")))?;
|
.map_err(|e| CliError::transfer(format!("cannot start: {e}")))?;
|
||||||
|
|
||||||
|
|||||||
@@ -441,7 +441,6 @@ fn restore(
|
|||||||
download.username.clone(),
|
download.username.clone(),
|
||||||
download.size,
|
download.size,
|
||||||
download.download_directory.clone(),
|
download.download_directory.clone(),
|
||||||
download.use_username_dirs,
|
|
||||||
) {
|
) {
|
||||||
Ok((_, updates)) => {
|
Ok((_, updates)) => {
|
||||||
if let Ok(mut pending) = downloads.lock() {
|
if let Ok(mut pending) = downloads.lock() {
|
||||||
|
|||||||
@@ -220,7 +220,6 @@ pub struct DownloadDto {
|
|||||||
pub token: u32,
|
pub token: u32,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub download_directory: String,
|
pub download_directory: String,
|
||||||
pub use_username_dirs: bool,
|
|
||||||
pub status: DownloadStatusDto,
|
pub status: DownloadStatusDto,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub queue_position: Option<u32>,
|
pub queue_position: Option<u32>,
|
||||||
@@ -236,7 +235,6 @@ impl From<&Download> for DownloadDto {
|
|||||||
token: download.token,
|
token: download.token,
|
||||||
size: download.size,
|
size: download.size,
|
||||||
download_directory: download.download_directory.clone(),
|
download_directory: download.download_directory.clone(),
|
||||||
use_username_dirs: download.use_username_dirs,
|
|
||||||
status: DownloadStatusDto::from(&download.status),
|
status: DownloadStatusDto::from(&download.status),
|
||||||
queue_position: download.queue_position,
|
queue_position: download.queue_position,
|
||||||
metadata: DownloadMetadataDto::from(&download.metadata),
|
metadata: DownloadMetadataDto::from(&download.metadata),
|
||||||
|
|||||||
@@ -436,7 +436,6 @@ impl Daemon {
|
|||||||
params.username.clone(),
|
params.username.clone(),
|
||||||
params.size,
|
params.size,
|
||||||
directory,
|
directory,
|
||||||
false,
|
|
||||||
params.metadata.into(),
|
params.metadata.into(),
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@@ -877,7 +876,6 @@ mod tests {
|
|||||||
username: String,
|
username: String,
|
||||||
size: u64,
|
size: u64,
|
||||||
directory: String,
|
directory: String,
|
||||||
use_username_dirs: bool,
|
|
||||||
) -> soulseek_rs::Result<(
|
) -> soulseek_rs::Result<(
|
||||||
soulseek_rs::types::Download,
|
soulseek_rs::types::Download,
|
||||||
Receiver<soulseek_rs::DownloadStatus>,
|
Receiver<soulseek_rs::DownloadStatus>,
|
||||||
@@ -887,7 +885,6 @@ mod tests {
|
|||||||
username,
|
username,
|
||||||
size,
|
size,
|
||||||
directory,
|
directory,
|
||||||
use_username_dirs,
|
|
||||||
soulseek_rs::types::DownloadMetadata::default(),
|
soulseek_rs::types::DownloadMetadata::default(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -897,7 +894,6 @@ mod tests {
|
|||||||
_username: String,
|
_username: String,
|
||||||
_size: u64,
|
_size: u64,
|
||||||
_directory: String,
|
_directory: String,
|
||||||
_use_username_dirs: bool,
|
|
||||||
_metadata: soulseek_rs::types::DownloadMetadata,
|
_metadata: soulseek_rs::types::DownloadMetadata,
|
||||||
) -> soulseek_rs::Result<(
|
) -> soulseek_rs::Result<(
|
||||||
soulseek_rs::types::Download,
|
soulseek_rs::types::Download,
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ impl FileConfig {
|
|||||||
"daemon" => self.daemon.clone(),
|
"daemon" => self.daemon.clone(),
|
||||||
"daemon_token" => self.daemon_token.clone(),
|
"daemon_token" => self.daemon_token.clone(),
|
||||||
"use_username_dirs" => {
|
"use_username_dirs" => {
|
||||||
self.use_username_dirs.clone().map(|v| v.to_string())
|
self.use_username_dirs.map(|v| v.to_string())
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ mod tests {
|
|||||||
token: 7,
|
token: 7,
|
||||||
size: 42,
|
size: 42,
|
||||||
download_directory: "/music".into(),
|
download_directory: "/music".into(),
|
||||||
use_username_dirs: false,
|
|
||||||
status,
|
status,
|
||||||
sender,
|
sender,
|
||||||
queue_position: None,
|
queue_position: None,
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ pub struct PersistedDownload {
|
|||||||
pub filename: String,
|
pub filename: String,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub download_directory: String,
|
pub download_directory: String,
|
||||||
pub use_username_dirs: bool,
|
|
||||||
pub completed: bool,
|
pub completed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +36,6 @@ impl PersistedDownload {
|
|||||||
filename: download.filename.clone(),
|
filename: download.filename.clone(),
|
||||||
size: download.size,
|
size: download.size,
|
||||||
download_directory: download.download_directory.clone(),
|
download_directory: download.download_directory.clone(),
|
||||||
use_username_dirs: download.use_username_dirs,
|
|
||||||
completed: matches!(download.status, DownloadStatus::Completed),
|
completed: matches!(download.status, DownloadStatus::Completed),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -184,7 +182,6 @@ mod tests {
|
|||||||
filename: "@@abc\\music\\song.mp3".into(),
|
filename: "@@abc\\music\\song.mp3".into(),
|
||||||
size: 123,
|
size: 123,
|
||||||
download_directory: "/music".into(),
|
download_directory: "/music".into(),
|
||||||
use_username_dirs: false,
|
|
||||||
completed: false,
|
completed: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -367,7 +367,6 @@ fn rebuild(dto: DownloadDto, sender: Sender<DownloadStatus>) -> Download {
|
|||||||
token: dto.token,
|
token: dto.token,
|
||||||
size: dto.size,
|
size: dto.size,
|
||||||
download_directory: dto.download_directory,
|
download_directory: dto.download_directory,
|
||||||
use_username_dirs: dto.use_username_dirs,
|
|
||||||
status: dto.status.into(),
|
status: dto.status.into(),
|
||||||
sender,
|
sender,
|
||||||
queue_position: dto.queue_position,
|
queue_position: dto.queue_position,
|
||||||
@@ -579,7 +578,6 @@ impl SessionApi for RemoteSession {
|
|||||||
username: String,
|
username: String,
|
||||||
size: u64,
|
size: u64,
|
||||||
_download_directory: String,
|
_download_directory: String,
|
||||||
_use_username_dirs: bool,
|
|
||||||
// the daemon's own download directory wins — a remote caller
|
// the daemon's own download directory wins — a remote caller
|
||||||
// cannot see the filesystem the bytes land on. Plumb a per-transfer
|
// cannot see the filesystem the bytes land on. Plumb a per-transfer
|
||||||
// directory through if a client ever needs to sort into subfolders.
|
// directory through if a client ever needs to sort into subfolders.
|
||||||
@@ -598,7 +596,6 @@ impl SessionApi for RemoteSession {
|
|||||||
username: String,
|
username: String,
|
||||||
size: u64,
|
size: u64,
|
||||||
_download_directory: String,
|
_download_directory: String,
|
||||||
_use_username_dirs: bool,
|
|
||||||
metadata: DownloadMetadata,
|
metadata: DownloadMetadata,
|
||||||
) -> Result<(Download, Receiver<DownloadStatus>)> {
|
) -> Result<(Download, Receiver<DownloadStatus>)> {
|
||||||
self.start_download(filename, username, size, metadata)
|
self.start_download(filename, username, size, metadata)
|
||||||
|
|||||||
@@ -177,7 +177,6 @@ mod tests {
|
|||||||
token: 1,
|
token: 1,
|
||||||
size,
|
size,
|
||||||
download_directory: "/tmp".to_string(),
|
download_directory: "/tmp".to_string(),
|
||||||
use_username_dirs: false,
|
|
||||||
status,
|
status,
|
||||||
sender,
|
sender,
|
||||||
queue_position: None,
|
queue_position: None,
|
||||||
|
|||||||
@@ -221,7 +221,6 @@ impl MainTui {
|
|||||||
let sender = self.downloads_sender();
|
let sender = self.downloads_sender();
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let download_dir = self.download_dir.clone();
|
let download_dir = self.download_dir.clone();
|
||||||
let use_username_dirs = self.use_username_dirs;
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
for (path, size) in files {
|
for (path, size) in files {
|
||||||
match client.download(
|
match client.download(
|
||||||
@@ -229,7 +228,6 @@ impl MainTui {
|
|||||||
username.clone(),
|
username.clone(),
|
||||||
size,
|
size,
|
||||||
download_dir.clone(),
|
download_dir.clone(),
|
||||||
use_username_dirs,
|
|
||||||
) {
|
) {
|
||||||
Ok((download, rx)) => {
|
Ok((download, rx)) => {
|
||||||
let _ = sender.send((download, rx));
|
let _ = sender.send((download, rx));
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use super::MainTui;
|
use super::MainTui;
|
||||||
use crate::models::{DownloadEntry, FileDisplayData};
|
use crate::models::{DownloadEntry, FileDisplayData};
|
||||||
use crate::ui::panes::{InfoSubject, selected_transfer};
|
use crate::ui::panes::{InfoSubject, selected_transfer};
|
||||||
|
use crate::ui::sanitize_path_component;
|
||||||
use soulseek_rs::types::UploadStatus;
|
use soulseek_rs::types::UploadStatus;
|
||||||
use soulseek_rs::{DownloadStatus, types::Download};
|
use soulseek_rs::{DownloadStatus, types::Download};
|
||||||
|
use std::path::Path;
|
||||||
use std::{sync::mpsc, thread};
|
use std::{sync::mpsc, thread};
|
||||||
|
|
||||||
impl MainTui {
|
impl MainTui {
|
||||||
@@ -48,16 +50,9 @@ impl MainTui {
|
|||||||
self.state.downloads.remove(index);
|
self.state.downloads.remove(index);
|
||||||
self.select_download_after_removal(index);
|
self.select_download_after_removal(index);
|
||||||
let _ = self.client.remove_download(&username, &filename);
|
let _ = self.client.remove_download(&username, &filename);
|
||||||
let use_username_dirs = self.use_username_dirs;
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
match client.download(
|
match client.download(filename.clone(), username, size, directory) {
|
||||||
filename.clone(),
|
|
||||||
username,
|
|
||||||
size,
|
|
||||||
directory,
|
|
||||||
use_username_dirs,
|
|
||||||
) {
|
|
||||||
Ok((download, rx)) => {
|
Ok((download, rx)) => {
|
||||||
let _ = sender.send((download, rx));
|
let _ = sender.send((download, rx));
|
||||||
}
|
}
|
||||||
@@ -214,12 +209,21 @@ impl MainTui {
|
|||||||
peer_upload_speed: Some(file.speed),
|
peer_upload_speed: Some(file.speed),
|
||||||
peer_free_slots: Some(file.slots),
|
peer_free_slots: Some(file.slots),
|
||||||
};
|
};
|
||||||
|
let download_dir = if use_username_dirs {
|
||||||
|
let sanitized_username =
|
||||||
|
sanitize_path_component(file.username.as_str());
|
||||||
|
Path::new(download_dir.as_str())
|
||||||
|
.join(sanitized_username.as_str())
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
download_dir.clone()
|
||||||
|
};
|
||||||
match client.download_with_metadata(
|
match client.download_with_metadata(
|
||||||
file.filename.clone(),
|
file.filename.clone(),
|
||||||
file.username.clone(),
|
file.username.clone(),
|
||||||
file.size,
|
file.size,
|
||||||
download_dir.clone(),
|
download_dir,
|
||||||
use_username_dirs,
|
|
||||||
metadata,
|
metadata,
|
||||||
) {
|
) {
|
||||||
Ok((download, rx)) => {
|
Ok((download, rx)) => {
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ use crate::persist::{
|
|||||||
snapshot::{Snapshot, restore_messages, restore_searches},
|
snapshot::{Snapshot, restore_messages, restore_searches},
|
||||||
state::{PersistedMessage, StateStore},
|
state::{PersistedMessage, StateStore},
|
||||||
};
|
};
|
||||||
|
use crate::ui::sanitize_path_component;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
DefaultTerminal,
|
DefaultTerminal,
|
||||||
crossterm::event::{self, Event, KeyEventKind, poll},
|
crossterm::event::{self, Event, KeyEventKind, poll},
|
||||||
};
|
};
|
||||||
|
use std::path::Path;
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
pub struct MainTui {
|
pub struct MainTui {
|
||||||
@@ -121,7 +123,6 @@ impl MainTui {
|
|||||||
token: 0,
|
token: 0,
|
||||||
size: entry.size,
|
size: entry.size,
|
||||||
download_directory: entry.download_directory,
|
download_directory: entry.download_directory,
|
||||||
use_username_dirs: entry.use_username_dirs,
|
|
||||||
status: soulseek_rs::DownloadStatus::Completed,
|
status: soulseek_rs::DownloadStatus::Completed,
|
||||||
sender: std::sync::mpsc::channel().0,
|
sender: std::sync::mpsc::channel().0,
|
||||||
queue_position: None,
|
queue_position: None,
|
||||||
@@ -133,13 +134,23 @@ impl MainTui {
|
|||||||
} else {
|
} else {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let sender = sender.clone();
|
let sender = sender.clone();
|
||||||
|
let use_username_dirs = self.use_username_dirs;
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
|
let sanitized_username =
|
||||||
|
sanitize_path_component(entry.username.as_str());
|
||||||
|
let download_dir = if use_username_dirs {
|
||||||
|
Path::new(entry.download_directory.as_str())
|
||||||
|
.join(sanitized_username.as_str())
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
entry.download_directory
|
||||||
|
};
|
||||||
match client.download(
|
match client.download(
|
||||||
entry.filename.clone(),
|
entry.filename.clone(),
|
||||||
entry.username,
|
entry.username,
|
||||||
entry.size,
|
entry.size,
|
||||||
entry.download_directory,
|
download_dir,
|
||||||
entry.use_username_dirs,
|
|
||||||
) {
|
) {
|
||||||
Ok((download, rx)) => {
|
Ok((download, rx)) => {
|
||||||
let _ = sender.send((download, rx));
|
let _ = sender.send((download, rx));
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ impl MainTui {
|
|||||||
account,
|
account,
|
||||||
self.download_dir.clone(),
|
self.download_dir.clone(),
|
||||||
self.client.shared_directories(),
|
self.client.shared_directories(),
|
||||||
self.use_username_dirs.clone(),
|
self.use_username_dirs,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ fn at_status(
|
|||||||
token: 1,
|
token: 1,
|
||||||
size: 4096,
|
size: 4096,
|
||||||
download_directory: "/tmp".to_string(),
|
download_directory: "/tmp".to_string(),
|
||||||
use_username_dirs: false,
|
|
||||||
status,
|
status,
|
||||||
sender,
|
sender,
|
||||||
queue_position: None,
|
queue_position: None,
|
||||||
@@ -161,7 +160,6 @@ impl SessionApi for TalkativeSession {
|
|||||||
_username: String,
|
_username: String,
|
||||||
_size: u64,
|
_size: u64,
|
||||||
_directory: String,
|
_directory: String,
|
||||||
_use_username_dirs: bool,
|
|
||||||
) -> soulseek_rs::Result<(
|
) -> soulseek_rs::Result<(
|
||||||
soulseek_rs::types::Download,
|
soulseek_rs::types::Download,
|
||||||
std::sync::mpsc::Receiver<soulseek_rs::DownloadStatus>,
|
std::sync::mpsc::Receiver<soulseek_rs::DownloadStatus>,
|
||||||
@@ -174,7 +172,6 @@ impl SessionApi for TalkativeSession {
|
|||||||
_username: String,
|
_username: String,
|
||||||
_size: u64,
|
_size: u64,
|
||||||
_directory: String,
|
_directory: String,
|
||||||
_use_username_dirs: bool,
|
|
||||||
_metadata: soulseek_rs::types::DownloadMetadata,
|
_metadata: soulseek_rs::types::DownloadMetadata,
|
||||||
) -> soulseek_rs::Result<(
|
) -> soulseek_rs::Result<(
|
||||||
soulseek_rs::types::Download,
|
soulseek_rs::types::Download,
|
||||||
|
|||||||
@@ -339,7 +339,6 @@ mod tests {
|
|||||||
token: 1,
|
token: 1,
|
||||||
size: 4096,
|
size: 4096,
|
||||||
download_directory: "/tmp".to_string(),
|
download_directory: "/tmp".to_string(),
|
||||||
use_username_dirs: false,
|
|
||||||
status,
|
status,
|
||||||
sender,
|
sender,
|
||||||
queue_position: None,
|
queue_position: None,
|
||||||
|
|||||||
@@ -436,7 +436,6 @@ mod tests {
|
|||||||
token: 1,
|
token: 1,
|
||||||
size: 100,
|
size: 100,
|
||||||
download_directory: "~/dl".to_string(),
|
download_directory: "~/dl".to_string(),
|
||||||
use_username_dirs: false,
|
|
||||||
status,
|
status,
|
||||||
sender,
|
sender,
|
||||||
queue_position: None,
|
queue_position: None,
|
||||||
|
|||||||
@@ -72,6 +72,41 @@ pub const fn get_spinner_char(state: usize) -> &'static str {
|
|||||||
SPINNER_CHARS[state % SPINNER_CHARS.len()]
|
SPINNER_CHARS[state % SPINNER_CHARS.len()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn sanitize_path_component(name: &str) -> String {
|
||||||
|
let mut out = name
|
||||||
|
.chars()
|
||||||
|
.map(|c| match c {
|
||||||
|
'/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_',
|
||||||
|
c if c.is_control() => '_',
|
||||||
|
c => c,
|
||||||
|
})
|
||||||
|
.collect::<String>();
|
||||||
|
if out.is_empty() || out.ends_with(['.', ' ']) {
|
||||||
|
out.push('_');
|
||||||
|
}
|
||||||
|
if is_reserved(out.as_str()) {
|
||||||
|
out.insert(0, '_');
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn is_reserved(name: &str) -> bool {
|
||||||
|
let stem = name.split('.').next().unwrap_or(name).to_ascii_uppercase();
|
||||||
|
if matches!(stem.as_str(), "CON" | "PRN" | "AUX" | "NUL") {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
matches!(stem.get(..3), Some("COM" | "LPT"))
|
||||||
|
&& matches!(stem.as_bytes().get(3..), Some([b'1'..=b'9']))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn is_reserved(name: &str) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ fn download_entry(filename: &str, status: DownloadStatus) -> DownloadEntry {
|
|||||||
token: 1,
|
token: 1,
|
||||||
size: 1000,
|
size: 1000,
|
||||||
download_directory: "/music".into(),
|
download_directory: "/music".into(),
|
||||||
use_username_dirs: false,
|
|
||||||
status,
|
status,
|
||||||
sender: std::sync::mpsc::channel().0,
|
sender: std::sync::mpsc::channel().0,
|
||||||
queue_position: None,
|
queue_position: None,
|
||||||
|
|||||||
Reference in New Issue
Block a user