diff --git a/src/decoder/formats/heif.rs b/src/decoder/formats/heif.rs index 4c07567..2239a92 100644 --- a/src/decoder/formats/heif.rs +++ b/src/decoder/formats/heif.rs @@ -23,7 +23,7 @@ use crate::util; use crate::util::gettext::*; use crate::util::ToBufRead; -use anyhow::Context; +use anyhow::{anyhow, bail, Context}; use arc_swap::ArcSwap; use gtk::prelude::*; use libheif_rs::{ColorProfile, ColorSpace, HeifContext, LibHeif, Plane, RgbChroma}; @@ -56,7 +56,7 @@ impl Heif { let cancellable = gio::Cancellable::new(); let cancellable_ = cancellable.clone(); - updater.spawn_error_handled(move || { + updater.clone().spawn_error_handled(move || { let file_size = file .query_info( gio::FILE_ATTRIBUTE_STANDARD_SIZE, @@ -99,9 +99,17 @@ impl Heif { RgbChroma::Rgb }; - let mut image = LIBHEIF - .decode(&handle, ColorSpace::Rgb(rgb_chroma), None) - .context(gettext("Failed to decode image"))?; + let image_result = LIBHEIF.decode(&handle, ColorSpace::Rgb(rgb_chroma), None); + + let mut image = match image_result { + Err(err) + if matches!(err.sub_code, libheif_rs::HeifErrorSubCode::UnsupportedCodec) => + { + updater.send(DecoderUpdate::UnsupportedFormat); + bail!(anyhow!(err).context(gettext("Unknown image format"))); + } + image => image.context(gettext("Failed to decode image"))?, + }; let icc_profile = if let Some(profile) = handle.color_profile_raw() { if [ diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index e1088f3..841dd97 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -63,6 +63,8 @@ pub enum DecoderUpdate { Metadata(ImageMetadata), /// Image format determined Format(ImageFormat), + /// Image format not supported or unknown + UnsupportedFormat, /// New image data available, redraw Redraw, /// And error occured during decoding @@ -113,13 +115,13 @@ impl Decoder { pub async fn new( file: gio::File, tiles: Arc>, - ) -> anyhow::Result<(Self, mpsc::UnboundedReceiver)> { + ) -> (Result, mpsc::UnboundedReceiver) { let (sender, receiver) = mpsc::unbounded(); let update_sender = UpdateSender { sender }; tiles.set_update_sender(update_sender.clone()); - let decoder = gio::spawn_blocking( + let format_decoder = gio::spawn_blocking( glib::clone!(@strong update_sender => move || Self::format_decoder( update_sender, file, @@ -127,15 +129,20 @@ impl Decoder { )), ) .await - .map_err(|_| anyhow!("Constructing the FormatDecoder failed unexpectedly"))??; + .map_err(|_| anyhow!("Constructing the FormatDecoder failed unexpectedly")); - Ok(( - Self { + let decoder = match format_decoder { + Ok(Ok(decoder)) => Ok(Self { decoder, update_sender, - }, - receiver, - )) + }), + Err(err) | Ok(Err(err)) => { + update_sender.send(DecoderUpdate::Error(err)); + Err(()) + } + }; + + (decoder, receiver) } fn format_decoder( @@ -186,11 +193,13 @@ impl Decoder { update_sender.send(DecoderUpdate::Format(ImageFormat::Heif)); return Ok(FormatDecoder::Heif(Heif::new(file, update_sender, tiles))); } else { + update_sender.send(DecoderUpdate::UnsupportedFormat); bail!(gettext_f("Unknown image format: {}", &[mime_type.as_str()])); } } } + update_sender.send(DecoderUpdate::UnsupportedFormat); bail!(gettext("Unknown image format")) } diff --git a/src/file_model.rs b/src/file_model.rs index cb6cf52..880b2db 100644 --- a/src/file_model.rs +++ b/src/file_model.rs @@ -222,6 +222,10 @@ impl LpFileModel { self.imp().files.borrow().last().map(|x| x.1).cloned() } + pub fn remove(&self, file: &gio::File) -> Option { + self.imp().files.borrow_mut().remove(&file.uri()) + } + /// Currently sorts by name fn sort(files: &mut IndexMap) { files.sort_by(|_, x, _, y| util::compare_by_name(&x.uri(), &y.uri())); diff --git a/src/widgets/image.rs b/src/widgets/image.rs index 455670b..0da1a54 100644 --- a/src/widgets/image.rs +++ b/src/widgets/image.rs @@ -109,6 +109,7 @@ mod imp { pub struct LpImage { pub(super) file: RefCell>, pub(super) is_deleted: Cell, + pub(super) is_unsupported: Cell, /// Track changes to this image pub(super) file_monitor: RefCell>, pub(super) frame_buffer: Arc>, @@ -200,6 +201,9 @@ mod imp { glib::ParamSpecBoolean::builder("is-loaded") .read_only() .build(), + glib::ParamSpecBoolean::builder("is-unsupported") + .read_only() + .build(), glib::ParamSpecString::builder("error").read_only().build(), glib::ParamSpecObject::builder::("metadata") .read_only() @@ -241,6 +245,7 @@ mod imp { "file" => obj.file().to_value(), "is-deleted" => obj.is_deleted().to_value(), "is-loaded" => obj.is_loaded().to_value(), + "is-unsupported" => obj.is_unsupported().to_value(), "error" => obj.error().to_value(), "metadata" => obj.metadata().to_value(), "format-name" => obj.format_name().to_value(), @@ -759,13 +764,7 @@ impl LpImage { // Reset background color for reloads self.set_background_color(None); - let (decoder, mut decoder_update) = match Decoder::new(file.clone(), tiles.clone()).await { - Ok(x) => x, - Err(err) => { - self.set_error(err); - return; - } - }; + let (decoder_res, mut decoder_update) = Decoder::new(file.clone(), tiles.clone()).await; let weak_obj = self.downgrade(); spawn(async move { @@ -777,8 +776,10 @@ impl LpImage { log::debug!("Stopped listening to decoder since sender is gone"); }); - let decoder = Arc::new(decoder); - self.imp().decoder.replace(Some(decoder)); + if let Ok(decoder) = decoder_res { + let decoder = Arc::new(decoder); + self.imp().decoder.replace(Some(decoder)); + } } /// Called when decoder sends update @@ -837,6 +838,12 @@ impl LpImage { imp.tick_callback.replace(Some(callback_id)); } } + DecoderUpdate::UnsupportedFormat => { + if !self.is_unsupported() { + imp.is_unsupported.set(true); + self.notify("is-unsupported"); + } + } } } @@ -874,6 +881,10 @@ impl LpImage { self.imp().is_deleted.get() } + pub fn is_unsupported(&self) -> bool { + self.imp().is_unsupported.get() + } + /// Zoom level that makes the image fit in widget /// /// During image rotation the image does not actually fit into widget. diff --git a/src/widgets/image_view.rs b/src/widgets/image_view.rs index 4f8e506..fbb9739 100644 --- a/src/widgets/image_view.rs +++ b/src/widgets/image_view.rs @@ -256,7 +256,7 @@ impl LpImageView { if let Some(first) = files.first().cloned() { let model = LpFileModel::from_files(files); self.set_model(model); - let page = LpImagePage::from_file(&first); + let page = self.new_image_page(&first); sliding_view.clear(); sliding_view.append(&page); @@ -356,7 +356,7 @@ impl LpImageView { let page = if let Some(page) = sliding_view.get(new_file) { page } else { - let page = LpImagePage::from_file(new_file); + let page = self.new_image_page(new_file); if new_index > current_index { sliding_view.append(&page); @@ -397,7 +397,7 @@ impl LpImageView { if let Some(page) = existing.get(&file.uri()) { sliding_view.move_to(page, position); } else { - sliding_view.insert(&LpImagePage::from_file(file), position); + sliding_view.insert(&self.new_image_page(file), position); } } @@ -438,6 +438,32 @@ impl LpImageView { self.imp().model.borrow().clone() } + /// Create new image that communicates updates to file model + fn new_image_page(&self, file: &gio::File) -> LpImagePage { + let page = LpImagePage::from_file(file); + + page.image().connect_notify_local( + Some("is-unsupported"), + glib::clone!(@weak self as obj => move |image, _| { + if image.is_unsupported() { + if obj.current_image().as_ref() == Some(image) { + log::debug!( + "Image format unsupported but not removing since current image" + ); + return; + } + + if let Some(file) = image.file() { + log::debug!("Removing image with unsupported format {:?}", file.uri()); + obj.model().remove(&file); + } + } + }), + ); + + page + } + fn set_model(&self, model: LpFileModel) { model.connect_changed( glib::clone!(@weak self as obj => move || obj.model_content_changed_cb()),