2020-10-05 10:36:49 +13:00
use czkawka_core ::duplicate ::{ CheckingMethod , DeleteMethod } ;
2020-11-03 09:56:07 +13:00
use czkawka_core ::same_music ::MusicSimilarity ;
2020-11-10 00:55:27 +13:00
use czkawka_core ::similar_images ::Similarity ;
2020-10-05 10:36:49 +13:00
use std ::path ::PathBuf ;
use structopt ::StructOpt ;
2020-11-03 09:56:07 +13:00
2020-10-05 10:36:49 +13:00
#[ derive(Debug, StructOpt) ]
#[ structopt(name = " czkawka " , help_message = HELP_MESSAGE, template = HELP_TEMPLATE) ]
pub enum Commands {
#[ structopt(name = " dup " , about = " Finds duplicate files " , help_message = HELP_MESSAGE, after_help = " EXAMPLE: \n czkawka dup -d /home/rafal -e /home/rafal/Obrazy -m 25 -x 7z rar IMAGE -s hashmb -f results.txt -D aeo " ) ]
Duplicates {
#[ structopt(flatten) ]
directories : Directories ,
#[ structopt(flatten) ]
2020-10-07 21:34:15 +13:00
excluded_directories : ExcludedDirectories ,
2020-10-05 10:36:49 +13:00
#[ structopt(flatten) ]
2020-10-07 21:34:15 +13:00
excluded_items : ExcludedItems ,
#[ structopt(short, long, parse(try_from_str = parse_minimal_file_size), default_value = " 1024 " , help = " Minimum size in bytes " , long_help = " Minimum size of checked files in bytes, assigning bigger value may speed up searching " ) ]
2020-10-15 08:10:27 +13:00
minimal_file_size : u64 ,
2020-10-05 10:36:49 +13:00
#[ structopt(flatten) ]
allowed_extensions : AllowedExtensions ,
2020-10-25 07:44:21 +13:00
#[ structopt(short, long, default_value = " HASH " , parse(try_from_str = parse_checking_method), help = " Search method (NAME, SIZE, HASH, HASHMB) " , long_help = " Methods to search files. \n NAME - Fast but but rarely usable, \n SIZE - Fast but not accurate, checking by the file's size, \n HASHMB - More accurate but slower, checking by the hash of the file's first mebibyte or \n HASH - The slowest method, checking by the hash of the entire file " ) ]
2020-10-05 10:36:49 +13:00
search_method : CheckingMethod ,
2021-02-06 05:59:34 +13:00
#[ structopt(short = " D " , long, default_value = " NONE " , parse(try_from_str = parse_delete_method), help = " Delete method (AEN, AEO, ON, OO, HARD) " , long_help = " Methods to delete the files. \n AEN - All files except the newest, \n AEO - All files except the oldest, \n ON - Only 1 file, the newest, \n OO - Only 1 file, the oldest \n HARD - create hard link \n NONE - not delete files " ) ]
2020-10-05 10:36:49 +13:00
delete_method : DeleteMethod ,
#[ structopt(flatten) ]
file_to_save : FileToSave ,
#[ structopt(flatten) ]
not_recursive : NotRecursive ,
} ,
2020-10-07 21:34:15 +13:00
#[ structopt(name = " empty-folders " , about = " Finds empty folders " , help_message = HELP_MESSAGE, after_help = " EXAMPLE: \n czkawka empty-folders -d /home/rafal/rr /home/gateway -f results.txt " ) ]
2020-10-05 10:36:49 +13:00
EmptyFolders {
#[ structopt(flatten) ]
directories : Directories ,
2020-11-01 07:19:33 +13:00
#[ structopt(flatten) ]
excluded_directories : ExcludedDirectories ,
#[ structopt(flatten) ]
excluded_items : ExcludedItems ,
2020-10-05 10:36:49 +13:00
#[ structopt(short = " D " , long, help = " Delete found folders " ) ]
delete_folders : bool ,
#[ structopt(flatten) ]
file_to_save : FileToSave ,
} ,
#[ structopt(name = " big " , about = " Finds big files " , help_message = HELP_MESSAGE, after_help = " EXAMPLE: \n czkawka big -d /home/rafal/ /home/piszczal -e /home/rafal/Roman -n 25 -x VIDEO -f results.txt " ) ]
BiggestFiles {
#[ structopt(flatten) ]
directories : Directories ,
#[ structopt(flatten) ]
2020-10-07 21:34:15 +13:00
excluded_directories : ExcludedDirectories ,
2020-10-05 10:36:49 +13:00
#[ structopt(flatten) ]
2020-10-07 21:34:15 +13:00
excluded_items : ExcludedItems ,
2020-10-05 10:36:49 +13:00
#[ structopt(flatten) ]
allowed_extensions : AllowedExtensions ,
#[ structopt(short, long, default_value = " 50 " , help = " Number of files to be shown " ) ]
number_of_files : usize ,
2020-11-01 07:19:33 +13:00
#[ structopt(short = " D " , long, help = " Delete found files " ) ]
delete_files : bool ,
2020-10-05 10:36:49 +13:00
#[ structopt(flatten) ]
file_to_save : FileToSave ,
#[ structopt(flatten) ]
not_recursive : NotRecursive ,
} ,
#[ structopt(name = " empty-files " , about = " Finds emtpy files " , help_message = HELP_MESSAGE, after_help = " EXAMPLE: \n czkawka empty-files -d /home/rafal /home/szczekacz -e /home/rafal/Pulpit -R -f results.txt " ) ]
EmptyFiles {
#[ structopt(flatten) ]
directories : Directories ,
#[ structopt(flatten) ]
2020-10-07 21:34:15 +13:00
excluded_directories : ExcludedDirectories ,
2020-10-05 10:36:49 +13:00
#[ structopt(flatten) ]
2020-10-07 21:34:15 +13:00
excluded_items : ExcludedItems ,
2020-10-05 10:36:49 +13:00
#[ structopt(flatten) ]
allowed_extensions : AllowedExtensions ,
#[ structopt(short = " D " , long, help = " Delete found files " ) ]
delete_files : bool ,
#[ structopt(flatten) ]
file_to_save : FileToSave ,
#[ structopt(flatten) ]
not_recursive : NotRecursive ,
} ,
#[ structopt(name = " temp " , about = " Finds temporary files " , help_message = HELP_MESSAGE, after_help = " EXAMPLE: \n czkawka temp -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt -D " ) ]
Temporary {
#[ structopt(flatten) ]
directories : Directories ,
#[ structopt(flatten) ]
2020-10-07 21:34:15 +13:00
excluded_directories : ExcludedDirectories ,
2020-10-05 10:36:49 +13:00
#[ structopt(flatten) ]
2020-10-07 21:34:15 +13:00
excluded_items : ExcludedItems ,
2020-10-05 10:36:49 +13:00
#[ structopt(short = " D " , long, help = " Delete found files " ) ]
delete_files : bool ,
#[ structopt(flatten) ]
file_to_save : FileToSave ,
#[ structopt(flatten) ]
not_recursive : NotRecursive ,
} ,
2020-10-25 07:44:21 +13:00
#[ structopt(name = " image " , about = " Finds similar images " , help_message = HELP_MESSAGE, after_help = " EXAMPLE: \n czkawka image -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt " ) ]
2020-10-15 08:10:27 +13:00
SimilarImages {
#[ structopt(flatten) ]
directories : Directories ,
#[ structopt(flatten) ]
excluded_directories : ExcludedDirectories ,
#[ structopt(short, long, parse(try_from_str = parse_minimal_file_size), default_value = " 16384 " , help = " Minimum size in bytes " , long_help = " Minimum size of checked files in bytes, assigning bigger value may speed up searching " ) ]
minimal_file_size : u64 ,
2020-12-24 09:16:40 +13:00
#[ structopt(short, long, default_value = " High " , parse(try_from_str = parse_similar_images_similarity), help = " Similairty level (Minimal, VerySmall, Small, Medium, High, Very High) " , long_help = " Methods to choose similarity level of images which will be considered as duplicated. " ) ]
2020-11-10 00:55:27 +13:00
similarity : Similarity ,
2020-10-15 08:10:27 +13:00
#[ structopt(flatten) ]
excluded_items : ExcludedItems ,
#[ structopt(flatten) ]
file_to_save : FileToSave ,
#[ structopt(flatten) ]
not_recursive : NotRecursive ,
} ,
2020-10-31 22:29:11 +13:00
#[ structopt(name = " zeroed " , about = " Finds zeroed files " , help_message = HELP_MESSAGE, after_help = " EXAMPLE: \n czkawka zeroed -d /home/rafal -e /home/rafal/Pulpit -f results.txt " ) ]
ZeroedFiles {
#[ structopt(flatten) ]
directories : Directories ,
#[ structopt(flatten) ]
excluded_directories : ExcludedDirectories ,
#[ structopt(flatten) ]
excluded_items : ExcludedItems ,
#[ structopt(flatten) ]
allowed_extensions : AllowedExtensions ,
#[ structopt(short = " D " , long, help = " Delete found files " ) ]
delete_files : bool ,
#[ structopt(flatten) ]
file_to_save : FileToSave ,
#[ structopt(flatten) ]
not_recursive : NotRecursive ,
#[ structopt(short, long, parse(try_from_str = parse_minimal_file_size), default_value = " 1024 " , help = " Minimum size in bytes " , long_help = " Minimum size of checked files in bytes, assigning bigger value may speed up searching " ) ]
minimal_file_size : u64 ,
} ,
2020-11-03 09:56:07 +13:00
#[ structopt(name = " music " , about = " Finds same music by tags " , help_message = HELP_MESSAGE, after_help = " EXAMPLE: \n czkawka music -d /home/rafal -f results.txt " ) ]
SameMusic {
#[ structopt(flatten) ]
directories : Directories ,
#[ structopt(flatten) ]
excluded_directories : ExcludedDirectories ,
#[ structopt(flatten) ]
excluded_items : ExcludedItems ,
// #[structopt(short = "D", long, help = "Delete found files")]
// delete_files: bool, TODO
#[ structopt(short = " z " , long, default_value = " artist,title " , parse(try_from_str = parse_music_duplicate_type), help = " Search method (title, artist, album_title, album_artist, year) " , long_help = " Sets which rows must be equal to set this files as duplicates(may be mixed, but must be divided by commas). " ) ]
music_similarity : MusicSimilarity ,
#[ structopt(flatten) ]
file_to_save : FileToSave ,
#[ structopt(flatten) ]
not_recursive : NotRecursive ,
#[ structopt(short, long, parse(try_from_str = parse_minimal_file_size), default_value = " 1024 " , help = " Minimum size in bytes " , long_help = " Minimum size of checked files in bytes, assigning bigger value may speed up searching " ) ]
minimal_file_size : u64 ,
} ,
2020-12-22 04:09:39 +13:00
#[ structopt(name = " symlinks " , about = " Finds invalid symlinks " , help_message = HELP_MESSAGE, after_help = " EXAMPLE: \n czkawka symlinks -d /home/kicikici/ /home/szczek -e /home/kicikici/jestempsem -x jpg -f results.txt " ) ]
InvalidSymlinks {
#[ structopt(flatten) ]
directories : Directories ,
#[ structopt(flatten) ]
excluded_directories : ExcludedDirectories ,
#[ structopt(flatten) ]
excluded_items : ExcludedItems ,
#[ structopt(flatten) ]
allowed_extensions : AllowedExtensions ,
#[ structopt(short = " D " , long, help = " Delete found files " ) ]
delete_files : bool ,
#[ structopt(flatten) ]
file_to_save : FileToSave ,
#[ structopt(flatten) ]
not_recursive : NotRecursive ,
} ,
2021-01-13 08:06:12 +13:00
#[ structopt(name = " broken " , about = " Finds broken files " , help_message = HELP_MESSAGE, after_help = " EXAMPLE: \n czkawka broken -d /home/kicikici/ /home/szczek -e /home/kicikici/jestempsem -x jpg -f results.txt " ) ]
BrokenFiles {
#[ structopt(flatten) ]
directories : Directories ,
#[ structopt(flatten) ]
excluded_directories : ExcludedDirectories ,
#[ structopt(flatten) ]
excluded_items : ExcludedItems ,
#[ structopt(flatten) ]
allowed_extensions : AllowedExtensions ,
#[ structopt(short = " D " , long, help = " Delete found files " ) ]
delete_files : bool ,
#[ structopt(flatten) ]
file_to_save : FileToSave ,
#[ structopt(flatten) ]
not_recursive : NotRecursive ,
} ,
2020-10-05 10:36:49 +13:00
}
#[ derive(Debug, StructOpt) ]
pub struct Directories {
#[ structopt(short, long, parse(from_os_str), required = true, help = " Directorie(s) to search " , long_help = " List of directorie(s) which will be searched(absolute path) " ) ]
pub directories : Vec < PathBuf > ,
}
#[ derive(Debug, StructOpt) ]
2020-10-07 21:34:15 +13:00
pub struct ExcludedDirectories {
#[ structopt(short, long, parse(from_os_str), help = " Excluded directorie(s) " , long_help = " List of directorie(s) which will be excluded from search(absolute path) " ) ]
2020-10-05 10:36:49 +13:00
pub excluded_directories : Vec < PathBuf > ,
}
#[ derive(Debug, StructOpt) ]
2020-10-07 21:34:15 +13:00
pub struct ExcludedItems {
2021-01-11 08:44:10 +13:00
#[ structopt(short = " E " , long, help = " Excluded item(s) " , long_help = " List of excluded item(s) which contains * wildcard(may be slow, so use -e where possible) " ) ]
pub excluded_items : Vec < String > ,
2020-10-05 10:36:49 +13:00
}
#[ derive(Debug, StructOpt) ]
pub struct AllowedExtensions {
#[ structopt(
short = " x " ,
long ,
help = " Allowed file extension(s) " ,
2021-01-14 04:03:05 +13:00
long_help = " List of checked files with provided extension(s). There are also helpful macros which allow to easy use a typical extensions like: \n IMAGE( \" jpg,kra,gif,png,bmp,tiff,hdr,svg \" ), \n TEXT( \" txt,doc,docx,odt,rtf \" ), \n VIDEO( \" mp4,flv,mkv,webm,vob,ogv,gifv,avi,mov,wmv,mpg,m4v,m4p,mpeg,3gp \" ) or \n MUSIC( \" mp3,flac,ogg,tta,wma,webm \" ) \n "
2020-10-05 10:36:49 +13:00
) ]
pub allowed_extensions : Vec < String > ,
}
#[ derive(Debug, StructOpt) ]
pub struct NotRecursive {
#[ structopt(short = " R " , long, help = " Prevents from recursive check of folders " ) ]
pub not_recursive : bool ,
}
#[ derive(Debug, StructOpt) ]
pub struct FileToSave {
#[ structopt(short, long, value_name = " file-name " , help = " Saves the results into the file " ) ]
pub file_to_save : Option < PathBuf > ,
}
impl FileToSave {
pub fn file_name ( & self ) -> Option < & str > {
if let Some ( file_name ) = & self . file_to_save {
return file_name . to_str ( ) ;
}
None
}
}
fn parse_checking_method ( src : & str ) -> Result < CheckingMethod , & 'static str > {
match src . to_ascii_lowercase ( ) . as_str ( ) {
2020-10-25 07:44:21 +13:00
" name " = > Ok ( CheckingMethod ::Name ) ,
2020-10-05 10:36:49 +13:00
" size " = > Ok ( CheckingMethod ::Size ) ,
" hash " = > Ok ( CheckingMethod ::Hash ) ,
" hashmb " = > Ok ( CheckingMethod ::HashMB ) ,
2020-10-25 07:44:21 +13:00
_ = > Err ( " Couldn't parse the search method (allowed: NAME, SIZE, HASH, HASHMB) " ) ,
2020-10-05 10:36:49 +13:00
}
}
fn parse_delete_method ( src : & str ) -> Result < DeleteMethod , & 'static str > {
match src . to_ascii_lowercase ( ) . as_str ( ) {
2020-10-13 02:25:32 +13:00
" none " = > Ok ( DeleteMethod ::None ) ,
2020-10-05 10:36:49 +13:00
" aen " = > Ok ( DeleteMethod ::AllExceptNewest ) ,
" aeo " = > Ok ( DeleteMethod ::AllExceptOldest ) ,
2021-02-06 05:59:34 +13:00
" hard " = > Ok ( DeleteMethod ::HardLink ) ,
2020-10-05 10:36:49 +13:00
" on " = > Ok ( DeleteMethod ::OneNewest ) ,
" oo " = > Ok ( DeleteMethod ::OneOldest ) ,
2021-02-06 05:59:34 +13:00
_ = > Err ( " Couldn't parse the delete method (allowed: AEN, AEO, ON, OO, HARD) " ) ,
2020-10-05 10:36:49 +13:00
}
}
2020-11-10 00:55:27 +13:00
fn parse_similar_images_similarity ( src : & str ) -> Result < Similarity , & 'static str > {
match src . to_ascii_lowercase ( ) . replace ( '_' , " " ) . as_str ( ) {
2020-12-24 09:16:40 +13:00
" minimal " = > Ok ( Similarity ::Minimal ) ,
2020-11-10 00:55:27 +13:00
" verysmall " = > Ok ( Similarity ::VerySmall ) ,
" small " = > Ok ( Similarity ::Small ) ,
" medium " = > Ok ( Similarity ::Medium ) ,
" high " = > Ok ( Similarity ::High ) ,
" veryhigh " = > Ok ( Similarity ::VeryHigh ) ,
_ = > Err ( " Couldn't parse the delete method (allowed: verysmall, small, medium, high, veryhigh) " ) ,
}
}
2020-10-07 21:34:15 +13:00
fn parse_minimal_file_size ( src : & str ) -> Result < u64 , String > {
2020-10-05 10:36:49 +13:00
match src . parse ::< u64 > ( ) {
2020-10-15 08:10:27 +13:00
Ok ( minimal_file_size ) = > {
if minimal_file_size > 0 {
Ok ( minimal_file_size )
2020-10-05 10:36:49 +13:00
} else {
Err ( " Minimum file size must be at least 1 byte " . to_string ( ) )
}
}
Err ( e ) = > Err ( e . to_string ( ) ) ,
}
}
2020-11-03 09:56:07 +13:00
fn parse_music_duplicate_type ( src : & str ) -> Result < MusicSimilarity , String > {
if src . is_empty ( ) {
return Ok ( MusicSimilarity ::NONE ) ;
}
let mut similarity : MusicSimilarity = MusicSimilarity ::NONE ;
let parts : Vec < & str > = src . split ( ',' ) . collect ( ) ;
if parts . iter ( ) . any ( | e | e . to_lowercase ( ) . contains ( " title " ) & & ! e . to_lowercase ( ) . contains ( " album " ) ) {
similarity | = MusicSimilarity ::TITLE ;
}
if parts . iter ( ) . any ( | e | e . to_lowercase ( ) . contains ( " artist " ) & & ! e . to_lowercase ( ) . contains ( " album " ) ) {
similarity | = MusicSimilarity ::ARTIST ;
}
if parts . iter ( ) . any ( | e | e . to_lowercase ( ) . contains ( " title " ) & & e . to_lowercase ( ) . contains ( " album " ) ) {
similarity | = MusicSimilarity ::ALBUM_TITLE ;
}
if parts . iter ( ) . any ( | e | e . to_lowercase ( ) . contains ( " artist " ) & & e . to_lowercase ( ) . contains ( " album " ) ) {
similarity | = MusicSimilarity ::ALBUM_ARTIST ;
}
if parts . iter ( ) . any ( | e | e . to_lowercase ( ) . contains ( " year " ) ) {
similarity | = MusicSimilarity ::YEAR ;
}
if similarity = = MusicSimilarity ::NONE {
return Err ( " Couldn't parse the music search method (allowed: title,artist,album_title,album_artist,year) " . to_string ( ) ) ;
}
Ok ( similarity )
}
2020-10-05 10:36:49 +13:00
static HELP_MESSAGE : & str = " Prints help information (--help will give more information) " ;
const HELP_TEMPLATE : & str = r #"
{ bin } { version }
USAGE :
{ usage } [ SCFLAGS ] [ SCOPTIONS ]
FLAGS :
{ flags }
SUBCOMMANDS :
{ subcommands }
try " {usage} -h " to get more info about a specific tool
EXAMPLES :
{ bin } dup - d / home / rafal - e / home / rafal / Obrazy - m 25 - x 7 z rar IMAGE - s hashmb - f results . txt - D aeo
{ bin } empty - folders - d / home / rafal / rr / home / gateway - f results . txt
{ bin } big - d / home / rafal / / home / piszczal - e / home / rafal / Roman - n 25 - x VIDEO - f results . txt
{ bin } empty - files - d / home / rafal / home / szczekacz - e / home / rafal / Pulpit - R - f results . txt
2020-10-25 07:44:21 +13:00
{ bin } temp - d / home / rafal / - E * / . git * / tmp * * Pulpit - f results . txt - D
2020-10-31 22:29:11 +13:00
{ bin } image - d / home / rafal - e / home / rafal / Pulpit - f results . txt
2020-11-03 09:56:07 +13:00
{ bin } zeroed - d / home / rafal - e / home / krzak - f results . txt "
2020-12-22 04:09:39 +13:00
{ bin } music - d / home / rafal - e / home / rafal / Pulpit - z " artist,year, ARTISTALBUM, ALBUM___tiTlE " - f results . txt
2021-01-13 08:06:12 +13:00
{ bin } symlinks - d / home / kicikici / / home / szczek - e / home / kicikici / jestempsem - x jpg - f results . txt
{ bin } broken - d / home / mikrut / - e / home / mikrut / trakt - f results . txt " #;