Skip to content

Commit 051264e

Browse files
committed
qtorrent: add option to include images
1 parent 0b8810c commit 051264e

10 files changed

Lines changed: 300 additions & 48 deletions

File tree

Cargo.lock

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cli-tools"
3-
version = "9.9.0"
3+
version = "9.10.0"
44
edition = "2024"
55
authors = ["Esgrove <esgrove@outlook.com>"]
66
description = "A collection of CLI utilities"

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,13 +432,15 @@ Options:
432432
-t, --tags <TAGS> Tags for the torrent (comma-separated)
433433
-a, --paused Add torrent in paused state
434434
-p, --dryrun Print what would be done without actually adding torrents
435-
-o, --offline Offline mode: skip qBittorrent connection entirely (implies --dryrun)
435+
-o, --offline Offline mode: skip qBittorrent connection entirely (implies dryrun)
436436
-y, --yes Skip confirmation prompts
437-
-e, --skip-ext <EXT> File extensions to skip (e.g., nfo, txt, jpg)
437+
-e, --skip-ext <EXT> File extensions to skip (e.g., nfo, txt)
438438
-k, --skip-dir <NAME> Directory names to skip (case-insensitive full name match)
439439
-m, --min-size <MB> Minimum file size in MB (files smaller than this will be skipped)
440+
-i, --include-images Include image files (.jpg, .jpeg, .png)
441+
-M, --min-image-size <KB> Minimum image file size in KB
440442
-r, --recurse Recurse into subdirectories when searching for torrent files
441-
-x, --skip-existing Skip rename prompts for existing/duplicate torrents
443+
-x, --skip-existing Skip rename prompts for existing torrents
442444
-v, --verbose Print verbose output
443445
-h, --help Print help (see more with '--help')
444446
-V, --version Print version

