1use ssec_core::Encrypt;
2use futures_util::StreamExt;
3use tokio::io::AsyncWriteExt;
4use rand::rngs::OsRng;
5use indicatif::{ProgressBar, ProgressStyle};
6use crate::cli::EncArgs;
7use crate::password::prompt_password;
8use crate::io::IoBundle;
9use crate::DEFINITE_BAR_STYLE;
10
11const SPINNER_STYLE: &str = "{spinner} deriving encryption key";
12
13pub async fn enc<B: IoBundle>(args: EncArgs, io: B) -> Result<(), ()> {
14 let password = prompt_password(io).await.map_err(|e| {
15 eprintln!("failed to read password interactively: {e}");
16 })?;
17
18 let f_in = tokio::fs::File::open(&args.in_file).await.map_err(|e| {
19 eprintln!("failed to open file {:?}: {e}", args.in_file);
20 })?;
21 let f_in_metadata = f_in.metadata().await.map_err(|e| {
22 eprintln!("failed to get metadata of input file: {e}");
23 })?;
24
25 if !f_in_metadata.is_file() {
26 eprintln!("ERROR: input file {:?} is not, in fact, a file", args.in_file);
27 return Err(());
28 }
29
30 let f_in_len = f_in_metadata.len();
31 if f_in_len == 0 {
32 eprintln!("input file {:?} is empty", args.in_file);
33 return Ok(());
34 }
35
36 let progress = match B::is_interactive() && !args.silent {
37 true => ProgressBar::new(f_in_len),
38 false => ProgressBar::hidden()
39 };
40 let progress_read = progress.wrap_async_read(f_in);
41 let s = tokio_util::io::ReaderStream::new(progress_read);
42 let mut enc = tokio::task::spawn_blocking(move || {
43 let spinner = match B::is_interactive() && !args.silent {
44 true => ProgressBar::new_spinner(),
45 false => ProgressBar::hidden()
46 };
47 spinner.set_style(ProgressStyle::with_template(SPINNER_STYLE).unwrap());
48 spinner.enable_steady_tick(std::time::Duration::from_millis(100));
49
50 Encrypt::new_uncompressed(s, &password, &mut OsRng)
51 }).await.unwrap().unwrap();
52
53 let mut f_out = match args.out_file {
54 Some(out_file) => {
55 tokio::fs::OpenOptions::new()
56 .create(true)
57 .write(true)
58 .truncate(true)
59 .open(&out_file).await.map_err(|e| {
60 eprintln!("failed to open specified output file {out_file:?}: {e}");
61 }).map(tokio::io::BufWriter::new)?
62 },
63 None => {
64 let out_name = format!("{}.ssec", args.in_file.display());
65 tokio::fs::File::create_new(&out_name).await.map_err(|e| {
66 eprintln!("failed to create new output file {out_name:?}: {e}");
67 }).map(tokio::io::BufWriter::new)?
68 }
69 };
70
71 progress.set_style(ProgressStyle::with_template(DEFINITE_BAR_STYLE).unwrap());
72 progress.reset();
73
74 while let Some(bytes) = enc.next().await {
75 let b = bytes.unwrap();
76 f_out.write_all(&b).await.unwrap();
77 }
78
79 f_out.shutdown().await.unwrap();
80
81 Ok(())
82}