1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use clap::{Parser, ValueEnum};
use std::{
fmt::Display,
num::{NonZeroU32, NonZeroU64, NonZeroUsize},
};
/// EveryGarf Comic Downloader
///
/// Concurrently download every Garfield comic to date
#[derive(Parser)]
#[command(author, version, about)]
pub struct Args {
/// Folder to download images into
///
/// Leave blank to use 'garfield' folder in user pictures directory (~/Pictures/garfield)
pub folder: Option<String>,
/// Save images in tree structure
///
/// `YYYY/MM/DD.png` instead of `YYYY-MM-DD.png`
#[arg(long)]
pub tree: bool,
/// Maximum number of images to download
///
/// Use `--max 0` to download no images, only check status
#[arg(short, long, default_value = None)]
pub max: Option<usize>,
/// Only download comics published after this date (inclusive)
///
/// Use `--max 1 --start_from <DATE>` to download 1 specific comic
#[arg(short, long, default_value = None)]
pub start_from: Option<chrono::NaiveDate>,
/// Maximum number of concurrent jobs to run
///
/// More jobs = faster, but is bottlenecked by network speed after a point
#[arg(short, long, default_value_t = NonZeroUsize::new(20).unwrap())]
pub jobs: NonZeroUsize,
/// Timeout for url and image requests (seconds)
#[arg(short, long, default_value_t = NonZeroU64::new(5).unwrap())]
pub timeout: NonZeroU64,
/// Timeout for proxy ping and cache fetch (seconds)
#[arg(short = 'T', long, default_value_t = NonZeroU64::new(10).unwrap())]
pub initial_timeout: NonZeroU64,
/// Amount of fetch attempts allowed per thread, before hard error
#[arg(short, long, default_value_t = NonZeroU32::new(10).unwrap())]
pub attempts: NonZeroU32,
/// Send desktop notifications on error
///
/// Useful when running in background
#[arg(short, long)]
pub notify_on_fail: bool,
/// Remove existing files / clean save folder (not recommended)
///
/// Contributes to 'elapsed time'
#[arg(long)]
pub remove_all: bool,
/// Url of custom proxy service
///
/// See [https://github.com/dxrcy/everygarf#proxy-service] for more information
#[arg(long, conflicts_with = "no_proxy", default_value = everygarf::PROXY_DEFAULT)]
pub proxy: String,
/// Do not use a proxy service (not recommended)
///
/// See [https://github.com/dxrcy/everygarf#proxy-service] for more information
#[arg(long, conflicts_with = "proxy")]
pub no_proxy: bool,
/// Always ping proxy service, even when downloading few images
#[arg(long)]
pub always_ping: bool,
/// Source for image URLs
#[arg(long, requires = "no_cache", default_value_t = everygarf::api::Source::default())]
pub source: everygarf::api::Source,
/// Specify cache file to read from
///
/// Disable cache with `no-cache`
#[arg(short, long, default_value = everygarf::CACHE_DEFAULT, conflicts_with = "source")]
pub cache: String,
/// Do not read remote or local cache file
#[arg(long, conflicts_with = "cache")]
pub no_cache: bool,
/// Save image URLs to local cache file
///
/// Use cache file with `--cache <FILE>`
///
/// Without `--no-cache`, this reuses the existing cached URLs
#[arg(long)]
pub save_cache: Option<String>,
/// Image format (file extension) to save images as
///
/// Format is ignored when files are checked for missing images, so no two images will have the
/// same date, even if they have different file extensions
#[arg(short, long, ignore_case = true, default_value_t = Default::default())]
pub format: ImageFormat,
/// Returns exit code 10 if images are missing
///
/// Does not print anything to stdout
#[arg(short, long)]
pub query: bool,
}
/// File extension to save images as
#[derive(Default, Clone, Copy, ValueEnum)]
pub enum ImageFormat {
#[default]
Png,
Jpg,
Gif,
}
impl Display for ImageFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_possible_value().unwrap().get_name())
}
}