From b63e6a8892a465114f513a986138a537e1244eae Mon Sep 17 00:00:00 2001 From: Tine Jozelj Date: Thu, 11 Jan 2024 19:02:35 +0100 Subject: [PATCH] feat: image loading on different thread --- Cargo.lock | 97 +++++++++++++++++++++++++++ Cargo.toml | 3 + data/resources/style.css | 4 +- data/resources/ui/components/card.blp | 4 +- src/api/.loaders.rs.rustfmt | 54 +++++++++++++++ src/api/base.rs | 14 ++++ src/api/loaders.rs | 27 ++++---- src/components/card.rs | 10 +-- src/main.rs | 1 - 9 files changed, 191 insertions(+), 23 deletions(-) create mode 100644 src/api/.loaders.rs.rustfmt diff --git a/Cargo.lock b/Cargo.lock index 625335a..590f60f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,6 +362,21 @@ dependencies = [ "slab", ] +[[package]] +name = "gdk" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5ba081bdef3b75ebcdbfc953699ed2d7417d6bd853347a42a37d76406a33646" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + [[package]] name = "gdk-pixbuf" version = "0.18.5" @@ -388,6 +403,23 @@ dependencies = [ "system-deps", ] +[[package]] +name = "gdk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + [[package]] name = "gdk4" version = "0.7.3" @@ -878,6 +910,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.20" @@ -940,13 +982,16 @@ version = "0.1.0" dependencies = [ "bytes", "futures", + "gdk", "gdk-pixbuf", "gettext-rs", + "glib", "gsettings-macro", "gtk4", "libadwaita", "reqwest", "serde", + "tokio", "tracing", "tracing-subscriber", ] @@ -1108,6 +1153,29 @@ dependencies = [ "system-deps", ] +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1323,6 +1391,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "security-framework" version = "2.9.2" @@ -1413,6 +1487,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.9" @@ -1581,11 +1664,25 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "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.48", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index df50e79..92d9ba9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,9 @@ gsettings-macro = "0.1.20" gdk-pixbuf = "0.18.5" bytes = "1.5.0" futures = "0.3.30" +tokio = { version = "1.35.1", features = ["full"] } +glib = "0.18.5" +gdk = "0.18.0" [dependencies.adw] package = "libadwaita" diff --git a/data/resources/style.css b/data/resources/style.css index e142005..067a638 100644 --- a/data/resources/style.css +++ b/data/resources/style.css @@ -1,4 +1,4 @@ .card image { - min-width: 500px; - min-height: 500px; + min-width: 200px; + min-height: 200px; } diff --git a/data/resources/ui/components/card.blp b/data/resources/ui/components/card.blp index 77ef6f0..bacc1a2 100644 --- a/data/resources/ui/components/card.blp +++ b/data/resources/ui/components/card.blp @@ -5,7 +5,9 @@ template $Card: Box { orientation: horizontal; styles ["card"] - Image image {} + Image image { + icon-name: "image-loading"; + } Box contents { orientation: vertical; diff --git a/src/api/.loaders.rs.rustfmt b/src/api/.loaders.rs.rustfmt new file mode 100644 index 0000000..d09bfda --- /dev/null +++ b/src/api/.loaders.rs.rustfmt @@ -0,0 +1,54 @@ +use gdk_pixbuf::traits::PixbufLoaderExt; +use gdk_pixbuf::{Pixbuf, PixbufLoader}; +use std::io::{Error, ErrorKind, Write}; +use std::thread; +use tokio::sync::oneshot; + +use crate::api::*; + +// A wrapper to be able to implement the Write trait on a PixbufLoader +struct LocalPixbufLoader<'a>(&'a PixbufLoader); + +impl<'a> Write for LocalPixbufLoader<'a> { + fn write(&mut self, buf: &[u8]) -> Result { + self.0 + .write(buf) + .map_err(|e| Error::new(ErrorKind::Other, format!("glib error: {e}")))?; + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), Error> { + self.0 + .close() + .map_err(|e| Error::new(ErrorKind::Other, format!("glib error: {e}")))?; + Ok(()) + } +} + +pub struct ImageLoader {} + +impl ImageLoader { + pub fn new() -> Self { + Self {} + } + + pub async fn from_url(&self, url: String) -> Option { + let (sender, receiver) = oneshot::channel(); + + let pixbuf_loader = PixbufLoader::new(); + let mut loader = LocalPixbufLoader(&pixbuf_loader); + + thread::spawn(move || { + let client = base::get_curse_forge_client().unwrap(); + let response = client.get(url).send(); + let bytes = response.unwrap().bytes().ok().unwrap(); + sender.send(bytes).unwrap(); + }); + + let bytes = receiver.await.ok()?; + loader.write_all(&bytes).ok()?; + + pixbuf_loader.close().ok()?; + pixbuf_loader.pixbuf() + } +} diff --git a/src/api/base.rs b/src/api/base.rs index 246953b..90ce040 100644 --- a/src/api/base.rs +++ b/src/api/base.rs @@ -22,3 +22,17 @@ pub fn get_curse_forge_client() -> Result Result { + let mut api_key_header = + reqwest::header::HeaderValue::from_str(crate::config::API_KEY).unwrap(); + api_key_header.set_sensitive(true); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("x-api-key", api_key_header); + + reqwest::Client::builder() + .user_agent(format!("dev.mnts.ModManager/{}", crate::config::VERSION)) + .default_headers(headers) + .build() +} diff --git a/src/api/loaders.rs b/src/api/loaders.rs index 4dc83f4..d09bfda 100644 --- a/src/api/loaders.rs +++ b/src/api/loaders.rs @@ -1,10 +1,8 @@ -// Inspired by https://github.com/xou816/spot/blob/aa1c57842c3f4e424eb62c69365438f904b2dc63/src/app/loader.rs -// Without the caching. -// -use bytes::Bytes; use gdk_pixbuf::traits::PixbufLoaderExt; use gdk_pixbuf::{Pixbuf, PixbufLoader}; use std::io::{Error, ErrorKind, Write}; +use std::thread; +use tokio::sync::oneshot; use crate::api::*; @@ -34,20 +32,21 @@ impl ImageLoader { Self {} } - fn get_image(url: &str) -> Option { - let client = base::get_curse_forge_client().unwrap(); - let response = client.get(url).send().ok(); - response.unwrap().bytes().ok() - } + pub async fn from_url(&self, url: String) -> Option { + let (sender, receiver) = oneshot::channel(); - pub fn load_remote(&self, url: &str, width: i32, height: i32) -> Option { let pixbuf_loader = PixbufLoader::new(); - pixbuf_loader.set_size(width, height); let mut loader = LocalPixbufLoader(&pixbuf_loader); - if let Some(mut resp) = Self::get_image(url) { - loader.write_all(&resp).ok()?; - } + thread::spawn(move || { + let client = base::get_curse_forge_client().unwrap(); + let response = client.get(url).send(); + let bytes = response.unwrap().bytes().ok().unwrap(); + sender.send(bytes).unwrap(); + }); + + let bytes = receiver.await.ok()?; + loader.write_all(&bytes).ok()?; pixbuf_loader.close().ok()?; pixbuf_loader.pixbuf() diff --git a/src/components/card.rs b/src/components/card.rs index 377ee14..1517baf 100644 --- a/src/components/card.rs +++ b/src/components/card.rs @@ -1,5 +1,4 @@ use adw::subclass::prelude::*; -use glib::clone; use gtk::CompositeTemplate; use gtk::{gio, glib}; @@ -64,10 +63,11 @@ impl Card { let url = image_url.to_string(); - worker.send_local_task(clone!(@weak widget => async move { + let image = widget.image.clone(); + worker.send_local_task(async move { let loader = ImageLoader::new(); - let pixbuf = loader.load_remote(url.as_str(), 800, 800); - widget.image.set_from_pixbuf(pixbuf.as_ref()); - })); + let pixbuf = loader.from_url(url).await; + image.set_from_pixbuf(pixbuf.as_ref()); + }); } } diff --git a/src/main.rs b/src/main.rs index b7814a0..6bb9d10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,6 @@ mod settings; mod windows; use self::application::ModManagerApplication; -use self::dispatch::spawn_task_handler; use config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE}; use gettextrs::{gettext, LocaleCategory};