tidy-browser 0.3.4

Tidy up browser information.
Documentation
use std::{
    fmt::Display,
    fs::File,
    io::{ErrorKind, IoSlice, Write},
    path::PathBuf,
};

use decrypt_cookies::{chromium::ChromiumCookie, prelude::cookies::CookiesInfo};
use serde::Serialize;
use snafu::ResultExt;
use tokio::task::{self, JoinHandle};

use crate::{
    args::Format,
    error::{self, IoSnafu, JsonSnafu},
};

/// Copy from nightly [`Write::write_all_vectored`]
/// TODO: Use std method
pub(crate) fn write_all_vectored(
    file: &mut File,
    mut bufs: &mut [IoSlice<'_>],
) -> std::io::Result<()> {
    IoSlice::advance_slices(&mut bufs, 0);
    while !bufs.is_empty() {
        match file.write_vectored(bufs) {
            Ok(0) => {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::WriteZero,
                    "failed to write whole buffer",
                ));
            },
            Ok(n) => IoSlice::advance_slices(&mut bufs, n),
            Err(ref e) if matches!(e.kind(), ErrorKind::Interrupted) => {},
            Err(e) => return Err(e),
        }
    }
    Ok(())
}

pub(crate) fn write_cookies<S>(
    out_file: PathBuf,
    cookies: Vec<impl CookiesInfo + Send + Serialize + 'static>,
    sep: S,
    format: Format,
) -> JoinHandle<Result<(), error::Error>>
where
    S: Display + Send + Clone + 'static,
{
    task::spawn_blocking(move || {
        let mut file = File::options()
            .write(true)
            .create(true)
            .truncate(true)
            .open(&out_file)
            .with_context(|_| error::IoSnafu { path: out_file.clone() })?;

        match format {
            Format::Csv => {
                let mut slices = Vec::with_capacity(2 + cookies.len() * 2);

                let header = <ChromiumCookie as CookiesInfo>::csv_header(sep.clone());
                slices.push(IoSlice::new(header.as_bytes()));
                slices.push(IoSlice::new(b"\n"));

                let csvs: Vec<_> = cookies
                    .into_iter()
                    .map(|v| v.to_csv(sep.clone()))
                    .collect();

                for csv in &csvs {
                    slices.push(IoSlice::new(csv.as_bytes()));
                    slices.push(IoSlice::new(b"\n"));
                }

                write_all_vectored(&mut file, &mut slices)
                    .with_context(|_| error::IoSnafu { path: out_file })
            },
            Format::Json => serde_json::to_writer(file, &cookies).context(JsonSnafu),
            Format::JsonLines => serde_jsonlines::write_json_lines(&out_file, &cookies)
                .context(IoSnafu { path: out_file }),
        }
    })
}