From 745a3a61d5993a15679864d0066faaeac92be779 Mon Sep 17 00:00:00 2001 From: Avril Date: Mon, 13 Jul 2020 21:28:06 +0100 Subject: [PATCH] start decoder --- Cargo.toml | 3 +- src/ext.rs | 27 +++++++++++++ src/loli.rs | 7 ---- src/loli/encoding.rs | 55 +++++++++++++++++++++++++++ src/loli/error.rs | 77 ++++++++++++++++++++++++++++++++++++++ src/loli/mod.rs | 28 ++++++++++++++ src/main.rs | 3 ++ src/tempfile.rs | 2 +- src/work_async.rs | 11 +++--- src/work_async/tasklist.rs | 2 +- 10 files changed, 200 insertions(+), 15 deletions(-) create mode 100644 src/ext.rs delete mode 100644 src/loli.rs create mode 100644 src/loli/encoding.rs create mode 100644 src/loli/error.rs create mode 100644 src/loli/mod.rs diff --git a/Cargo.toml b/Cargo.toml index e4bfc60..7b53966 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,5 @@ lazy_static = "1.4" tokio = {version = "0.2", features= ["full"], optional=true} reqwest = {version = "0.10", features= ["stream"]} memmap = "0.7" -getrandom = "0.1" \ No newline at end of file +getrandom = "0.1" +base64 = "0.12" \ No newline at end of file diff --git a/src/ext.rs b/src/ext.rs new file mode 100644 index 0000000..8fb6bf2 --- /dev/null +++ b/src/ext.rs @@ -0,0 +1,27 @@ + +pub trait JoinStrsExt: Sized +{ + /// Join an iterator of `str` with a seperator + fn join(self, with: &str) -> String; +} + +impl JoinStrsExt for I +where I: Iterator, + T: AsRef +{ + fn join(self, with: &str) -> String + { + let mut output = String::new(); + let mut first=true; + for string in self + { + if !first { + output.push_str(with); + } + let string = string.as_ref(); + output.push_str(string); + first=false; + } + output + } +} diff --git a/src/loli.rs b/src/loli.rs deleted file mode 100644 index d96b846..0000000 --- a/src/loli.rs +++ /dev/null @@ -1,7 +0,0 @@ -use super::*; - -#[derive(Debug)] -pub struct Loli -{ - -} diff --git a/src/loli/encoding.rs b/src/loli/encoding.rs new file mode 100644 index 0000000..e49c420 --- /dev/null +++ b/src/loli/encoding.rs @@ -0,0 +1,55 @@ +use super::*; +use std::{ + str, + io::{ + self, + Write, + }, +}; + +const HEADER_BASE64_JPEG: &'static str = "/9j/"; +const HEADER_BASE64_PNG: &'static str = "iVBO"; +const HEADER_BASE64_GIF: &'static str = "R0lG"; + +/// An image type header +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum ImageType +{ + Png, + Jpeg, + Gif, +} + +impl Default for ImageType +{ + fn default() -> Self + { + Self::Jpeg + } +} + +impl str::FromStr for ImageType +{ + type Err = error::Error; + + /// Determine image type from base64 + fn from_str(from: &str) -> Result + { + if from.len() > 4 { + Ok(match &from[..4] { + HEADER_BASE64_GIF => Self::Gif, + HEADER_BASE64_PNG => Self::Png, + HEADER_BASE64_JPEG => Self::Jpeg, + _ => return Err(error::Error::UnknownFormat), + }) + } else { + Err(error::Error::InvalidFormat) + } + } +} + +/// Calculate the required data size from base64 input size +pub const fn data_size(base64: usize) -> usize +{ + ((4 * base64 / 3) + 3) & !3 +} diff --git a/src/loli/error.rs b/src/loli/error.rs new file mode 100644 index 0000000..7a6ea9f --- /dev/null +++ b/src/loli/error.rs @@ -0,0 +1,77 @@ +use super::*; +use std::{ + error, + fmt, + io, +}; + +#[derive(Debug)] +pub enum Error +{ + /// Image format could not be determined. + UnknownFormat, + /// Image is not valid + InvalidFormat, + /// Image decode failedb + DecodeError, + + // Internals + /// IO error + IO(io::Error), + /// Failed to format text + Formatter(fmt::Error), + /// Something bad + Unknown, +} + +impl error::Error for Error +{ + fn source(&self) -> Option<&(dyn error::Error + 'static)> + { + Some(match &self { + Self::IO(io) => io, + Self::Formatter(fmt) => fmt, + _ => return None, + }) + } +} + +impl fmt::Display for Error +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "loli module: ")?; + match self { + Self::UnknownFormat => write!(f, "could not determine image format"), + Self::InvalidFormat => write!(f, "image is not valid"), + Self::DecodeError => write!(f, "image decode failed"), + Self::IO(io) => write!(f, "io: {}", io), + Self::Formatter(fmt) => write!(f, "formatting: {}", fmt), + _ => write!(f, "unknown error"), + } + } +} + +impl From for Error +{ + fn from(er: io::Error) -> Self + { + Self::IO(er) + } +} + +impl From for Error +{ + fn from(er: fmt::Error) -> Self + { + Self::Formatter(er) + } +} + +impl From for Error +{ + fn from(_er: base64::DecodeError) -> Self + { + Self::DecodeError + } +} diff --git a/src/loli/mod.rs b/src/loli/mod.rs new file mode 100644 index 0000000..61be524 --- /dev/null +++ b/src/loli/mod.rs @@ -0,0 +1,28 @@ + +pub mod error; +pub mod encoding; + +/// Attempt to decode an image +pub fn decode(input: S, mut output: W) -> Result +where S: AsRef<[u8]>, + W: AsMut<[u8]> +{ + let input_bytes = input.as_ref(); + let output_bytes = output.as_mut(); + + Ok(base64::decode_config_slice(input_bytes, base64::STANDARD, output_bytes)?) +} + +/// Calculate the size for a base64 inpue +pub fn calc_size(input: T) -> usize +where T: AsRef<[u8]> +{ + encoding::data_size(input.as_ref().len()) +} + +/// Try to get the image format from a `str` slice. +pub fn attempt_get_format(input: T) -> Result +where T: AsRef +{ + input.as_ref().parse() +} diff --git a/src/main.rs b/src/main.rs index 84bae27..fd08678 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,9 @@ use termprogress::{ ProgressBar, }; +mod ext; +use ext::*; + mod config; mod args; mod url; diff --git a/src/tempfile.rs b/src/tempfile.rs index 254a894..9791f67 100644 --- a/src/tempfile.rs +++ b/src/tempfile.rs @@ -30,7 +30,7 @@ fn generate_filename(mut to: impl fmt::Write) -> Result<(), error::Error> write!(to, "{}", PREFIX)?; for byte in buffer.iter() { - write!(to, "{:2x}", *byte)?; + write!(to, "{:02x}", *byte)?; } Ok(()) diff --git a/src/work_async.rs b/src/work_async.rs index ee7e924..6701424 100644 --- a/src/work_async.rs +++ b/src/work_async.rs @@ -15,7 +15,7 @@ mod tasklist; mod progress; /// Download a loli async -pub async fn perform(url: impl AsRef, path: impl AsRef, mut progress: progress::CommandSender) -> Result<(), error::Error> +pub async fn perform(url: impl AsRef, path: impl AsRef, progress: &mut progress::CommandSender) -> Result { let url = url.as_ref(); let path = path.as_ref(); @@ -54,9 +54,9 @@ pub async fn perform(url: impl AsRef, path: impl AsRef, mut progress: prog_send!(progress.bump(1)); } prog_send!(progress.println(format!("done for {}", url))); - prog_send!(link progress.pop_task(task)); + //prog_send!(link progress.pop_task(task)); - Ok(()) + Ok(task) } pub async fn work(conf: config::Config) -> Result<(), Box> @@ -85,10 +85,11 @@ pub async fn work(conf: config::Config) -> Result<(), Box }, };*/ let temp = tempfile::TempFile::new(); - match perform(&url, &temp, prog).await { + match perform(&url, &temp, &mut prog).await { Err(e) => panic!("Failed downloading {} -> {:?}: {}", url, temp, e), //TODO: Make real error handler - Ok(_) => { + Ok(task) => { //TODO: memmap `temp` and decode base64 into new file `path`. also determine the encoding. + prog_send!(link prog.pop_task(task)); }, } diff --git a/src/work_async/tasklist.rs b/src/work_async/tasklist.rs index 0faa6fb..bc58ee3 100644 --- a/src/work_async/tasklist.rs +++ b/src/work_async/tasklist.rs @@ -71,7 +71,7 @@ impl TaskList pub fn recalc_buffer(&mut self) { - self.1 = self.0.iter().map(|(_, s)| s.as_str()).collect(); //TODO: Iterator extension join method for strings + self.1 = self.0.iter().map(|(_, s)| s.as_str()).join(", "); } pub fn as_str(&self) -> &str