mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-04-19 05:59:16 -04:00
feat: Add file and index commands to CLI
Co-authored-by: ijamespine <ijamespine@me.com>
This commit is contained in:
@@ -33,6 +33,9 @@ enum Commands {
|
||||
/// File operations
|
||||
#[command(subcommand)]
|
||||
File(FileCommands),
|
||||
/// Indexing operations
|
||||
#[command(subcommand)]
|
||||
Index(IndexCommands),
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
@@ -45,6 +48,12 @@ enum LibraryCommands {
|
||||
enum FileCommands {
|
||||
/// Copy files
|
||||
Copy(FileCopyArgs),
|
||||
/// Delete files
|
||||
Delete(FileDeleteArgs),
|
||||
/// Validate files
|
||||
Validate(FileValidateArgs),
|
||||
/// Detect duplicate files
|
||||
Dedupe(FileDedupeArgs),
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
@@ -76,19 +85,169 @@ struct FileCopyArgs {
|
||||
impl FileCopyArgs {
|
||||
fn to_input(&self) -> sd_core::ops::files::copy::input::FileCopyInput {
|
||||
use sd_core::ops::files::copy::input::{CopyMethod, FileCopyInput};
|
||||
FileCopyInput {
|
||||
library_id: None,
|
||||
sources: self.sources.clone(),
|
||||
destination: self.destination.clone(),
|
||||
overwrite: self.overwrite,
|
||||
verify_checksum: self.verify_checksum,
|
||||
preserve_timestamps: self.preserve_timestamps,
|
||||
move_files: self.move_files,
|
||||
copy_method: CopyMethod::Auto,
|
||||
let mut input = FileCopyInput::new(self.sources.clone(), self.destination.clone())
|
||||
.with_overwrite(self.overwrite)
|
||||
.with_verification(self.verify_checksum)
|
||||
.with_timestamp_preservation(self.preserve_timestamps)
|
||||
.with_move(self.move_files)
|
||||
.with_copy_method(CopyMethod::Auto);
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
struct FileDeleteArgs {
|
||||
/// Files or directories to delete (one or more)
|
||||
pub targets: Vec<std::path::PathBuf>,
|
||||
|
||||
/// Permanently delete instead of moving to trash
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub permanent: bool,
|
||||
|
||||
/// Delete directories recursively
|
||||
#[arg(long, default_value_t = true)]
|
||||
pub recursive: bool,
|
||||
}
|
||||
|
||||
impl FileDeleteArgs {
|
||||
fn to_input(&self) -> sd_core::ops::files::delete::input::FileDeleteInput {
|
||||
use sd_core::domain::addressing::{SdPath, SdPathBatch};
|
||||
use sd_core::ops::files::delete::input::FileDeleteInput;
|
||||
let paths = self
|
||||
.targets
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(SdPath::local)
|
||||
.collect::<Vec<_>>();
|
||||
FileDeleteInput {
|
||||
targets: SdPathBatch::new(paths),
|
||||
permanent: self.permanent,
|
||||
recursive: self.recursive,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
struct FileValidateArgs {
|
||||
/// Paths to validate (one or more)
|
||||
pub paths: Vec<std::path::PathBuf>,
|
||||
|
||||
/// Verify checksums during validation
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub verify_checksums: bool,
|
||||
|
||||
/// Perform deep scan
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub deep_scan: bool,
|
||||
}
|
||||
|
||||
impl FileValidateArgs {
|
||||
fn to_input(&self) -> sd_core::ops::files::validation::input::FileValidationInput {
|
||||
use sd_core::ops::files::validation::input::FileValidationInput;
|
||||
FileValidationInput {
|
||||
paths: self.paths.clone(),
|
||||
verify_checksums: self.verify_checksums,
|
||||
deep_scan: self.deep_scan,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, ValueEnum)]
|
||||
enum DedupeAlgorithmArg {
|
||||
ContentHash,
|
||||
SizeOnly,
|
||||
NameAndSize,
|
||||
DeepScan,
|
||||
}
|
||||
|
||||
impl DedupeAlgorithmArg {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::ContentHash => "content_hash",
|
||||
Self::SizeOnly => "size_only",
|
||||
Self::NameAndSize => "name_and_size",
|
||||
Self::DeepScan => "deep_scan",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
struct FileDedupeArgs {
|
||||
/// Paths to scan for duplicates (one or more)
|
||||
pub paths: Vec<std::path::PathBuf>,
|
||||
|
||||
/// Detection algorithm
|
||||
#[arg(long, value_enum, default_value = "content-hash")]
|
||||
pub algorithm: DedupeAlgorithmArg,
|
||||
|
||||
/// Similarity threshold (0.0 - 1.0)
|
||||
#[arg(long, default_value_t = 1.0)]
|
||||
pub threshold: f64,
|
||||
}
|
||||
|
||||
impl FileDedupeArgs {
|
||||
fn to_input(&self) -> sd_core::ops::files::duplicate_detection::input::DuplicateDetectionInput {
|
||||
use sd_core::ops::files::duplicate_detection::input::DuplicateDetectionInput;
|
||||
DuplicateDetectionInput {
|
||||
paths: self.paths.clone(),
|
||||
algorithm: self.algorithm.as_str().to_string(),
|
||||
threshold: self.threshold,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum IndexCommands {
|
||||
/// Start indexing for one or more paths
|
||||
Start(IndexStartArgs),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, ValueEnum)]
|
||||
enum IndexModeArg { Shallow, Content, Deep }
|
||||
|
||||
#[derive(Debug, Clone, ValueEnum)]
|
||||
enum IndexScopeArg { Current, Recursive }
|
||||
|
||||
impl From<IndexModeArg> for sd_core::ops::indexing::job::IndexMode {
|
||||
fn from(m: IndexModeArg) -> Self {
|
||||
use sd_core::ops::indexing::job::IndexMode as M;
|
||||
match m { IndexModeArg::Shallow => M::Shallow, IndexModeArg::Content => M::Content, IndexModeArg::Deep => M::Deep }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IndexScopeArg> for sd_core::ops::indexing::job::IndexScope {
|
||||
fn from(s: IndexScopeArg) -> Self {
|
||||
use sd_core::ops::indexing::job::IndexScope as S;
|
||||
match s { IndexScopeArg::Current => S::Current, IndexScopeArg::Recursive => S::Recursive }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
struct IndexStartArgs {
|
||||
/// Paths to index (one or more)
|
||||
pub paths: Vec<std::path::PathBuf>,
|
||||
|
||||
/// Library ID to run indexing in (defaults to the only library if just one exists)
|
||||
#[arg(long)]
|
||||
pub library: Option<uuid::Uuid>,
|
||||
|
||||
/// Indexing mode
|
||||
#[arg(long, value_enum, default_value = "content")]
|
||||
pub mode: IndexModeArg,
|
||||
|
||||
/// Indexing scope
|
||||
#[arg(long, value_enum, default_value = "recursive")]
|
||||
pub scope: IndexScopeArg,
|
||||
|
||||
/// Include hidden files
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub include_hidden: bool,
|
||||
|
||||
/// Persist results to the database instead of in-memory
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub persistent: bool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
@@ -134,6 +293,62 @@ async fn main() -> Result<()> {
|
||||
core.action(&input).await?;
|
||||
println!("Copy request submitted");
|
||||
}
|
||||
Commands::File(FileCommands::Delete(args)) => {
|
||||
let input = args.to_input();
|
||||
if let Err(errors) = input.validate() {
|
||||
anyhow::bail!(errors.join("; "));
|
||||
}
|
||||
core.action(&input).await?;
|
||||
println!("Delete request submitted");
|
||||
}
|
||||
Commands::File(FileCommands::Validate(args)) => {
|
||||
let input = args.to_input();
|
||||
core.action(&input).await?;
|
||||
println!("Validation request submitted");
|
||||
}
|
||||
Commands::File(FileCommands::Dedupe(args)) => {
|
||||
let input = args.to_input();
|
||||
core.action(&input).await?;
|
||||
println!("Duplicate detection request submitted");
|
||||
}
|
||||
Commands::Index(IndexCommands::Start(args)) => {
|
||||
use sd_core::ops::indexing::input::IndexInput;
|
||||
use sd_core::ops::indexing::job::{IndexMode, IndexPersistence, IndexScope};
|
||||
|
||||
let library_id = if let Some(id) = args.library {
|
||||
id
|
||||
} else {
|
||||
// If only one library exists, use it; otherwise require --library
|
||||
let libs: Vec<sd_core::ops::libraries::list::output::LibraryInfo> = core
|
||||
.query(&sd_core::ops::libraries::list::query::ListLibrariesQuery::basic())
|
||||
.await?;
|
||||
match libs.len() {
|
||||
0 => anyhow::bail!("No libraries found; specify --library after creating one"),
|
||||
1 => libs[0].id,
|
||||
_ => anyhow::bail!("Multiple libraries found; please specify --library <UUID>"),
|
||||
}
|
||||
};
|
||||
|
||||
let persistence = if args.persistent {
|
||||
IndexPersistence::Persistent
|
||||
} else {
|
||||
IndexPersistence::Ephemeral
|
||||
};
|
||||
|
||||
let input = IndexInput::new(library_id, args.paths.clone())
|
||||
.with_mode(IndexMode::from(args.mode.clone()))
|
||||
.with_scope(IndexScope::from(args.scope.clone()))
|
||||
.with_include_hidden(args.include_hidden)
|
||||
.with_persistence(persistence);
|
||||
|
||||
// Validate input
|
||||
if let Err(errors) = input.validate() {
|
||||
anyhow::bail!(errors.join("; "));
|
||||
}
|
||||
|
||||
core.action(&input).await?;
|
||||
println!("Indexing request submitted");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user