1
0
Fork 0
mirror of synced 2024-05-17 19:03:08 +12:00
czkawka/czkawka_cli/src/commands.rs
2024-03-21 22:14:35 +00:00

702 lines
24 KiB
Rust

use std::path::PathBuf;
use image_hasher::{FilterType, HashAlg};
use czkawka_core::common_dir_traversal::CheckingMethod;
use czkawka_core::common_tool::DeleteMethod;
use czkawka_core::duplicate::HashType;
use czkawka_core::same_music::MusicSimilarity;
use czkawka_core::similar_images::SimilarityPreset;
use czkawka_core::CZKAWKA_VERSION;
#[derive(clap::Parser)]
#[clap(
name = "czkawka",
help_template = HELP_TEMPLATE,
version = CZKAWKA_VERSION
)]
pub struct Args {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Debug, clap::Subcommand)]
pub enum Commands {
#[clap(
name = "dup",
about = "Finds duplicate files",
after_help = "EXAMPLE:\n czkawka dup -d /home/rafal -e /home/rafal/Obrazy -m 25 -x 7z rar IMAGE -s hash -f results.txt -D aeo"
)]
Duplicates(DuplicatesArgs),
#[clap(
name = "empty-folders",
about = "Finds empty folders",
after_help = "EXAMPLE:\n czkawka empty-folders -d /home/rafal/rr /home/gateway -f results.txt"
)]
EmptyFolders(EmptyFoldersArgs),
#[clap(
name = "big",
about = "Finds big files",
after_help = "EXAMPLE:\n czkawka big -d /home/rafal/ /home/piszczal -e /home/rafal/Roman -n 25 -J -x VIDEO -f results.txt"
)]
BiggestFiles(BiggestFilesArgs),
#[clap(
name = "empty-files",
about = "Finds empty files",
after_help = "EXAMPLE:\n czkawka empty-files -d /home/rafal /home/szczekacz -e /home/rafal/Pulpit -R -f results.txt"
)]
EmptyFiles(EmptyFilesArgs),
#[clap(
name = "temp",
about = "Finds temporary files",
after_help = "EXAMPLE:\n czkawka temp -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt -D"
)]
Temporary(TemporaryArgs),
#[clap(
name = "image",
about = "Finds similar images",
after_help = "EXAMPLE:\n czkawka image -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt"
)]
SimilarImages(SimilarImagesArgs),
#[clap(name = "music", about = "Finds same music by tags", after_help = "EXAMPLE:\n czkawka music -d /home/rafal -f results.txt")]
SameMusic(SameMusicArgs),
#[clap(
name = "symlinks",
about = "Finds invalid symlinks",
after_help = "EXAMPLE:\n czkawka symlinks -d /home/kicikici/ /home/szczek -e /home/kicikici/jestempsem -x jpg -f results.txt"
)]
InvalidSymlinks(InvalidSymlinksArgs),
#[clap(
name = "broken",
about = "Finds broken files",
after_help = "EXAMPLE:\n czkawka broken -d /home/kicikici/ /home/szczek -e /home/kicikici/jestempsem -x jpg -f results.txt"
)]
BrokenFiles(BrokenFilesArgs),
#[clap(name = "video", about = "Finds similar video files", after_help = "EXAMPLE:\n czkawka videos -d /home/rafal -f results.txt")]
SimilarVideos(SimilarVideosArgs),
#[clap(
name = "ext",
about = "Finds files with invalid extensions",
after_help = "EXAMPLE:\n czkawka broken -d /home/czokolada/ -f results.txt"
)]
BadExtensions(BadExtensionsArgs),
#[clap(name = "tester", about = "Small utility to test supported speed of ", after_help = "EXAMPLE:\n czkawka tester")]
Tester,
}
#[derive(Debug, clap::Args)]
pub struct DuplicatesArgs {
#[clap(flatten)]
pub common_cli_items: CommonCliItems,
#[clap(
short,
long,
value_parser = parse_minimal_file_size,
default_value = "8192",
help = "Minimum size in bytes",
long_help = "Minimum size of checked files in bytes; assigning bigger values may speed up searching"
)]
pub minimal_file_size: u64,
#[clap(
short = 'i',
long,
value_parser = parse_maximal_file_size,
default_value = "18446744073709551615",
help = "Maximum size in bytes",
long_help = "Maximum size of checked files in bytes; assigning lower values may speed up searching"
)]
pub maximal_file_size: u64,
#[clap(
short = 'c',
long,
value_parser = parse_minimal_file_size,
default_value = "257144",
help = "Minimum cached file size in bytes",
long_help = "Minimum size of cached files in bytes; assigning bigger values may speed up the scan, but loading the cache will be slower; assigning smaller values may slow down the scan and some files may need to be hashed again, but loading the cache will be faster"
)]
pub minimal_cached_file_size: u64,
#[clap(
short,
long,
default_value = "HASH",
value_parser = parse_checking_method_duplicate,
help = "Search method (NAME, SIZE, HASH)",
long_help = "Methods to search files:\nNAME - Fast but but rarely usable;\nSIZE - Fast but not accurate, checking by the file's size;\nHASH - The slowest method, checking by the hash of the entire file."
)]
pub search_method: CheckingMethod,
#[clap(flatten)]
pub delete_method: DMethod,
#[clap(
short = 't',
long,
default_value = "BLAKE3",
value_parser = parse_hash_type,
help = "Hash type (BLAKE3, CRC32, XXH3)"
)]
pub hash_type: HashType,
#[clap(flatten)]
pub case_sensitive_name_comparison: CaseSensitiveNameComparison,
#[clap(flatten)]
pub allow_hard_links: AllowHardLinks,
#[clap(flatten)]
pub dry_run: DryRun,
}
#[derive(Debug, clap::Args)]
pub struct EmptyFoldersArgs {
#[clap(flatten)]
pub common_cli_items: CommonCliItems,
#[clap(short = 'D', long, help = "Delete found folders")]
pub delete_folders: bool,
}
#[derive(Debug, clap::Args)]
pub struct BiggestFilesArgs {
#[clap(flatten)]
pub common_cli_items: CommonCliItems,
#[clap(short, long, default_value = "50", help = "Number of files to be shown")]
pub number_of_files: usize,
#[clap(short = 'D', long, help = "Delete found files")]
pub delete_files: bool,
#[clap(short = 'J', long, help = "Finds the smallest files instead the biggest")]
pub smallest_mode: bool,
}
#[derive(Debug, clap::Args)]
pub struct EmptyFilesArgs {
#[clap(flatten)]
pub common_cli_items: CommonCliItems,
#[clap(short = 'D', long, help = "Delete found files")]
pub delete_files: bool,
}
#[derive(Debug, clap::Args)]
pub struct TemporaryArgs {
#[clap(flatten)]
pub common_cli_items: CommonCliItems,
#[clap(short = 'D', long, help = "Delete found files")]
pub delete_files: bool,
}
#[derive(Debug, clap::Args)]
pub struct SimilarImagesArgs {
#[clap(flatten)]
pub common_cli_items: CommonCliItems,
#[clap(
short,
long,
value_parser = parse_minimal_file_size,
default_value = "16384",
help = "Minimum size in bytes",
long_help = "Minimum size of checked files in bytes; assigning bigger values may speed up searching"
)]
pub minimal_file_size: u64,
#[clap(
short = 'i',
long,
value_parser = parse_minimal_file_size,
default_value = "18446744073709551615",
help = "Maximum size in bytes",
long_help = "Maximum size of checked files in bytes; assigning lower values may speed up searching"
)]
pub maximal_file_size: u64,
#[clap(
short,
long,
default_value = "High",
value_parser = parse_similar_images_similarity,
help = "Similarity level (allowed values: Minimal, VerySmall, Small, Medium, High, VeryHigh)",
long_help = "Methods to choose similarity level of images which will be considered as duplicated.\n (allowed values: Minimal, VerySmall, Small, Medium, High, VeryHigh)"
)]
pub similarity_preset: SimilarityPreset,
#[clap(flatten)]
pub delete_method: DMethod,
#[clap(flatten)]
pub allow_hard_links: AllowHardLinks,
#[clap(flatten)]
pub dry_run: DryRun,
#[clap(
short = 'g',
long,
default_value = "Gradient",
value_parser = parse_similar_hash_algorithm,
help = "Hash algorithm (allowed values: Mean, Gradient, Blockhash, VertGradient, DoubleGradient)"
)]
pub hash_alg: HashAlg,
#[clap(
short = 'z',
long,
default_value = "Nearest",
value_parser = parse_similar_image_filter,
help = "Hash algorithm (allowed values: Lanczos3, Nearest, Triangle, Faussian, Catmullrom)"
)]
pub image_filter: FilterType,
#[clap(
short = 'c',
long,
default_value = "16",
value_parser = parse_image_hash_size,
help = "Hash size (allowed values: 8, 16, 32, 64)"
)]
pub hash_size: u8,
}
#[derive(Debug, clap::Args)]
pub struct SameMusicArgs {
#[clap(flatten)]
pub common_cli_items: CommonCliItems,
#[clap(flatten)]
pub delete_method: DMethod,
#[clap(flatten)]
pub dry_run: DryRun,
#[clap(
short = 'z',
long,
default_value = "track_title,track_artist",
value_parser = parse_music_duplicate_type,
help = "Search method (track_title,track_artist,year,bitrate,genre,length))",
long_help = "Sets which rows must be equal to set these files as duplicates (may be mixed, but must be separated by commas)."
)]
pub music_similarity: MusicSimilarity,
#[clap(
short,
long,
default_value = "TAGS",
value_parser = parse_checking_method_same_music,
help = "Search method (CONTENT, TAGS)",
long_help = "Methods to search files:\nCONTENT - finds similar audio files by content;\nTAGS - finds similar images by tags; needs to set"
)]
pub search_method: CheckingMethod,
#[clap(
short,
long,
value_parser = parse_minimal_file_size,
default_value = "8192",
help = "Minimum size in bytes",
long_help = "Minimum size of checked files in bytes; assigning bigger values may speed up searching"
)]
pub minimal_file_size: u64,
#[clap(
short = 'i',
long,
value_parser = parse_maximal_file_size,
default_value = "18446744073709551615",
help = "Maximum size in bytes",
long_help = "Maximum size of checked files in bytes; assigning lower values may speed up searching"
)]
pub maximal_file_size: u64,
#[clap(
short = 'l',
long,
value_parser = parse_minimum_segment_duration,
default_value = "10.0",
help = "Maximum size in bytes",
long_help = "Minimum segment duration; smaller values will find also shorter similar segments, which may increase false positives"
)]
pub minimum_segment_duration: f32,
#[clap(
short = 'd',
long,
value_parser = parse_maximum_difference,
default_value = "2.0",
help = "Maximum difference between segments",
long_help = "Maximum difference between segments; 0.0 will find only identical segments; 10.0 will find also segments which are almost not similar at all"
)]
pub maximum_difference: f64,
}
fn parse_maximum_difference(src: &str) -> Result<f64, String> {
match src.parse::<f64>() {
Ok(maximum_difference) => {
if maximum_difference <= 0.0 {
Err("Maximum difference must be bigger than 0".to_string())
} else if maximum_difference >= 10.0 {
Err("Maximum difference must be smaller than 10.0".to_string())
} else {
Ok(maximum_difference)
}
}
Err(e) => Err(e.to_string()),
}
}
fn parse_minimum_segment_duration(src: &str) -> Result<f32, String> {
match src.parse::<f32>() {
Ok(minimum_segment_duration) => {
if minimum_segment_duration <= 0.0 {
Err("Minimum segment duration must be bigger than 0".to_string())
} else if minimum_segment_duration >= 3600.0 {
Err("Minimum segment duration must be smaller than 3600 (greater values not make much sense)".to_string())
} else {
Ok(minimum_segment_duration)
}
}
Err(e) => Err(e.to_string()),
}
}
#[derive(Debug, clap::Args)]
pub struct InvalidSymlinksArgs {
#[clap(flatten)]
pub common_cli_items: CommonCliItems,
#[clap(short = 'D', long, help = "Delete found files")]
pub delete_files: bool,
}
#[derive(Debug, clap::Args)]
pub struct BrokenFilesArgs {
#[clap(flatten)]
pub common_cli_items: CommonCliItems,
#[clap(short = 'D', long, help = "Delete found files")]
pub delete_files: bool,
}
#[derive(Debug, clap::Args)]
pub struct SimilarVideosArgs {
#[clap(flatten)]
pub common_cli_items: CommonCliItems,
#[clap(flatten)]
pub delete_method: DMethod,
#[clap(flatten)]
pub allow_hard_links: AllowHardLinks,
#[clap(flatten)]
pub dry_run: DryRun,
#[clap(
short,
long,
value_parser = parse_minimal_file_size,
default_value = "8192",
help = "Minimum size in bytes",
long_help = "Minimum size of checked files in bytes; assigning bigger values may speed up searching"
)]
pub minimal_file_size: u64,
#[clap(
short = 'i',
long,
value_parser = parse_maximal_file_size,
default_value = "18446744073709551615",
help = "Maximum size in bytes",
long_help = "Maximum size of checked files in bytes; assigning lower values may speed up searching"
)]
pub maximal_file_size: u64,
#[clap(
short = 't',
long,
value_parser = parse_tolerance,
default_value = "10",
help = "Video maximum difference (allowed values: <0,20>)",
long_help = "Maximum difference between video frames; bigger values mean that videos can look more and more different (allowed values: <0,20>)"
)]
pub tolerance: i32,
}
#[derive(Debug, clap::Args)]
pub struct BadExtensionsArgs {
#[clap(flatten)]
pub common_cli_items: CommonCliItems,
}
#[derive(Debug, clap::Args)]
pub struct CommonCliItems {
#[clap(short = 'T', long, default_value = "0", help = "Limits number of threads; 0 (default) will use all available threads")]
pub thread_number: usize,
#[clap(
short,
long,
required = true,
help = "Directory(ies) to search",
long_help = "List of directories which will be searched (absolute path)"
)]
pub directories: Vec<PathBuf>,
#[clap(
short,
long,
help = "Excluded directory(ies)",
long_help = "List of directories which will be excluded from search (absolute path)"
)]
pub excluded_directories: Vec<PathBuf>,
#[clap(
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>,
#[clap(
short = 'x',
long,
help = "Allowed file extension(s)",
long_help = "List of checked files with provided extension(s). There are also helpful macros which allow to easy use a typical extensions like:\nIMAGE(\"jpg,kra,gif,png,bmp,tiff,hdr,svg\"),\nTEXT(\"txt,doc,docx,odt,rtf\"),\nVIDEO(\"mp4,flv,mkv,webm,vob,ogv,gifv,avi,mov,wmv,mpg,m4v,m4p,mpeg,3gp\") or\nMUSIC(\"mp3,flac,ogg,tta,wma,webm\")\n "
)]
pub allowed_extensions: Vec<String>,
#[clap(short = 'P', long, help = "Excluded file extension(s)", long_help = "List of extensions that will be removed from search.\n ")]
pub excluded_extensions: Vec<String>,
#[clap(flatten)]
pub file_to_save: FileToSave,
#[clap(flatten)]
pub json_compact_file_to_save: JsonCompactFileToSave,
#[clap(flatten)]
pub json_pretty_file_to_save: JsonPrettyFileToSave,
#[clap(short = 'R', long, help = "Prevents from recursive check of folders")]
pub not_recursive: bool,
#[cfg(target_family = "unix")]
#[clap(short = 'X', long, help = "Exclude files on other filesystems")]
pub exclude_other_filesystems: bool,
}
#[derive(Debug, clap::Args)]
pub struct DMethod {
#[clap(
short = 'D',
long,
default_value = "NONE",
value_parser = parse_delete_method,
help = "Delete method (AEN, AEO, ON, OO, HARD)",
long_help = "Methods to delete the files:\nAEN - All files except the newest;\nAEO - All files except the oldest;\nON - Only 1 file, the newest;\nOO - Only 1 file, the oldest;\nHARD - create hard link;\nNONE - do not delete files."
)]
pub delete_method: DeleteMethod,
}
#[derive(Debug, clap::Args)]
pub struct FileToSave {
#[clap(short, long, value_name = "file-name", help = "Saves the results into a formatted txt file")]
pub file_to_save: Option<PathBuf>,
}
#[derive(Debug, clap::Args)]
pub struct JsonCompactFileToSave {
#[clap(short = 'C', long, value_name = "json-file-name", help = "Saves the results into a compact json file")]
pub compact_file_to_save: Option<PathBuf>,
}
#[derive(Debug, clap::Args)]
pub struct JsonPrettyFileToSave {
#[clap(short, long, value_name = "pretty-json-file-name", help = "Saves the results into a pretty json file")]
pub pretty_file_to_save: Option<PathBuf>,
}
#[derive(Debug, clap::Args)]
pub struct AllowHardLinks {
#[clap(short = 'L', long, help = "Do not ignore hard links")]
pub allow_hard_links: bool,
}
#[derive(Debug, clap::Args)]
pub struct CaseSensitiveNameComparison {
#[clap(short = 'l', long, help = "Use case sensitive name comparison")]
pub case_sensitive_name_comparison: bool,
}
#[derive(Debug, clap::Args)]
pub struct DryRun {
#[clap(long, help = "Do nothing and print the operation that would happen.")]
pub dry_run: bool,
}
impl FileToSave {
pub fn file_name(&self) -> Option<&str> {
if let Some(file_name) = &self.file_to_save {
return file_name.to_str();
}
None
}
}
impl JsonCompactFileToSave {
pub fn file_name(&self) -> Option<&str> {
if let Some(file_name) = &self.compact_file_to_save {
return file_name.to_str();
}
None
}
}
impl JsonPrettyFileToSave {
pub fn file_name(&self) -> Option<&str> {
if let Some(file_name) = &self.pretty_file_to_save {
return file_name.to_str();
}
None
}
}
fn parse_hash_type(src: &str) -> Result<HashType, &'static str> {
match src.to_ascii_lowercase().as_str() {
"blake3" => Ok(HashType::Blake3),
"crc32" => Ok(HashType::Crc32),
"xxh3" => Ok(HashType::Xxh3),
_ => Err("Couldn't parse the hash type (allowed values: BLAKE3, CRC32, XXH3)"),
}
}
fn parse_tolerance(src: &str) -> Result<i32, &'static str> {
match src.parse::<i32>() {
Ok(t) => {
if (0..=20).contains(&t) {
Ok(t)
} else {
Err("Tolerance should be in range <0,20> (higher and lower similarity)")
}
}
_ => Err("Failed to parse tolerance as i32 value."),
}
}
fn parse_checking_method_duplicate(src: &str) -> Result<CheckingMethod, &'static str> {
match src.to_ascii_lowercase().as_str() {
"name" => Ok(CheckingMethod::Name),
"size" => Ok(CheckingMethod::Size),
"size_name" => Ok(CheckingMethod::SizeName),
"hash" => Ok(CheckingMethod::Hash),
_ => Err("Couldn't parse the search method (allowed values: NAME, SIZE, HASH)"),
}
}
fn parse_checking_method_same_music(src: &str) -> Result<CheckingMethod, &'static str> {
match src.to_ascii_lowercase().as_str() {
"tags" => Ok(CheckingMethod::AudioTags),
"content" => Ok(CheckingMethod::AudioContent),
_ => Err("Couldn't parse the searc method (allowed values: TAGS, CONTENT)"),
}
}
fn parse_delete_method(src: &str) -> Result<DeleteMethod, &'static str> {
match src.to_ascii_lowercase().as_str() {
"none" => Ok(DeleteMethod::None),
"aen" => Ok(DeleteMethod::AllExceptNewest),
"aeo" => Ok(DeleteMethod::AllExceptOldest),
"hard" => Ok(DeleteMethod::HardLink),
"on" => Ok(DeleteMethod::OneNewest),
"oo" => Ok(DeleteMethod::OneOldest),
_ => Err("Couldn't parse the delete method (allowed values: AEN, AEO, ON, OO, HARD)"),
}
}
fn parse_similar_images_similarity(src: &str) -> Result<SimilarityPreset, &'static str> {
match src.to_lowercase().replace('_', "").as_str() {
"minimal" => Ok(SimilarityPreset::Minimal),
"verysmall" => Ok(SimilarityPreset::VerySmall),
"small" => Ok(SimilarityPreset::Small),
"medium" => Ok(SimilarityPreset::Medium),
"high" => Ok(SimilarityPreset::High),
"veryhigh" => Ok(SimilarityPreset::VeryHigh),
_ => Err("Couldn't parse the image similarity preset (allowed values: Minimal, VerySmall, Small, Medium, High, VeryHigh)"),
}
}
fn parse_minimal_file_size(src: &str) -> Result<u64, String> {
match src.parse::<u64>() {
Ok(minimal_file_size) => {
if minimal_file_size > 0 {
Ok(minimal_file_size)
} else {
Err("Minimum file size must be at least 1 byte".to_string())
}
}
Err(e) => Err(e.to_string()),
}
}
fn parse_maximal_file_size(src: &str) -> Result<u64, String> {
match src.parse::<u64>() {
Ok(maximal_file_size) => Ok(maximal_file_size),
Err(e) => Err(e.to_string()),
}
}
fn parse_similar_image_filter(src: &str) -> Result<FilterType, String> {
let filter_type = match src.to_lowercase().as_str() {
"lanczos3" => FilterType::Lanczos3,
"nearest" => FilterType::Nearest,
"triangle" => FilterType::Triangle,
"faussian" => FilterType::Gaussian,
"catmullrom" => FilterType::CatmullRom,
_ => return Err("Couldn't parse the image resize filter (allowed values: Lanczos3, Nearest, Triangle, Faussian, Catmullrom)".to_string()),
};
Ok(filter_type)
}
fn parse_similar_hash_algorithm(src: &str) -> Result<HashAlg, String> {
let algorithm = match src.to_lowercase().as_str() {
"mean" => HashAlg::Mean,
"gradient" => HashAlg::Gradient,
"blockhash" => HashAlg::Blockhash,
"vertgradient" => HashAlg::VertGradient,
"doublegradient" => HashAlg::DoubleGradient,
_ => return Err("Couldn't parse the hash algorithm (allowed values: Mean, Gradient, Blockhash, VertGradient, DoubleGradient)".to_string()),
};
Ok(algorithm)
}
fn parse_image_hash_size(src: &str) -> Result<u8, String> {
let hash_size = match src.to_lowercase().as_str() {
"8" => 8,
"16" => 16,
"32" => 32,
"64" => 64,
_ => return Err("Couldn't parse the image hash size (allowed values: 8, 16, 32, 64)".to_string()),
};
Ok(hash_size)
}
fn parse_music_duplicate_type(src: &str) -> Result<MusicSimilarity, String> {
if src.trim().is_empty() {
return Ok(MusicSimilarity::NONE);
}
let mut similarity: MusicSimilarity = MusicSimilarity::NONE;
let parts: Vec<String> = src.split(',').map(|e| e.to_lowercase().replace('_', "")).collect();
if parts.contains(&"tracktitle".into()) {
similarity |= MusicSimilarity::TRACK_TITLE;
}
if parts.contains(&"trackartist".into()) {
similarity |= MusicSimilarity::TRACK_ARTIST;
}
if parts.contains(&"year".into()) {
similarity |= MusicSimilarity::YEAR;
}
if parts.contains(&"bitrate".into()) {
similarity |= MusicSimilarity::BITRATE;
}
if parts.contains(&"genre".into()) {
similarity |= MusicSimilarity::GENRE;
}
if parts.contains(&"length".into()) {
similarity |= MusicSimilarity::LENGTH;
}
if similarity == MusicSimilarity::NONE {
return Err("Couldn't parse the music search method (allowed values: track_title, track_artist, year, bitrate, genre, length)".to_string());
}
Ok(similarity)
}
const HELP_TEMPLATE: &str = r#"
{bin} {version}
USAGE:
{usage} [SCFLAGS] [SCOPTIONS]
OPTIONS:
{options}
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 7z rar IMAGE -s hash -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
{bin} temp -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt -D
{bin} image -d /home/rafal -e /home/rafal/Pulpit -f results.txt
{bin} music -d /home/rafal -e /home/rafal/Pulpit -z "artist,year, ARTISTALBUM, ALBUM___tiTlE" -f results.txt
{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
{bin} extnp -d /home/mikrut/ -e /home/mikrut/trakt -f results.txt"#;