feat(tui): added option for storing downloaded files in username directories
This commit is contained in:
@@ -1148,9 +1148,13 @@ 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)) =
|
let Ok((_download, status_rx)) = client.download(
|
||||||
client.download(filename.clone(), peer.clone(), size, dir)
|
filename.clone(),
|
||||||
else {
|
peer.clone(),
|
||||||
|
size,
|
||||||
|
dir,
|
||||||
|
false,
|
||||||
|
) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let deadline = run_deadline;
|
let deadline = run_deadline;
|
||||||
|
|||||||
@@ -99,12 +99,14 @@ 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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -115,6 +117,7 @@ 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);
|
||||||
@@ -132,6 +135,7 @@ 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,6 +23,7 @@ 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,6 +97,7 @@ 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,6 +242,7 @@ 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,6 +110,10 @@ 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));
|
||||||
|
|
||||||
@@ -635,6 +639,7 @@ 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,6 +479,7 @@ 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,6 +185,7 @@ 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>,
|
||||||
|
|||||||
+13
-1
@@ -102,6 +102,7 @@ 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,
|
||||||
@@ -109,6 +110,7 @@ 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>;
|
||||||
@@ -271,8 +273,16 @@ 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, filename, username, size, download_directory)
|
Self::download(
|
||||||
|
self,
|
||||||
|
filename,
|
||||||
|
username,
|
||||||
|
size,
|
||||||
|
download_directory,
|
||||||
|
use_username_dirs,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn download_with_metadata(
|
fn download_with_metadata(
|
||||||
@@ -281,6 +291,7 @@ 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(
|
||||||
@@ -289,6 +300,7 @@ impl SessionApi for Client {
|
|||||||
username,
|
username,
|
||||||
size,
|
size,
|
||||||
download_directory,
|
download_directory,
|
||||||
|
use_username_dirs,
|
||||||
metadata,
|
metadata,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -518,6 +518,7 @@ 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,6 +441,7 @@ 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,6 +220,7 @@ 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>,
|
||||||
@@ -235,6 +236,7 @@ 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,6 +436,7 @@ 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| {
|
||||||
@@ -876,6 +877,7 @@ 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>,
|
||||||
@@ -885,6 +887,7 @@ mod tests {
|
|||||||
username,
|
username,
|
||||||
size,
|
size,
|
||||||
directory,
|
directory,
|
||||||
|
use_username_dirs,
|
||||||
soulseek_rs::types::DownloadMetadata::default(),
|
soulseek_rs::types::DownloadMetadata::default(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -894,6 +897,7 @@ 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,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ impl SettingsMode {
|
|||||||
pub enum SettingsRow {
|
pub enum SettingsRow {
|
||||||
ChangePassword,
|
ChangePassword,
|
||||||
Logout,
|
Logout,
|
||||||
|
UseUsernameDirs,
|
||||||
DownloadDir,
|
DownloadDir,
|
||||||
/// Share path at this index.
|
/// Share path at this index.
|
||||||
Share(usize),
|
Share(usize),
|
||||||
@@ -79,6 +80,7 @@ pub struct SettingsState {
|
|||||||
pub account: AccountInfo,
|
pub account: AccountInfo,
|
||||||
pub download_dir: String,
|
pub download_dir: String,
|
||||||
pub share_dirs: Vec<String>,
|
pub share_dirs: Vec<String>,
|
||||||
|
pub use_username_dirs: bool,
|
||||||
/// Index into [`Self::rows`].
|
/// Index into [`Self::rows`].
|
||||||
pub selected: usize,
|
pub selected: usize,
|
||||||
pub mode: SettingsMode,
|
pub mode: SettingsMode,
|
||||||
@@ -94,11 +96,13 @@ impl SettingsState {
|
|||||||
account: AccountInfo,
|
account: AccountInfo,
|
||||||
download_dir: String,
|
download_dir: String,
|
||||||
share_dirs: Vec<String>,
|
share_dirs: Vec<String>,
|
||||||
|
use_username_dirs: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
account,
|
account,
|
||||||
download_dir,
|
download_dir,
|
||||||
share_dirs,
|
share_dirs,
|
||||||
|
use_username_dirs,
|
||||||
selected: 0,
|
selected: 0,
|
||||||
mode: SettingsMode::Navigate,
|
mode: SettingsMode::Navigate,
|
||||||
input: String::new(),
|
input: String::new(),
|
||||||
@@ -119,6 +123,7 @@ impl SettingsState {
|
|||||||
if self.can_log_out() {
|
if self.can_log_out() {
|
||||||
rows.push(SettingsRow::Logout);
|
rows.push(SettingsRow::Logout);
|
||||||
}
|
}
|
||||||
|
rows.push(SettingsRow::UseUsernameDirs);
|
||||||
rows.push(SettingsRow::DownloadDir);
|
rows.push(SettingsRow::DownloadDir);
|
||||||
rows.extend((0..self.share_dirs.len()).map(SettingsRow::Share));
|
rows.extend((0..self.share_dirs.len()).map(SettingsRow::Share));
|
||||||
rows
|
rows
|
||||||
@@ -191,6 +196,10 @@ impl SettingsState {
|
|||||||
self.input.clear();
|
self.input.clear();
|
||||||
}
|
}
|
||||||
SettingsRow::Logout => self.mode = SettingsMode::ConfirmingLogout,
|
SettingsRow::Logout => self.mode = SettingsMode::ConfirmingLogout,
|
||||||
|
SettingsRow::UseUsernameDirs => {
|
||||||
|
self.use_username_dirs = !self.use_username_dirs;
|
||||||
|
return SettingsAction::Apply;
|
||||||
|
}
|
||||||
SettingsRow::DownloadDir => {
|
SettingsRow::DownloadDir => {
|
||||||
self.mode = SettingsMode::EditingDownloadDir;
|
self.mode = SettingsMode::EditingDownloadDir;
|
||||||
self.input.clone_from(&self.download_dir);
|
self.input.clone_from(&self.download_dir);
|
||||||
@@ -300,6 +309,7 @@ mod tests {
|
|||||||
account(),
|
account(),
|
||||||
"/dl".into(),
|
"/dl".into(),
|
||||||
vec!["/dl".into(), "/music".into()],
|
vec!["/dl".into(), "/music".into()],
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,6 +503,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
"/dl".into(),
|
"/dl".into(),
|
||||||
vec!["/music".into()],
|
vec!["/music".into()],
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
s.rows(),
|
s.rows(),
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub struct FileConfig {
|
|||||||
/// A TOML array rather than a `config set` key, because a query may itself
|
/// A TOML array rather than a `config set` key, because a query may itself
|
||||||
/// contain a comma.
|
/// contain a comma.
|
||||||
pub wishlist: Option<Vec<String>>,
|
pub wishlist: Option<Vec<String>>,
|
||||||
|
pub use_username_dirs: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileConfig {
|
impl FileConfig {
|
||||||
@@ -59,6 +60,7 @@ impl FileConfig {
|
|||||||
"password_cmd",
|
"password_cmd",
|
||||||
"daemon",
|
"daemon",
|
||||||
"daemon_token",
|
"daemon_token",
|
||||||
|
"use_username_dirs",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// The value of `key`, or `None` when it is unset or unknown. Lists are
|
/// The value of `key`, or `None` when it is unset or unknown. Lists are
|
||||||
@@ -83,6 +85,9 @@ impl FileConfig {
|
|||||||
"password_cmd" => self.password_cmd.clone(),
|
"password_cmd" => self.password_cmd.clone(),
|
||||||
"daemon" => self.daemon.clone(),
|
"daemon" => self.daemon.clone(),
|
||||||
"daemon_token" => self.daemon_token.clone(),
|
"daemon_token" => self.daemon_token.clone(),
|
||||||
|
"use_username_dirs" => {
|
||||||
|
self.use_username_dirs.clone().map(|v| v.to_string())
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,6 +167,17 @@ impl FileConfig {
|
|||||||
// The single-folder spelling would otherwise shadow the list.
|
// The single-folder spelling would otherwise shadow the list.
|
||||||
self.shared_dir = None;
|
self.shared_dir = None;
|
||||||
}
|
}
|
||||||
|
"use_username_dirs" => {
|
||||||
|
self.use_username_dirs = Some(match value {
|
||||||
|
"true" | "1" | "yes" => true,
|
||||||
|
"false" | "0" | "no" => false,
|
||||||
|
other => {
|
||||||
|
return Err(format!(
|
||||||
|
"use_username_dirs wants true or false, got '{other}'"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"unknown setting '{other}' — try one of: {}",
|
"unknown setting '{other}' — try one of: {}",
|
||||||
@@ -277,6 +293,7 @@ pub struct Resolved {
|
|||||||
pub accept_children: bool,
|
pub accept_children: bool,
|
||||||
pub download_dir: String,
|
pub download_dir: String,
|
||||||
pub shared_dirs: Vec<String>,
|
pub shared_dirs: Vec<String>,
|
||||||
|
pub use_username_dirs: bool,
|
||||||
pub max_concurrent_downloads: usize,
|
pub max_concurrent_downloads: usize,
|
||||||
pub search_timeout: u64,
|
pub search_timeout: u64,
|
||||||
pub password_cmd: Option<String>,
|
pub password_cmd: Option<String>,
|
||||||
@@ -330,6 +347,7 @@ pub fn resolve(cli: &crate::cli::Cli, file: &FileConfig) -> Resolved {
|
|||||||
&& !disable_listener,
|
&& !disable_listener,
|
||||||
download_dir: download_dir.clone(),
|
download_dir: download_dir.clone(),
|
||||||
shared_dirs: resolve_shared_dirs(cli, file, &download_dir),
|
shared_dirs: resolve_shared_dirs(cli, file, &download_dir),
|
||||||
|
use_username_dirs: file.use_username_dirs.unwrap_or(false),
|
||||||
max_concurrent_downloads: cli
|
max_concurrent_downloads: cli
|
||||||
.max_concurrent_downloads
|
.max_concurrent_downloads
|
||||||
.or(file.max_concurrent_downloads)
|
.or(file.max_concurrent_downloads)
|
||||||
@@ -573,6 +591,7 @@ mod tests {
|
|||||||
daemon: Some("nas.local:5030".into()),
|
daemon: Some("nas.local:5030".into()),
|
||||||
daemon_token: Some("deadbeef".into()),
|
daemon_token: Some("deadbeef".into()),
|
||||||
wishlist: None,
|
wishlist: None,
|
||||||
|
use_username_dirs: Some(false),
|
||||||
};
|
};
|
||||||
let resolved = resolve(&bare_cli(), &file);
|
let resolved = resolve(&bare_cli(), &file);
|
||||||
assert_eq!(resolved.username.as_deref(), Some("alice"));
|
assert_eq!(resolved.username.as_deref(), Some("alice"));
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ 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,6 +22,7 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ 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),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -182,6 +184,7 @@ 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,6 +367,7 @@ 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,
|
||||||
@@ -578,6 +579,7 @@ 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.
|
||||||
@@ -596,6 +598,7 @@ 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)
|
||||||
|
|||||||
@@ -522,6 +522,7 @@ fn run_default_tui(
|
|||||||
terminal,
|
terminal,
|
||||||
Arc::new(outcome.client),
|
Arc::new(outcome.client),
|
||||||
resolved.download_dir.clone(),
|
resolved.download_dir.clone(),
|
||||||
|
resolved.use_username_dirs,
|
||||||
Duration::from_secs(resolved.search_timeout),
|
Duration::from_secs(resolved.search_timeout),
|
||||||
store,
|
store,
|
||||||
config_path.clone(),
|
config_path.clone(),
|
||||||
@@ -598,6 +599,7 @@ fn run_attached_tui(
|
|||||||
terminal,
|
terminal,
|
||||||
Arc::new(session),
|
Arc::new(session),
|
||||||
download_dir,
|
download_dir,
|
||||||
|
resolved.use_username_dirs,
|
||||||
Duration::from_secs(resolved.search_timeout),
|
Duration::from_secs(resolved.search_timeout),
|
||||||
None,
|
None,
|
||||||
config_path,
|
config_path,
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ 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,6 +221,7 @@ 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(
|
||||||
@@ -228,6 +229,7 @@ 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));
|
||||||
|
|||||||
@@ -48,9 +48,16 @@ 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(filename.clone(), username, size, directory) {
|
match client.download(
|
||||||
|
filename.clone(),
|
||||||
|
username,
|
||||||
|
size,
|
||||||
|
directory,
|
||||||
|
use_username_dirs,
|
||||||
|
) {
|
||||||
Ok((download, rx)) => {
|
Ok((download, rx)) => {
|
||||||
let _ = sender.send((download, rx));
|
let _ = sender.send((download, rx));
|
||||||
}
|
}
|
||||||
@@ -197,6 +204,7 @@ 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 file in selected_files {
|
for file in selected_files {
|
||||||
@@ -211,6 +219,7 @@ impl MainTui {
|
|||||||
file.username.clone(),
|
file.username.clone(),
|
||||||
file.size,
|
file.size,
|
||||||
download_dir.clone(),
|
download_dir.clone(),
|
||||||
|
use_username_dirs,
|
||||||
metadata,
|
metadata,
|
||||||
) {
|
) {
|
||||||
Ok((download, rx)) => {
|
Ok((download, rx)) => {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ pub struct MainTui {
|
|||||||
client: Arc<dyn SessionApi>,
|
client: Arc<dyn SessionApi>,
|
||||||
state: AppState,
|
state: AppState,
|
||||||
download_dir: String,
|
download_dir: String,
|
||||||
|
use_username_dirs: bool,
|
||||||
search_timeout: Duration,
|
search_timeout: Duration,
|
||||||
spinner_state: usize,
|
spinner_state: usize,
|
||||||
store: Option<StateStore>,
|
store: Option<StateStore>,
|
||||||
@@ -40,6 +41,7 @@ impl MainTui {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
client: Arc<dyn SessionApi>,
|
client: Arc<dyn SessionApi>,
|
||||||
download_dir: String,
|
download_dir: String,
|
||||||
|
use_username_dirs: bool,
|
||||||
search_timeout: Duration,
|
search_timeout: Duration,
|
||||||
store: Option<StateStore>,
|
store: Option<StateStore>,
|
||||||
config_path: Option<std::path::PathBuf>,
|
config_path: Option<std::path::PathBuf>,
|
||||||
@@ -49,6 +51,7 @@ impl MainTui {
|
|||||||
state: AppState::new(),
|
state: AppState::new(),
|
||||||
download_dir,
|
download_dir,
|
||||||
search_timeout,
|
search_timeout,
|
||||||
|
use_username_dirs,
|
||||||
spinner_state: 0,
|
spinner_state: 0,
|
||||||
store,
|
store,
|
||||||
config_path,
|
config_path,
|
||||||
@@ -118,6 +121,7 @@ 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,
|
||||||
@@ -135,6 +139,7 @@ impl MainTui {
|
|||||||
entry.username,
|
entry.username,
|
||||||
entry.size,
|
entry.size,
|
||||||
entry.download_directory,
|
entry.download_directory,
|
||||||
|
entry.use_username_dirs,
|
||||||
) {
|
) {
|
||||||
Ok((download, rx)) => {
|
Ok((download, rx)) => {
|
||||||
let _ = sender.send((download, rx));
|
let _ = sender.send((download, rx));
|
||||||
@@ -256,13 +261,20 @@ pub fn launch_main_tui(
|
|||||||
terminal: DefaultTerminal,
|
terminal: DefaultTerminal,
|
||||||
client: Arc<dyn SessionApi>,
|
client: Arc<dyn SessionApi>,
|
||||||
download_dir: String,
|
download_dir: String,
|
||||||
|
use_username_dirs: bool,
|
||||||
search_timeout: Duration,
|
search_timeout: Duration,
|
||||||
store: Option<StateStore>,
|
store: Option<StateStore>,
|
||||||
config_path: Option<std::path::PathBuf>,
|
config_path: Option<std::path::PathBuf>,
|
||||||
listener_fallback: Option<(u16, u16)>,
|
listener_fallback: Option<(u16, u16)>,
|
||||||
) -> Result<TuiExit> {
|
) -> Result<TuiExit> {
|
||||||
let mut tui =
|
let mut tui = MainTui::new(
|
||||||
MainTui::new(client, download_dir, search_timeout, store, config_path);
|
client,
|
||||||
|
download_dir,
|
||||||
|
use_username_dirs,
|
||||||
|
search_timeout,
|
||||||
|
store,
|
||||||
|
config_path,
|
||||||
|
);
|
||||||
tui.listener_fallback = listener_fallback;
|
tui.listener_fallback = listener_fallback;
|
||||||
tui.run(terminal)
|
tui.run(terminal)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,6 +369,16 @@ impl MainTui {
|
|||||||
lines.push(Line::from(""));
|
lines.push(Line::from(""));
|
||||||
|
|
||||||
lines.push(section("Folders"));
|
lines.push(section("Folders"));
|
||||||
|
lines.push(entry(
|
||||||
|
selected == SettingsRow::UseUsernameDirs,
|
||||||
|
"Use username directory ",
|
||||||
|
if settings.use_username_dirs {
|
||||||
|
"[x]"
|
||||||
|
} else {
|
||||||
|
"[ ]"
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
lines.push(if settings.mode == SettingsMode::EditingDownloadDir {
|
lines.push(if settings.mode == SettingsMode::EditingDownloadDir {
|
||||||
typing("Download folder: ", settings.input.clone())
|
typing("Download folder: ", settings.input.clone())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ fn settings_shortcuts(
|
|||||||
SettingsRow::ChangePassword => ("Enter", "change password"),
|
SettingsRow::ChangePassword => ("Enter", "change password"),
|
||||||
SettingsRow::Logout => ("Enter", "log out"),
|
SettingsRow::Logout => ("Enter", "log out"),
|
||||||
SettingsRow::DownloadDir => ("Enter", "edit folder"),
|
SettingsRow::DownloadDir => ("Enter", "edit folder"),
|
||||||
|
SettingsRow::UseUsernameDirs => ("Enter", "toggle option"),
|
||||||
SettingsRow::Share(_) => ("d", "remove share"),
|
SettingsRow::Share(_) => ("d", "remove share"),
|
||||||
},
|
},
|
||||||
("a", "add share"),
|
("a", "add share"),
|
||||||
|
|||||||
@@ -20,6 +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(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@ impl MainTui {
|
|||||||
};
|
};
|
||||||
let download_dir = settings.download_dir.clone();
|
let download_dir = settings.download_dir.clone();
|
||||||
let share_dirs = settings.share_dirs.clone();
|
let share_dirs = settings.share_dirs.clone();
|
||||||
|
let use_username_dirs = settings.use_username_dirs;
|
||||||
|
|
||||||
let shares = if self.client.daemon_endpoint().is_some() {
|
let shares = if self.client.daemon_endpoint().is_some() {
|
||||||
share_dirs.clone()
|
share_dirs.clone()
|
||||||
@@ -121,6 +123,7 @@ impl MainTui {
|
|||||||
// load, so leaving it would resurrect a removed folder.
|
// load, so leaving it would resurrect a removed folder.
|
||||||
config.shared_dir = None;
|
config.shared_dir = None;
|
||||||
config.shared_dirs = Some(share_dirs);
|
config.shared_dirs = Some(share_dirs);
|
||||||
|
config.use_username_dirs = Some(use_username_dirs);
|
||||||
config.save(&path)
|
config.save(&path)
|
||||||
});
|
});
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ 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,
|
||||||
@@ -160,6 +161,7 @@ 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>,
|
||||||
@@ -172,6 +174,7 @@ 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,
|
||||||
@@ -349,6 +352,7 @@ fn attach(session: Arc<TalkativeSession>) -> MainTui {
|
|||||||
MainTui::new(
|
MainTui::new(
|
||||||
session,
|
session,
|
||||||
"/tmp".to_string(),
|
"/tmp".to_string(),
|
||||||
|
false,
|
||||||
Duration::from_secs(1),
|
Duration::from_secs(1),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ fn applying_settings_attached_pushes_both_folders_to_the_daemon() {
|
|||||||
let mut tui = MainTui::new(
|
let mut tui = MainTui::new(
|
||||||
session.clone(),
|
session.clone(),
|
||||||
"/daemon/old".to_string(),
|
"/daemon/old".to_string(),
|
||||||
|
false,
|
||||||
Duration::from_secs(1),
|
Duration::from_secs(1),
|
||||||
None,
|
None,
|
||||||
Some(config_path.clone()),
|
Some(config_path.clone()),
|
||||||
@@ -73,6 +74,7 @@ fn applying_settings_locally_stays_on_this_machine() {
|
|||||||
let mut tui = MainTui::new(
|
let mut tui = MainTui::new(
|
||||||
session.clone(),
|
session.clone(),
|
||||||
"/tmp".to_string(),
|
"/tmp".to_string(),
|
||||||
|
false,
|
||||||
Duration::from_secs(1),
|
Duration::from_secs(1),
|
||||||
None,
|
None,
|
||||||
Some(config_path),
|
Some(config_path),
|
||||||
|
|||||||
@@ -339,6 +339,7 @@ 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,6 +436,7 @@ 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,
|
||||||
|
|||||||
Reference in New Issue
Block a user