blkar_lib/
cli_encode.rs

1use crate::cli_utils::*;
2use crate::encode_core;
3use crate::encode_core::Param;
4use crate::file_utils;
5use crate::json_printer::BracketType;
6use crate::misc_utils;
7use crate::multihash;
8use crate::rand_utils;
9use crate::sbx_specs::{
10    ver_to_block_size, ver_to_data_size, ver_to_usize, ver_uses_rs, SBX_FILE_UID_LEN,
11};
12use crate::time_utils;
13use clap::*;
14use std::str::FromStr;
15use std::time::UNIX_EPOCH;
16
17pub fn sub_command<'a, 'b>() -> App<'a, 'b> {
18    SubCommand::with_name("encode")
19        .about("Encode file")
20        .arg(
21            in_file_arg()
22                .help("File to encode. Supply - to use stdin as input. Use ./- for files named -."),
23        )
24        .arg(out_arg().help(
25            "SBX container name (defaults to INFILE.sbx or INFILE.ecsbx). If OUT is a
26directory, then the container is stored as OUT/INFILE.sbx or
27OUT/INFILE.ecsbx (only the file part of INFILE is used).",
28        ))
29        .arg(force_arg().help("Force overwrite even if OUT exists"))
30        .arg(
31            Arg::with_name("hash_type")
32                .value_name("HASH-TYPE")
33                .long("hash")
34                .takes_value(true)
35                .help(
36                    "Hash function to use, one of (case-insensitive) :
37          sha1
38(default) sha256
39          sha512
40          blake2b-256
41          blake2b-512
42          blake2s-128
43          blake2s-256",
44                ),
45        )
46        .arg(Arg::with_name("no_meta").long("no-meta").help(
47            "Skip metadata block in the SBX container. Metadata block is
48never skipped for version 17, 18, 19.
49This means this option has no effect for version 17, 18, 19.",
50        ))
51        .arg(pr_verbosity_level_arg())
52        .arg(sbx_version_arg())
53        .arg(only_pick_uid_arg().long("uid").help(
54            "Alternative file UID in hex (by default UID is randomly generated).
55UID must be exactly 6 bytes (12 hex digits) in length.",
56        ))
57        .arg(rs_data_arg())
58        .arg(rs_parity_arg())
59        .arg(from_byte_arg().help(FROM_BYTE_ARG_HELP_MSG_RAW_UNALIGNED))
60        .arg(to_byte_inc_arg())
61        .arg(to_byte_exc_arg())
62        .arg(burst_arg().help(
63            "Burst error resistance level. Note that blkar only guesses up to
641000 in repair, show, and sort mode. If you use level above 1000,
65then blkar will make an incorrect guess, and you will need to
66specify it explicitly in repair and sort mode. Show mode does
67not rely on burst level, but provides an option for enabling
68automatic guessing.",
69        ))
70        .arg(
71            Arg::with_name("info_only")
72                .long("info-only")
73                .help("Only display information about encoding then exit"),
74        )
75        .arg(json_arg())
76}
77
78pub fn encode<'a>(matches: &ArgMatches<'a>) -> i32 {
79    let json_printer = get_json_printer!(matches);
80
81    json_printer.print_open_bracket(None, BracketType::Curly);
82
83    // compute uid
84    let mut uid: [u8; SBX_FILE_UID_LEN] = [0; SBX_FILE_UID_LEN];
85    {
86        match matches.value_of("uid") {
87            None => {
88                rand_utils::fill_random_bytes(&mut uid);
89            }
90            Some(x) => {
91                parse_uid!(uid, x, json_printer);
92            }
93        }
94    }
95
96    let (version, data_par_burst) = get_ver_and_data_par_burst_w_defaults!(matches, json_printer);
97
98    let out_extension = if ver_uses_rs(version) { "ecsbx" } else { "sbx" };
99
100    let in_file = get_in_file!(accept_stdin matches, json_printer);
101
102    let out = match matches.value_of("out") {
103        None => {
104            if file_utils::check_if_file_is_stdin(in_file) {
105                exit_with_msg!(usr json_printer => "Explicit output file name is required when input is stdin");
106            } else {
107                format!("{}.{}", in_file, out_extension)
108            }
109        }
110        Some(x) => {
111            if file_utils::check_if_file_is_dir(x) {
112                if file_utils::check_if_file_is_stdin(in_file) {
113                    exit_with_msg!(usr json_printer => "Explicit output file name is required when input is stdin");
114                }
115
116                let in_file = file_utils::get_file_name_part_of_path(in_file).unwrap();
117                misc_utils::make_path(&[x, &format!("{}.{}", in_file, out_extension)])
118            } else {
119                String::from(x)
120            }
121        }
122    };
123
124    let hash_type = match matches.value_of("hash_type") {
125        None => multihash::HashType::SHA256,
126        Some(x) => match multihash::string_to_hash_type(x) {
127            Ok(x) => x,
128            Err(_) => exit_with_msg!(usr json_printer => "Invalid hash type"),
129        },
130    };
131
132    let pr_verbosity_level = get_pr_verbosity_level!(matches, json_printer);
133
134    let meta_enabled = get_meta_enabled!(matches);
135
136    let from_pos = get_from_pos!(matches, json_printer);
137    let to_pos = get_to_pos!(matches, json_printer);
138
139    if matches.is_present("info_only") {
140        json_printer.print_open_bracket(Some("stats"), BracketType::Curly);
141
142        if file_utils::check_if_file_is_stdin(in_file) {
143            exit_with_msg!(usr json_printer => "No information is available for stdin input");
144        }
145
146        let in_file_meta = match file_utils::get_file_metadata(in_file) {
147            Ok(x) => x,
148            Err(_) => exit_with_msg!(usr json_printer => "Failed to get metadata of \"{}\"",
149                                     in_file),
150        };
151
152        let in_file_size = match file_utils::get_file_size(in_file) {
153            Ok(x) => x,
154            Err(_) => exit_with_msg!(usr json_printer => "Failed to get file size of \"{}\"",
155                                     in_file),
156        };
157
158        let in_file_mod_time = match in_file_meta.modified() {
159            Ok(t) => match t.duration_since(UNIX_EPOCH) {
160                Ok(t) => Some(t.as_secs() as i64),
161                Err(_) => None,
162            },
163            Err(_) => None,
164        };
165
166        let in_file_mod_time_str = match in_file_mod_time {
167            None => null_if_json_else_NA!(json_printer).to_string(),
168            Some(x) => match (
169                time_utils::i64_secs_to_date_time_string(x, time_utils::TimeMode::UTC),
170                time_utils::i64_secs_to_date_time_string(x, time_utils::TimeMode::Local),
171            ) {
172                (Some(u), Some(l)) => format!("{} (UTC)  {} (Local)", u, l),
173                _ => "Invalid file modification time".to_string(),
174            },
175        };
176
177        let out_file_size = file_utils::from_orig_file_size::calc_container_size(
178            version,
179            Some(meta_enabled),
180            data_par_burst,
181            in_file_size,
182        );
183
184        if ver_uses_rs(version) {
185            print_maybe_json!(json_printer, "File name                    : {}", in_file);
186            print_maybe_json!(json_printer, "SBX container name           : {}", out);
187            print_maybe_json!(
188                json_printer,
189                "SBX container version        : {}",
190                ver_to_usize(version)
191            );
192            print_maybe_json!(
193                json_printer,
194                "SBX container block size     : {}",
195                ver_to_block_size(version)
196            );
197            print_maybe_json!(
198                json_printer,
199                "SBX container data  size     : {}",
200                ver_to_data_size(version)
201            );
202            print_maybe_json!(
203                json_printer,
204                "RS data   shard count        : {}",
205                data_par_burst.unwrap().0
206            );
207            print_maybe_json!(
208                json_printer,
209                "RS parity shard count        : {}",
210                data_par_burst.unwrap().1
211            );
212            print_maybe_json!(
213                json_printer,
214                "Burst error resistance level : {}",
215                data_par_burst.unwrap().2
216            );
217            print_maybe_json!(
218                json_printer,
219                "File size                    : {}",
220                in_file_size
221            );
222            print_maybe_json!(
223                json_printer,
224                "SBX container size           : {}",
225                out_file_size
226            );
227            print_maybe_json!(
228                json_printer,
229                "File modification time       : {}",
230                in_file_mod_time_str
231            );
232        } else {
233            print_maybe_json!(json_printer, "File name                : {}", in_file);
234            print_maybe_json!(json_printer, "SBX container name       : {}", out);
235            print_maybe_json!(
236                json_printer,
237                "SBX container version    : {}",
238                ver_to_usize(version)
239            );
240            print_maybe_json!(
241                json_printer,
242                "SBX container block size : {}",
243                ver_to_block_size(version)
244            );
245            print_maybe_json!(
246                json_printer,
247                "SBX container data  size : {}",
248                ver_to_data_size(version)
249            );
250            print_maybe_json!(json_printer, "File size                : {}", in_file_size);
251            print_maybe_json!(json_printer, "SBX container size       : {}", out_file_size);
252            print_maybe_json!(
253                json_printer,
254                "File modification time   : {}",
255                in_file_mod_time_str
256            );
257        }
258
259        json_printer.print_close_bracket();
260
261        exit_with_msg!(ok json_printer => "")
262    } else {
263        exit_if_file!(exists &out
264                      => matches.is_present("force")
265                      => json_printer
266                      => "File \"{}\" already exists", out);
267
268        let in_file = if file_utils::check_if_file_is_stdin(in_file) {
269            None
270        } else {
271            Some(in_file)
272        };
273
274        let param = Param::new(
275            version,
276            &uid,
277            data_par_burst,
278            meta_enabled,
279            &json_printer,
280            hash_type,
281            from_pos,
282            to_pos,
283            in_file,
284            &out,
285            pr_verbosity_level,
286        );
287        match encode_core::encode_file(&param) {
288            Ok(s) => exit_with_msg!(ok json_printer => "{}", s),
289            Err(e) => exit_with_msg!(op json_printer => "{}", e),
290        }
291    }
292}