From 800f703214e2a9ff36dc3762d74decdb6215cefe Mon Sep 17 00:00:00 2001 From: CosmicHorror Date: Tue, 16 Apr 2024 21:25:20 -0600 Subject: [PATCH 1/4] refactor(tests): Drop wiremock (#320) * docs: Describe what we use deps for * refactor(tests): Switch `wiremock` for a custom impl --- Cargo.lock | 289 ++++----------------------------------- Cargo.toml | 180 +++++++++++++++++------- src/interpreter/tests.rs | 61 ++------- src/main.rs | 1 + src/test_utils.rs | 111 +++++++++++++++ 5 files changed, 283 insertions(+), 359 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9508f01d..19b690a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,6 +160,12 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "ash" version = "0.37.3+1.3.251" @@ -169,16 +175,6 @@ dependencies = [ "libloading 0.7.4", ] -[[package]] -name = "assert-json-diff" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "async-broadcast" version = "0.7.0" @@ -565,6 +561,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "chunked_transfer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" + [[package]] name = "clap" version = "4.5.4" @@ -913,24 +915,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7046468a81e6a002061c01e6a7c83139daf91b11c30e66795b13217c2d885c8b" -[[package]] -name = "deadpool" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490" -dependencies = [ - "async-trait", - "deadpool-runtime", - "num_cpus", - "tokio", -] - -[[package]] -name = "deadpool-runtime" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49" - [[package]] name = "deranged" version = "0.3.11" @@ -1419,48 +1403,12 @@ dependencies = [ "new_debug_unreachable", ] -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", - "futures-sink", -] - [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - [[package]] name = "futures-io" version = "0.3.30" @@ -1480,17 +1428,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.58", -] - [[package]] name = "futures-sink" version = "0.3.30" @@ -1509,10 +1446,8 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ - "futures-channel", "futures-core", "futures-io", - "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1669,25 +1604,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eec1c01eb1de97451ee0d60de7d81cf1e72aabefb021616027f3d1c3ec1c723c" -[[package]] -name = "h2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 2.2.6", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "half" version = "2.4.0" @@ -1788,46 +1704,6 @@ dependencies = [ "syn 2.0.58", ] -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - [[package]] name = "httpdate" version = "1.0.3" @@ -1850,43 +1726,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "hyper" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" -dependencies = [ - "bytes", - "futures-util", - "http", - "http-body", - "hyper", - "pin-project-lite", - "socket2", - "tokio", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1993,6 +1832,7 @@ dependencies = [ "syntect", "taffy", "tempfile", + "tiny_http", "toml", "tracing", "tracing-subscriber", @@ -2001,7 +1841,6 @@ dependencies = [ "ureq", "wgpu", "winit", - "wiremock", ] [[package]] @@ -3790,16 +3629,6 @@ dependencies = [ "wayland-backend", ] -[[package]] -name = "socket2" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "spin" version = "0.9.8" @@ -4101,6 +3930,18 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tiny_http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +dependencies = [ + "ascii", + "chunked_transfer", + "httpdate", + "log", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -4116,47 +3957,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "tokio" -version = "1.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-macros" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.58", -] - -[[package]] -name = "tokio-util" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - [[package]] name = "toml" version = "0.8.12" @@ -4274,12 +4074,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "ttf-parser" version = "0.19.2" @@ -4580,15 +4374,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -5271,30 +5056,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "wiremock" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec874e1eef0df2dcac546057fe5e29186f09c378181cd7b635b4b7bcc98e9d81" -dependencies = [ - "assert-json-diff", - "async-trait", - "base64 0.21.7", - "deadpool", - "futures", - "http", - "http-body-util", - "hyper", - "hyper-util", - "log", - "once_cell", - "regex", - "serde", - "serde_json", - "tokio", - "url", -] - [[package]] name = "x11-clipboard" version = "0.9.2" diff --git a/Cargo.toml b/Cargo.toml index 7b726fc4..e1d0a355 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ exclude = [ "/ci/*", "/.github/*", "/assets/manual_test_data/*", + "/typos.toml", ] keywords = ["markdown", "viewer", "gpu"] @@ -28,55 +29,150 @@ x11 = ["copypasta/x11", "winit/x11"] wayland = ["copypasta/wayland", "winit/wayland"] [dependencies] -winit = { version = "0.28.7", default-features = false } -wgpu = "0.16" -bytemuck = { version = "1.15.0", features = [ "derive" ] } -lyon = "1.0.1" -comrak = { version = "0.22.0", default-features = false, features = ["shortcodes", "syntect"] } -open = "5.1.2" -html5ever = "0.27.0" -image = "0.24.9" -clap = { version = "4.4.18", features = ["cargo", "derive"] } -copypasta = { version = "0.10.1", default-features = false } -resvg = "0.37.0" +# `anstream` and `anstyle` are both terminal helper crates used for our custom +# panic hook (and already used by `clap`, so it's a "free" dependency) +anstream = "0.6.13" +anstyle = "1.0.6" +# Easier error handling anyhow = "1.0.81" +# System preferred color scheme detection +dark-light = "1.1.1" +# System specific directories dirs = "5.0.1" -serde = { version = "1.0.197", features = ["derive"] } -toml = "0.8.12" +# Used to open the config file with `$ inlyne config open` +edit = "0.1.5" +# Faster hash for the text cache +fxhash = "0.2.1" +# GPU text rendering +glyphon = "0.3" +# Used to values used in YAML frontmatter when converting to HTML +html-escape = "0.2.13" +# Parsing the HTML document that the markdown+html was converted into +html5ever = "0.27.0" +# Provides some extra helpers that we use for our custom panic hook +human-panic = "1.2.3" +# Generic image decoding +image = "0.24.9" +# 2D GPU graphics rendering +lyon = "1.0.1" +# Images are compressed to in-memory lz4 blobs +lz4_flex = "0.11.3" +# Generic metrics facade for our metrics recording/emission infra +metrics = "0.22.3" +# File event notifications for the live reloading feature notify = "6.1.1" -dark-light = "1.1.1" -# We only decompress our own compressed data, so disable `safe-decode` and -# `checked-decode` -lz4_flex = { version = "0.11.3", default-features = false, features = ["frame", "safe-encode", "std"] } +# Used to open external links like in the user's browser +open = "5.1.2" +# Some alternative atomics that are slightly more ergonoics than `std`'s +parking_lot = "0.12.1" +# Dead simple way to handle some async operations pollster = "0.3.0" +# Used to get a handle to the display, so that we can setup a clipboard +raw-window-handle = "0.5.2" +# SVG rendering +resvg = "0.37.0" +# Parses the optional YAML frontmatter (replace with just a yaml parser) serde_yaml = "0.9.34" -indexmap = { version = "2.2.6", features = ["serde"] } -html-escape = "0.2.13" -fxhash = "0.2.1" -twox-hash = "1.6.3" -taffy = "0.3.18" -syntect = "5.2.0" +# Easy `Debug` formatting changes used to keep snapshot tests more succinct smart-debug = "0.0.3" +# Helps power our syntax highlighting +syntect = "5.2.0" +# Some CSS layout algos that we use as a pretty decent alternative to us +# lacking HTML ones +taffy = "0.3.18" +# For parsing our config file +toml = "0.8.12" +# In application tracing (aka logging on steroids) +tracing = "0.1.40" +# Extra syntax and theme definitions for `syntect` two-face = "0.3.0" +# More text hashing... +twox-hash = "1.6.3" +# HTTP client for requesting images from urls +ureq = "2.9.6" +# Cross platform GPU magic sauce +wgpu = "0.16" + +# Used for casting types to GPU compatible formats +[dependencies.bytemuck] +version = "1.15.0" +features = ["derive"] + +# Command line arg parsing +[dependencies.clap] +version = "4.4.18" +features = ["cargo", "derive"] + +# Converts our markdown+html to pure HTML +[dependencies.comrak] +version = "0.22.0" +default-features = false +features = ["shortcodes", "syntect"] + +# Clipboard handling +[dependencies.copypasta] +version = "0.10.1" +default-features = false + # NOTE: We need `fontconfig` enabled to pick up fonts on some systems, but # `glyphon` doesn't provide any way set that feature for `fontdb`, so we have to # set the feature for the transitive dep while manually making sure that we keep # the versions in sync ;-; -fontdb = { version = "0.14.1", features = ["fontconfig"] } -human-panic = "1.2.3" -notify-debouncer-full = { version = "0.3.1", default-features = false } -tracing = "0.1.40" -tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } -glyphon = "0.3" -string_cache = { version = "0.8.7", default-features = false } -raw-window-handle = "0.5.2" -edit = "0.1.5" -anstream = "0.6.13" -anstyle = "1.0.6" -metrics = "0.22.3" -metrics-util = { version = "0.16.3", default-features = false, features = ["registry", "summary"] } -parking_lot = "0.12.1" -ureq = "2.9.6" +[dependencies.fontdb] +version = "0.14.1" +features = ["fontconfig"] + +# Common dep used from yaml that can probably be replaced with a `Vec<(_, _)>` +[dependencies.indexmap] +version = "2.2.6" +features = ["serde"] + +# Metrics helpers used for our custom metric logger +[dependencies.metrics-util] +version = "0.16.3" +default-features = false +features = ["registry", "summary"] + +# Debouncer that papers over some issues with various ways that editors save +# files +[dependencies.notify-debouncer-full] +version = "0.3.1" +default-features = false + +# For dealing with both TOML and YAML +[dependencies.serde] +version = "1.0.197" +features = ["derive"] + +# Common dep used by `html5ever` +[dependencies.string_cache] +version = "0.8.7" +default-features = false + +# Our specific tracing implementation +[dependencies.tracing-subscriber] +version = "0.3.18" +features = ["env-filter"] + +# Cross-platform window handling +[dependencies.winit] +version = "0.28.7" +default-features = false + +[dev-dependencies] +# Succinct and more readable binary blobs +base64 = "0.22.0" +# Used to update file's modified time for tests +filetime = "0.2.23" +# Snapshot testing +insta = "1.38.0" +# Assertions displayed as diffs which is immensely helpful for some of our large +# values +pretty_assertions = "1.4.0" +# Throwaway files/dirs for isolated test environments +tempfile = "3.10.1" +# Use for setting up a local http server to test image requests in isolation +tiny_http = "0.12.0" [target.'cfg(inlyne_tcp_metrics)'.dependencies] metrics-exporter-tcp = "0.9.0" @@ -94,14 +190,6 @@ debug = true inherits = "release" lto = true -[dev-dependencies] -base64 = "0.22.0" -filetime = "0.2.23" -insta = "1.38.0" -pretty_assertions = "1.4.0" -tempfile = "3.10.1" -wiremock = "0.6.0" - # Selectively bump up opt level for some dependencies to improve dev build perf [profile.dev.package] ttf-parser.opt-level = 2 diff --git a/src/interpreter/tests.rs b/src/interpreter/tests.rs index 027a03d6..6b60ab66 100644 --- a/src/interpreter/tests.rs +++ b/src/interpreter/tests.rs @@ -11,14 +11,13 @@ use super::{HtmlInterpreter, ImageCallback, WindowInteractor}; use crate::color::{Theme, ThemeDefaults}; use crate::image::{Image, ImageData}; use crate::opts::ResolvedTheme; -use crate::test_utils::init_test_log; +use crate::test_utils::{init_test_log, mock_file_server, File}; use crate::utils::Align; use crate::{Element, ImageCache}; use base64::prelude::*; use syntect::highlighting::Theme as SyntectTheme; use wgpu::TextureFormat; -use wiremock::{matchers, Mock, MockServer, ResponseTemplate}; // We use a dummy window with an internal counter that keeps track of when rendering a single md // document is finished @@ -415,55 +414,14 @@ snapshot_interpreted_elements!( (num_is_bold, NUM_IS_BOLD), ); -struct File { - url_path: String, - mime: String, - bytes: Vec, -} - -impl File { - fn new(url_path: &str, mime: &str, bytes: &[u8]) -> Self { - Self { - url_path: url_path.to_owned(), - mime: mime.to_owned(), - bytes: bytes.to_owned(), - } - } -} - -/// Spin up a server, so we can test network requests without external services -fn mock_file_server(files: &[File]) -> (MockServer, String) { - let setup_server = async { - let mock_server = MockServer::start().await; - - for file in files { - let File { - url_path, - mime, - bytes, - } = file; - Mock::given(matchers::method("GET")) - .and(matchers::path(url_path)) - .respond_with(ResponseTemplate::new(200).set_body_raw(bytes.to_owned(), mime)) - .mount(&mock_server) - .await; - } - - mock_server - }; - let server = pollster::block_on(setup_server); - - let server_url = server.uri(); - (server, server_url) -} - #[test] fn centered_image_with_size_align_and_link() { init_test_log(); let logo = include_bytes!("../../assets/test_data/bun_logo.png"); let logo_path = "/bun_logo.png"; - let (_server, server_url) = mock_file_server(&[File::new(logo_path, "image/png", logo)]); + let files = vec![File::new(logo_path, "image/png", logo)]; + let (_server, server_url) = mock_file_server(files); let logo_url = server_url + logo_path; let text = format!( @@ -488,8 +446,11 @@ fn image_loading_fails_gracefully() { let json = r#"{"im": "not an image"}"#; let json_path = "/snapshot.png"; - let (_server, server_url) = - mock_file_server(&[File::new(json_path, "application/json", json.as_bytes())]); + let (_server, server_url) = mock_file_server(vec![File::new( + json_path, + "application/json", + json.as_bytes(), + )]); let json_url = server_url + json_path; let text = format!("![This actually returns JSON 😈]({json_url})"); @@ -538,11 +499,13 @@ fn picture_dark_light() { (light_path, B64_SINGLE_PIXEL_WEBP_000), (default_path, B64_SINGLE_PIXEL_WEBP_999), ] + .into_iter() .map(|(path, b64_bytes)| { let bytes = BASE64_STANDARD.decode(b64_bytes).unwrap(); File::new(path, webp_mime, &bytes) - }); - let (_server, server_url) = mock_file_server(&files); + }) + .collect(); + let (_server, server_url) = mock_file_server(files); let dark_url = format!("{server_url}{dark_path}"); let light_url = format!("{server_url}{light_path}"); let default_url = format!("{server_url}{default_path}"); diff --git a/src/main.rs b/src/main.rs index 49679c18..2ab4c33c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ pub mod positioner; pub mod renderer; pub mod selection; pub mod table; +#[cfg(test)] pub mod test_utils; pub mod text; pub mod utils; diff --git a/src/test_utils.rs b/src/test_utils.rs index 46fff985..3f309e8d 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1,3 +1,12 @@ +use std::{ + sync::{ + mpsc::{sync_channel, Receiver, SyncSender}, + Arc, + }, + thread, +}; + +use tiny_http::{Header, Method, Request, Response, Server}; use tracing_subscriber::prelude::*; pub fn init_test_log() { @@ -14,3 +23,105 @@ pub fn init_test_log() { ) .try_init(); } + +/// Spin up a server, so we can test network requests without external services +pub fn mock_file_server(files: Vec) -> (FileServer, String) { + let server = FileServer::spawn(files); + let url = server.url().to_owned(); + (server, url) +} + +pub struct File { + pub url_path: String, + pub mime: String, + pub bytes: Vec, +} + +impl File { + pub fn new(url_path: &str, mime: &str, bytes: &[u8]) -> Self { + Self { + url_path: url_path.to_owned(), + mime: mime.to_owned(), + bytes: bytes.to_owned(), + } + } +} + +pub struct FileServer { + url: String, + shutdown_send: SyncSender<()>, +} + +impl FileServer { + // Spawn the server + // |-> Move one handle to a shutdown thread + // |-> Move The other handle to a request handler thread + // | \-> Each request gets handled on a newly spawned thread + // \-> Return a server guard that shuts down on `drop()` + pub fn spawn>>(files: Files) -> Self { + let files = files.into(); + // Bind to the ephemeral port and then get the actual resolved address + let server = Server::http("127.0.0.1:0").unwrap(); + let ip = server + .server_addr() + .to_ip() + .expect("Provided addr is an ip"); + // We're using an `::http()` server + let url = format!("http://{ip}"); + + let server = Arc::new(server); + let (shutdown_send, shutdown_recv) = sync_channel(1); + + Self::spawn_router(Arc::clone(&server), files); + Self::spawn_shutdown(server, shutdown_recv); + + Self { url, shutdown_send } + } + + fn spawn_shutdown(server: Arc, shutdown_recv: Receiver<()>) { + thread::spawn(move || { + if let Ok(()) = shutdown_recv.recv() { + // Unblock the `.incoming_requests()` + server.unblock(); + } + }); + } + + fn spawn_router(server: Arc, files: Arc<[File]>) { + thread::spawn(move || { + for req in server.incoming_requests() { + let req_files = Arc::clone(&files); + thread::spawn(|| Self::handle_req(req, req_files)); + } + // Time to shutdown now + }); + } + + fn handle_req(req: Request, files: Arc<[File]>) { + match req.method() { + Method::Get => { + let path = req.url(); + match files.iter().find(|file| file.url_path == path) { + Some(file) => { + let header = + Header::from_bytes(b"Content-Type", file.mime.as_bytes()).unwrap(); + let resp = Response::from_data(file.bytes.clone()).with_header(header); + let _ = req.respond(resp); + } + None => _ = req.respond(Response::empty(404)), + } + } + _ => _ = req.respond(Response::empty(404)), + } + } + + pub fn url(&self) -> &str { + &self.url + } +} + +impl Drop for FileServer { + fn drop(&mut self) { + let _ = self.shutdown_send.send(()); + } +} From 7b0a0380ccd647b9202e0cf2bcd8fcb230f80d22 Mon Sep 17 00:00:00 2001 From: CosmicHorror Date: Wed, 17 Apr 2024 00:01:07 -0600 Subject: [PATCH 2/4] refactor(tests): simplify the file server (#321) --- src/test_utils.rs | 99 ++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 53 deletions(-) diff --git a/src/test_utils.rs b/src/test_utils.rs index 3f309e8d..754faac6 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1,12 +1,6 @@ -use std::{ - sync::{ - mpsc::{sync_channel, Receiver, SyncSender}, - Arc, - }, - thread, -}; +use std::{sync::Arc, thread}; -use tiny_http::{Header, Method, Request, Response, Server}; +use tiny_http::{Header, Method, Request, Response, ResponseBox, Server}; use tracing_subscriber::prelude::*; pub fn init_test_log() { @@ -25,8 +19,24 @@ pub fn init_test_log() { } /// Spin up a server, so we can test network requests without external services -pub fn mock_file_server(files: Vec) -> (FileServer, String) { - let server = FileServer::spawn(files); +pub fn mock_file_server(files: Vec) -> (HttpServer, String) { + let files: Arc<[File]> = files.into(); + let server = HttpServer::spawn(files, |files, req| match req.method() { + Method::Get => { + let path = req.url(); + match files.iter().find(|file| file.url_path == path) { + Some(file) => { + let header = Header::from_bytes(b"Content-Type", file.mime.as_bytes()).unwrap(); + Response::from_data(file.bytes.clone()) + .with_header(header) + .boxed() + } + None => Response::empty(404).boxed(), + } + } + _ => Response::empty(404).boxed(), + }); + let url = server.url().to_owned(); (server, url) } @@ -47,19 +57,23 @@ impl File { } } -pub struct FileServer { +// TODO: move some of this to a `tiny-http-utils` crate? +pub struct HttpServer { url: String, - shutdown_send: SyncSender<()>, + server: Arc, } -impl FileServer { +impl HttpServer { // Spawn the server - // |-> Move one handle to a shutdown thread - // |-> Move The other handle to a request handler thread + // |-> Move a handle to a request handler thread // | \-> Each request gets handled on a newly spawned thread // \-> Return a server guard that shuts down on `drop()` - pub fn spawn>>(files: Files) -> Self { - let files = files.into(); + pub fn spawn(state: S, handler_fn: F) -> Self + where + S: Send + Clone + 'static, + F: Fn(S, &Request) -> ResponseBox + Send + Clone + Copy + 'static, + { + // let files = files.into(); // Bind to the ephemeral port and then get the actual resolved address let server = Server::http("127.0.0.1:0").unwrap(); let ip = server @@ -70,58 +84,37 @@ impl FileServer { let url = format!("http://{ip}"); let server = Arc::new(server); - let (shutdown_send, shutdown_recv) = sync_channel(1); - Self::spawn_router(Arc::clone(&server), files); - Self::spawn_shutdown(server, shutdown_recv); + Self::spawn_router(Arc::clone(&server), state, handler_fn); - Self { url, shutdown_send } - } - - fn spawn_shutdown(server: Arc, shutdown_recv: Receiver<()>) { - thread::spawn(move || { - if let Ok(()) = shutdown_recv.recv() { - // Unblock the `.incoming_requests()` - server.unblock(); - } - }); + Self { url, server } } - fn spawn_router(server: Arc, files: Arc<[File]>) { + fn spawn_router(server: Arc, state: S, handler_fn: F) + where + S: Send + Clone + 'static, + F: Fn(S, &Request) -> ResponseBox + Send + Clone + Copy + 'static, + { thread::spawn(move || { for req in server.incoming_requests() { - let req_files = Arc::clone(&files); - thread::spawn(|| Self::handle_req(req, req_files)); + let s2 = state.clone(); + thread::spawn(move || { + let resp = handler_fn(s2, &req); + let _ = req.respond(resp); + }); } // Time to shutdown now }); } - fn handle_req(req: Request, files: Arc<[File]>) { - match req.method() { - Method::Get => { - let path = req.url(); - match files.iter().find(|file| file.url_path == path) { - Some(file) => { - let header = - Header::from_bytes(b"Content-Type", file.mime.as_bytes()).unwrap(); - let resp = Response::from_data(file.bytes.clone()).with_header(header); - let _ = req.respond(resp); - } - None => _ = req.respond(Response::empty(404)), - } - } - _ => _ = req.respond(Response::empty(404)), - } - } - pub fn url(&self) -> &str { &self.url } } -impl Drop for FileServer { +impl Drop for HttpServer { fn drop(&mut self) { - let _ = self.shutdown_send.send(()); + // Unblock the `.incoming_requests()` + self.server.unblock(); } } From aeea63f038d73d130d0261bd36a39860cad4aad0 Mon Sep 17 00:00:00 2001 From: CosmicHorror Date: Wed, 8 May 2024 22:12:39 -0600 Subject: [PATCH 3/4] chore(clippy): placate new beta clippy lints (#326) --- src/main.rs | 2 +- src/selection.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2ab4c33c..81041441 100644 --- a/src/main.rs +++ b/src/main.rs @@ -178,7 +178,7 @@ impl Inlyne { &window, opts.theme.clone(), opts.scale.unwrap_or(window.scale_factor() as f32), - opts.page_width.unwrap_or(std::f32::MAX), + opts.page_width.unwrap_or(f32::MAX), opts.font_opts.clone(), ))?; diff --git a/src/selection.rs b/src/selection.rs index 78287851..38e5366f 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -10,7 +10,7 @@ pub enum SelectionMode { Line, } -#[derive(Debug)] +#[derive(Debug, Default)] pub enum SelectionKind { Drag { start: Point, @@ -25,13 +25,16 @@ pub enum SelectionKind { position: Point, time: Instant, }, + #[default] None, } +#[derive(Default)] pub struct Selection { pub selection: SelectionKind, pub text: String, } + impl Selection { pub const fn new() -> Self { Self { From 97ec63b7e753b433ee9f73aac65c00a9cc1f8f67 Mon Sep 17 00:00:00 2001 From: CosmicHorror Date: Wed, 8 May 2024 22:25:28 -0600 Subject: [PATCH 4/4] refactor: Bump image version and remove streamed decoding (#325) --- Cargo.lock | 343 +++++++++++++++++- Cargo.toml | 2 +- src/image/decode.rs | 159 +------- ...tests__image_loading_fails_gracefully.snap | 6 +- 4 files changed, 342 insertions(+), 168 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19b690a5..aa07581c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "allocator-api2" version = "0.2.16" @@ -148,6 +154,23 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -342,6 +365,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -411,6 +457,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "bitstream-io" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c12d1856e42f0d817a835fe55853957c85c8c8a470114029143d3f12671446e" + [[package]] name = "block" version = "0.1.6" @@ -461,6 +513,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "built" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41bfbdb21256b87a8b5e80fab81a8eed158178e812fd7ba451907518b2742f16" + [[package]] name = "bumpalo" version = "3.15.4" @@ -493,6 +551,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.6.0" @@ -549,6 +613,16 @@ dependencies = [ "libc", ] +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -1744,20 +1818,35 @@ dependencies = [ [[package]] name = "image" -version = "0.24.9" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" dependencies = [ "bytemuck", "byteorder", "color_quant", "exr", "gif 0.13.1", - "jpeg-decoder", + "image-webp", "num-traits", "png", "qoi", + "ravif", + "rayon", + "rgb", "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" +dependencies = [ + "byteorder-lite", + "thiserror", ] [[package]] @@ -1766,6 +1855,12 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + [[package]] name = "indexmap" version = "1.9.3" @@ -1887,6 +1982,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "is-docker" version = "0.2.0" @@ -1941,9 +2047,6 @@ name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" -dependencies = [ - "rayon", -] [[package]] name = "js-sys" @@ -2018,6 +2121,17 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libloading" version = "0.7.4" @@ -2099,6 +2213,15 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "lru" version = "0.11.1" @@ -2207,6 +2330,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.7.2" @@ -2313,6 +2446,12 @@ dependencies = [ "sketches-ddsketch", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -2434,6 +2573,22 @@ dependencies = [ "memoffset 0.9.1", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + [[package]] name = "notify" version = "6.1.1" @@ -2476,12 +2631,53 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -2738,6 +2934,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pathdiff" version = "0.2.1" @@ -2977,6 +3179,19 @@ name = "profiling" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn 2.0.58", +] [[package]] name = "prost" @@ -3040,6 +3255,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.31.0" @@ -3100,6 +3321,56 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc13288f5ab39e6d7c9d501759712e6969fcc9734220846fc9ed26cae2cc4234" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -3493,6 +3764,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "similar" version = "2.5.0" @@ -3788,6 +4068,19 @@ dependencies = [ "libc", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + [[package]] name = "taffy" version = "0.3.18" @@ -3800,6 +4093,12 @@ dependencies = [ "slotmap", ] +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + [[package]] name = "tempfile" version = "3.10.1" @@ -4346,6 +4645,17 @@ dependencies = [ "getrandom", ] +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.0" @@ -4358,6 +4668,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.4" @@ -5254,6 +5570,12 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-inflate" version = "0.2.54" @@ -5263,6 +5585,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" version = "4.0.2" diff --git a/Cargo.toml b/Cargo.toml index e1d0a355..b607f8c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ html5ever = "0.27.0" # Provides some extra helpers that we use for our custom panic hook human-panic = "1.2.3" # Generic image decoding -image = "0.24.9" +image = "0.25.1" # 2D GPU graphics rendering lyon = "1.0.1" # Images are compressed to in-memory lz4 blobs diff --git a/src/image/decode.rs b/src/image/decode.rs index 6be2075c..7c41ed0f 100644 --- a/src/image/decode.rs +++ b/src/image/decode.rs @@ -1,14 +1,10 @@ -use std::cmp; use std::io; use std::time::Instant; use crate::metrics::{histogram, HistTag}; use crate::utils::usize_in_mib; -use image::codecs::{ - gif::GifDecoder, jpeg::JpegDecoder, png::PngDecoder, tiff::TiffDecoder, webp::WebPDecoder, -}; -use image::{ColorType, GenericImageView, ImageDecoder, ImageFormat, ImageResult}; +use image::GenericImageView; use lz4_flex::frame::{BlockSize, FrameDecoder, FrameEncoder, FrameInfo}; pub fn lz4_compress(reader: &mut R) -> anyhow::Result> { @@ -36,159 +32,6 @@ pub fn lz4_decompress(blob: &[u8], size: usize) -> anyhow::Result> { pub type ImageParts = (Vec, (u32, u32)); pub fn decode_and_compress(contents: &[u8]) -> anyhow::Result { - // We can stream decoding some formats although decoding may still load everything into memory - // at once depending on how the decoder behaves - let maybe_streamed = match image::guess_format(contents)? { - ImageFormat::Png => stream_decode_and_compress(contents, PngDecoder::new)?, - ImageFormat::Jpeg => stream_decode_and_compress(contents, JpegDecoder::new)?, - ImageFormat::Gif => stream_decode_and_compress(contents, GifDecoder::new)?, - ImageFormat::Tiff => stream_decode_and_compress(contents, TiffDecoder::new)?, - ImageFormat::WebP => stream_decode_and_compress(contents, WebPDecoder::new)?, - _ => None, - }; - - match maybe_streamed { - Some(streamed) => Ok(streamed), - None => fallback_decode_and_compress(contents), - } -} - -fn stream_decode_and_compress<'img, Dec>( - contents: &'img [u8], - decoder_constructor: fn(io::Cursor<&'img [u8]>) -> ImageResult, -) -> anyhow::Result> -where - Dec: ImageDecoder<'img>, -{ - let dec = decoder_constructor(io::Cursor::new(contents))?; - - let total_size = dec.total_bytes(); - let dimensions = dec.dimensions(); - let start = Instant::now(); - - let Some(mut adapter) = Rgba8Adapter::new(dec) else { - return Ok(None); - }; - - let maybe_image_parts = lz4_compress(&mut adapter).ok().map(|lz4_blob| { - tracing::debug!( - "Streaming image decode & compression:\n\ - - Full {:.2} MiB\n\ - - Compressed {:.2} MiB\n\ - - Time {:.2?}", - usize_in_mib(total_size as usize), - usize_in_mib(lz4_blob.len()), - start.elapsed(), - ); - - (lz4_blob, dimensions) - }); - Ok(maybe_image_parts) -} - -/// An adapter that can do a streaming transformation from some pixel formats to RGBA8 -enum Rgba8Adapter<'img> { - Rgba8(Box), - Rgb8 { - source: Box, - scratch: Vec, - }, -} - -impl<'img> Rgba8Adapter<'img> { - fn new>(dec: Dec) -> Option { - let adapter = match dec.color_type() { - ColorType::Rgba8 => { - // TODO: Need to figure out a workaround for streaming or eat the memory usage - #[allow(deprecated)] - Self::Rgba8(Box::new(dec.into_reader().ok()?)) - } - ColorType::Rgb8 => Self::Rgb8 { - // TODO: Need to figure out a workaround for streaming or eat the memory usage - #[allow(deprecated)] - source: Box::new(dec.into_reader().ok()?), - scratch: Vec::new(), - }, - _ => return None, - }; - - Some(adapter) - } -} - -impl<'img> io::Read for Rgba8Adapter<'img> { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - // TODO: can also do 16 bit adapters, but how to do them efficiently? - match self { - // Already the format we want, so just forward the data - Self::Rgba8(inner) => inner.read(buf), - // Transformation simply adds in a u8::MAX alpha channel - // [r1, g1, b1, r2, g2, b2, ...] => [r1, g1, b1, u8::MAX, r2, g2, b2, u8::MAX, ...] - // - // The actual implementation - // 1. Copies any left-over data from the scratch buffer to the output buffer - // 2. Performs a `.read()` on the underlying source to fill the scratch buffer - // 3. Does a pass backwards over the buffer to shift each pixel to its final position - // including the u8::MAX alpha channel - // 4. Copies data from the scratch buffer to the output buffer - // 5. Trims the scratch buffer to hold the left-over data - // - // This appears to be roughly just as fast as loading the full image into memory as an - // `image::DynamicImage` and then converting `.into_rgba8()` when testing with ~55 MiB - // of raw image data - Self::Rgb8 { source, scratch } => { - // Step 1. - if scratch.len() > buf.len() { - buf.copy_from_slice(&scratch[..buf.len()]); - scratch.copy_within(buf.len().., 0); - scratch.truncate(scratch.len() - buf.len()); - return Ok(buf.len()); - } - - let (left, right) = buf.split_at_mut(scratch.len()); - - left.copy_from_slice(scratch); - - // Step 2. - let num_pixels = right.len() / 3 + 1; - scratch.resize(num_pixels * 4, 0); - let n = source.read(&mut scratch[..num_pixels * 3])?; - if n == 0 { - scratch.clear(); - return Ok(left.len()); - } - - // Step 3. - let bytes_transformed = n * 4 / 3; - let mut rgb_end = n - 1; - let mut rgba_end = bytes_transformed - 1; - loop { - scratch[rgba_end] = u8::MAX; - scratch[rgba_end - 1] = scratch[rgb_end]; - scratch[rgba_end - 2] = scratch[rgb_end - 1]; - scratch[rgba_end - 3] = scratch[rgb_end - 2]; - - rgba_end = match rgba_end.checked_sub(4) { - Some(n) => n, - None => break, - }; - rgb_end -= 3; - } - - // Step 4. - right.copy_from_slice(&scratch[..right.len()]); - - // Step 5. - scratch.copy_within(right.len().., 0); - scratch.truncate(scratch.len() - right.len()); - - Ok(left.len() + cmp::min(right.len(), bytes_transformed)) - } - } - } -} - -fn fallback_decode_and_compress(contents: &[u8]) -> anyhow::Result<(Vec, (u32, u32))> { let image = image::load_from_memory(contents)?; let dimensions = image.dimensions(); let image_data = image.into_rgba8().into_raw(); diff --git a/src/interpreter/snapshots/inlyne__interpreter__tests__image_loading_fails_gracefully.snap b/src/interpreter/snapshots/inlyne__interpreter__tests__image_loading_fails_gracefully.snap index 31796628..de0bd89e 100644 --- a/src/interpreter/snapshots/inlyne__interpreter__tests__image_loading_fails_gracefully.snap +++ b/src/interpreter/snapshots/inlyne__interpreter__tests__image_loading_fails_gracefully.snap @@ -1,7 +1,7 @@ --- source: src/interpreter/tests.rs -description: "![This actually returns JSON 😈](http://127.0.0.1:36777/snapshot.png)" -expression: interpret_md(&text) +description: "![This actually returns JSON 😈](http://127.0.0.1:38947/snapshot.png)" +expression: "interpret_md_with_opts(&text, opts)" --- [ Spacer( @@ -16,7 +16,7 @@ expression: interpret_md(&text) image_data: Mutex { data: Some( ImageData { - lz4_blob: { len: 7762, data: [4, 34, 77, ..] }, + lz4_blob: { len: 7759, data: [4, 34, 77, ..] }, scale: false, dimensions: (63, 72), },