use std::collections::HashSet;
use crate::core::rclone::RcloneCommand as _;
use crate::core::rclone::command::Lsjson;
pub(crate) async fn list_existing_filenames(destination: &str) -> Result<HashSet<String>, String> {
let entries = (Lsjson {
path: destination.to_string(),
missing_ok: true,
job: "transform",
..Default::default()
})
.run()
.await?;
Ok(entries.into_iter().map(|entry| entry.name).collect())
}
pub(crate) async fn list_source_relative_paths(
source: &str,
extensions: &[&str],
) -> Result<Vec<String>, String> {
let entries = (Lsjson {
path: source.to_string(),
recursive: true,
include_extensions: extensions.iter().map(|ext| ext.to_string()).collect(),
missing_ok: false,
job: "transform",
})
.run()
.await?;
Ok(entries.into_iter().map(|entry| entry.path).collect())
}
#[cfg(test)]
mod tests {
use super::*;
async fn check_rclone_available() -> Result<(), String> {
crate::core::rclone::check_available().await
}
#[tokio::test]
async fn list_existing_filenames_lists_files_present_in_a_local_directory() {
if check_rclone_available().await.is_err() {
eprintln!("skipping: rclone not found on PATH");
return;
}
let dir = tempfile::tempdir().unwrap();
std::fs::write(
dir.path()
.join("2021-08-20_Delivery_Status_Notification_(Failure)_1.png"),
b"png bytes",
)
.unwrap();
std::fs::write(dir.path().join("plain.jpg"), b"jpeg bytes").unwrap();
let names = list_existing_filenames(dir.path().to_str().unwrap())
.await
.unwrap();
assert!(names.contains("2021-08-20_Delivery_Status_Notification_(Failure)_1.png"));
assert!(names.contains("plain.jpg"));
assert_eq!(names.len(), 2);
}
#[tokio::test]
async fn list_existing_filenames_returns_empty_for_a_destination_that_does_not_exist_yet() {
if check_rclone_available().await.is_err() {
eprintln!("skipping: rclone not found on PATH");
return;
}
let dir = tempfile::tempdir().unwrap();
let missing = dir.path().join("does-not-exist-yet");
let names = list_existing_filenames(missing.to_str().unwrap())
.await
.unwrap();
assert!(names.is_empty());
}
#[tokio::test]
async fn list_source_relative_paths_lists_nested_files_matching_the_extension() {
if check_rclone_available().await.is_err() {
eprintln!("skipping: rclone not found on PATH");
return;
}
let dir = tempfile::tempdir().unwrap();
std::fs::create_dir_all(dir.path().join("screenshots")).unwrap();
std::fs::write(dir.path().join("screenshots/img.png"), b"png bytes").unwrap();
std::fs::write(dir.path().join("notes.txt"), b"not a png").unwrap();
let paths = list_source_relative_paths(dir.path().to_str().unwrap(), &["png"])
.await
.unwrap();
assert_eq!(paths, vec!["screenshots/img.png".to_string()]);
}
#[tokio::test]
async fn list_source_relative_paths_matches_every_spelling_of_one_input_kind() {
if check_rclone_available().await.is_err() {
eprintln!("skipping: rclone not found on PATH");
return;
}
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("a.jpg"), b"jpg bytes").unwrap();
std::fs::write(dir.path().join("b.JPG"), b"uppercase jpg bytes").unwrap();
std::fs::write(dir.path().join("c.jpeg"), b"jpeg bytes").unwrap();
std::fs::write(dir.path().join("d.png"), b"a different format").unwrap();
let mut paths = list_source_relative_paths(dir.path().to_str().unwrap(), &["jpg", "jpeg"])
.await
.unwrap();
paths.sort();
assert_eq!(
paths,
vec![
"a.jpg".to_string(),
"b.JPG".to_string(),
"c.jpeg".to_string()
],
"every jpg/jpeg spelling must match, and only those"
);
}
#[tokio::test]
async fn list_source_relative_paths_errors_for_a_source_that_does_not_exist() {
if check_rclone_available().await.is_err() {
eprintln!("skipping: rclone not found on PATH");
return;
}
let dir = tempfile::tempdir().unwrap();
let missing = dir.path().join("does-not-exist-yet");
let err = list_source_relative_paths(missing.to_str().unwrap(), &["png"])
.await
.unwrap_err();
assert!(
err.contains("does-not-exist-yet"),
"the unresolvable source must be named in the error: {err}"
);
}
}