cli-tools.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,15 +307,22 @@ replace = [
307307
# Skip rename prompts for existing/duplicate torrents
308308
# skip_existing = false
309309

310+
# Include image files (.jpg, .jpeg, .png) in multi-file torrents
311+
# include_images = false
312+
310313
# File extensions to skip when selecting files from multi-file torrents (without dot)
311-
# skip_extensions = ["nfo", "txt", "jpg", "png", "sfv", "md5", "url"]
314+
# When include_images is false, keeping jpg/jpeg/png here matches the default behavior.
315+
# skip_extensions = ["nfo", "txt", "jpg", "jpeg", "png", "sfv", "md5", "url"]
312316

313317
# Directory names to skip in multi-file torrents (case-insensitive full name match)
314318
# skip_directories = ["sample", "proof", "screens"]
315319

316-
# Minimum file size in MB (files smaller than this will be skipped)
320+
# Minimum file size in MB for non-image files (0 disables size filtering)
317321
# min_file_size_mb = 10
318322

323+
# Minimum image file size in KB when include_images is true (0 disables image size filtering)
324+
# min_image_size_kb = 0
325+
319326
# Substrings to remove from torrent filename when generating suggested name
320327
# remove_from_name = ["[Example]", "Dummy"]
321328

src/bin/qtorrent/add.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,15 @@ impl QTorrent {
689689

690690
if !self.config.file_filter.is_empty() {
691691
println!("{}", "File filters:".bold());
692+
println!(
693+
" {} {}",
694+
"Include images:".dimmed(),
695+
if self.config.file_filter.include_images {
696+
"yes".green()
697+
} else {
698+
"no".yellow()
699+
}
700+
);
692701
if !self.config.file_filter.skip_extensions.is_empty() {
693702
println!(
694703
" {} {}",
@@ -706,6 +715,11 @@ impl QTorrent {
706715
if let Some(min_size_mb) = self.config.file_filter.min_size_mb {
707716
println!(" {} {} MB", "Min file size:".dimmed(), min_size_mb);
708717
}
718+
if self.config.file_filter.include_images
719+
&& let Some(min_image_size_kb) = self.config.file_filter.min_image_size_kb
720+
{
721+
println!(" {} {} KB", "Min image size:".dimmed(), min_image_size_kb);
722+
}
709723
}
710724
}
711725

src/bin/qtorrent/config.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ pub struct QtorrentConfig {
7373
/// Minimum file size in MB. Files smaller than this will be skipped.
7474
#[serde(default)]
7575
min_file_size_mb: Option<f64>,
76+
/// Include image files (.jpg, .jpeg, .png) in multi-file torrents.
77+
#[serde(default)]
78+
include_images: bool,
79+
/// Minimum image file size in KB. Files smaller than this will be skipped.
80+
#[serde(default)]
81+
min_image_size_kb: Option<f64>,
7682
/// Substrings to remove from torrent filename when generating suggested name.
7783
#[serde(default)]
7884
remove_from_name: Vec<String>,
@@ -190,6 +196,7 @@ impl Config {
190196
///
191197
/// # Errors
192198
/// Returns an error if the config file cannot be read or parsed.
199+
#[allow(clippy::too_many_lines)]
193200
pub fn from_args(args: QtorrentArgs) -> Result<Self> {
194201
let user_config = QtorrentConfig::get_user_config()?;
195202

@@ -248,13 +255,26 @@ impl Config {
248255
.collect()
249256
};
250257

258+
let include_images = args.include_images || user_config.include_images;
259+
251260
// Convert MB to bytes for easier comparison
252261
let min_file_size_bytes = args
253262
.min_file_size_mb
254263
.or(user_config.min_file_size_mb)
255-
.map(|mb| (mb * 1024.0 * 1024.0) as u64);
256-
257-
let file_filter = FileFilter::new(skip_extensions, skip_directories, min_file_size_bytes);
264+
.and_then(mb_to_bytes);
265+
266+
let min_image_size_bytes = args
267+
.min_image_size_kb
268+
.or(user_config.min_image_size_kb)
269+
.and_then(kb_to_bytes);
270+
271+
let file_filter = FileFilter::new(
272+
skip_extensions,
273+
skip_directories,
274+
min_file_size_bytes,
275+
include_images,
276+
min_image_size_bytes,
277+
);
258278

259279
// Substrings to remove from suggested name
260280
let remove_from_name = user_config.remove_from_name;
@@ -413,6 +433,14 @@ impl Config {
413433
}
414434
}
415435

436+
fn mb_to_bytes(mb: f64) -> Option<u64> {
437+
(mb > 0.0).then_some((mb * 1024.0 * 1024.0) as u64)
438+
}
439+
440+
fn kb_to_bytes(kb: f64) -> Option<u64> {
441+
(kb > 0.0).then_some((kb * 1024.0) as u64)
442+
}
443+
416444
#[cfg(test)]
417445
mod qtorrent_config_tests {
418446
use super::*;
@@ -476,11 +504,15 @@ tags = "hd,new"
476504
skip_extensions = ["nfo", "txt", "jpg"]
477505
skip_directories = ["sample", "subs"]
478506
min_file_size_mb = 50.0
507+
include_images = true
508+
min_image_size_kb = 2.5
479509
"#;
480510
let config = QtorrentConfig::from_toml_str(toml).expect("should parse config");
481511
assert_eq!(config.skip_extensions, vec!["nfo", "txt", "jpg"]);
482512
assert_eq!(config.skip_directories, vec!["sample", "subs"]);
483513
assert_eq!(config.min_file_size_mb, Some(50.0));
514+
assert!(config.include_images);
515+
assert_eq!(config.min_image_size_kb, Some(2.5));
484516
}
485517

486518
#[test]

src/bin/qtorrent/main.rs

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@ pub struct QtorrentArgs {
7979
#[arg(short = 'p', long)]
8080
pub dryrun: bool,
8181

82-
/// Offline mode: skip qBittorrent connection entirely (implies --dryrun)
82+
/// Offline mode: skip qBittorrent connection entirely (implies dryrun)
8383
#[arg(short = 'o', long)]
8484
pub offline: bool,
8585

8686
/// Skip confirmation prompts
8787
#[arg(short = 'y', long)]
8888
yes: bool,
8989

90-
/// File extensions to skip (e.g., nfo, txt, jpg)
90+
/// File extensions to skip (e.g., nfo, txt)
9191
#[arg(short = 'e', long = "skip-ext", name = "EXT", value_delimiter = ',')]
9292
skip_extensions: Vec<String>,
9393

@@ -99,11 +99,19 @@ pub struct QtorrentArgs {
9999
#[arg(short = 'm', long = "min-size", name = "MB")]
100100
min_file_size_mb: Option<f64>,
101101

102+
/// Include image files (.jpg, .jpeg, .png)
103+
#[arg(short = 'i', long)]
104+
include_images: bool,
105+
106+
/// Minimum image file size in KB
107+
#[arg(short = 'M', long = "min-image-size", name = "KB")]
108+
min_image_size_kb: Option<f64>,
109+
102110
/// Recurse into subdirectories when searching for torrent files
103111
#[arg(short = 'r', long)]
104112
pub recurse: bool,
105113

106-
/// Skip rename prompts for existing/duplicate torrents
114+
/// Skip rename prompts for existing torrents
107115
#[arg(short = 'x', long)]
108116
pub skip_existing: bool,
109117

@@ -226,6 +234,30 @@ mod cli_args_tests {
226234
assert_eq!(args.min_file_size_mb, Some(50.5));
227235
}
228236

237+
#[test]
238+
fn parses_include_images() {
239+
let args = QtorrentArgs::try_parse_from(["test", "--include-images"]).expect("should parse");
240+
assert!(args.include_images);
241+
}
242+
243+
#[test]
244+
fn parses_include_images_short_flag() {
245+
let args = QtorrentArgs::try_parse_from(["test", "-i"]).expect("should parse");
246+
assert!(args.include_images);
247+
}
248+
249+
#[test]
250+
fn parses_min_image_size() {
251+
let args = QtorrentArgs::try_parse_from(["test", "--min-image-size", "3.5"]).expect("should parse");
252+
assert_eq!(args.min_image_size_kb, Some(3.5));
253+
}
254+
255+
#[test]
256+
fn parses_min_image_size_short_flag() {
257+
let args = QtorrentArgs::try_parse_from(["test", "-M", "3.5"]).expect("should parse");
258+
assert_eq!(args.min_image_size_kb, Some(3.5));
259+
}
260+
229261
#[test]
230262
fn parses_combined_boolean_flags() {
231263
let args = QtorrentArgs::try_parse_from(["test", "-apyrxvo"]).expect("should parse");
@@ -289,6 +321,8 @@ mod cli_args_tests {
289321
assert!(args.skip_extensions.is_empty());
290322
assert!(args.skip_directories.is_empty());
291323
assert!(args.min_file_size_mb.is_none());
324+
assert!(!args.include_images);
325+
assert!(args.min_image_size_kb.is_none());
292326
assert!(!args.paused);
293327
assert!(!args.dryrun);
294328
assert!(!args.offline);
@@ -332,6 +366,9 @@ mod cli_args_tests {
332366
"sample",
333367
"-m",
334368
"50",
369+
"--include-images",
370+
"--min-image-size",
371+
"2",
335372
"-a",
336373
"-r",
337374
"-v",
@@ -349,6 +386,8 @@ mod cli_args_tests {
349386
assert_eq!(args.skip_extensions, vec!["nfo", "txt"]);
350387
assert_eq!(args.skip_directories, vec!["sample"]);
351388
assert_eq!(args.min_file_size_mb, Some(50.0));
389+
assert!(args.include_images);
390+
assert_eq!(args.min_image_size_kb, Some(2.0));
352391
assert!(args.paused);
353392
assert!(args.recurse);
354393
assert!(args.verbose);
@@ -524,6 +563,32 @@ mod config_from_args_tests {
524563
assert_eq!(config.file_filter.min_size_bytes, Some(10 * 1024 * 1024));
525564
}
526565

566+
#[test]
567+
fn config_cli_includes_images() {
568+
let args = QtorrentArgs::try_parse_from(["test", "--include-images"]).expect("should parse");
569+
let config = Config::from_args(args).expect("config should parse");
570+
assert!(config.file_filter.include_images);
571+
}
572+
573+
#[test]
574+
fn config_cli_image_min_size_converts_to_bytes() {
575+
let args =
576+
QtorrentArgs::try_parse_from(["test", "--include-images", "--min-image-size", "2"]).expect("should parse");
577+
let config = Config::from_args(args).expect("config should parse");
578+
assert_eq!(config.file_filter.min_image_size_bytes, Some(2 * 1024));
579+
}
580+
581+
#[test]
582+
fn config_zero_sizes_disable_size_filters() {
583+
let args =
584+
QtorrentArgs::try_parse_from(["test", "--include-images", "--min-size", "0", "--min-image-size", "0"])
585+
.expect("should parse");
586+
let config = Config::from_args(args).expect("config should parse");
587+
assert!(config.file_filter.include_images);
588+
assert!(config.file_filter.min_size_bytes.is_none());
589+
assert!(config.file_filter.min_image_size_bytes.is_none());
590+
}
591+
527592
#[test]
528593
fn config_has_credentials_when_cli_sets_them() {
529594
let args = QtorrentArgs::try_parse_from(["test", "-u", "user", "-w", "pass"]).expect("should parse");

0 commit comments

Comments
 (0)