image_page: Specific error for file loading
Give a specific error message if loading the source file fails. Closes #487
This commit is contained in:
parent
b9668c7262
commit
7151cf5d1d
8 changed files with 123 additions and 122 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
|
@ -881,8 +881,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glycin"
|
name = "glycin"
|
||||||
version = "3.0.7"
|
version = "3.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://gitlab.gnome.org/GNOME/glycin#f34faa00c480b7809ceb8503ec85282446206277"
|
||||||
checksum = "502ac59dc785b95521e7deea20e292f3501a11752600c4f37cf34676f794b4e3"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-fs",
|
"async-fs",
|
||||||
"async-global-executor",
|
"async-global-executor",
|
||||||
|
|
@ -918,8 +917,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glycin-common"
|
name = "glycin-common"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://gitlab.gnome.org/GNOME/glycin#f34faa00c480b7809ceb8503ec85282446206277"
|
||||||
checksum = "6e9b31c7fa5ccb9d91317c956e767daef59be9fee3ae9df30468470368678482"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"gufo-common",
|
"gufo-common",
|
||||||
|
|
@ -937,8 +935,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glycin-utils"
|
name = "glycin-utils"
|
||||||
version = "4.0.4"
|
version = "4.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://gitlab.gnome.org/GNOME/glycin#f34faa00c480b7809ceb8503ec85282446206277"
|
||||||
checksum = "7dfec0cd29d5ecfb1dd723868e22e2c49441020f35d5f14307018ff0a1679e2f"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-lock",
|
"async-lock",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,10 @@ chrono = { version = "0.4.38", default-features = false, features = ["std"] }
|
||||||
gettext-rs = { version = "0.7.0", features = ["gettext-system"] }
|
gettext-rs = { version = "0.7.0", features = ["gettext-system"] }
|
||||||
gettext-sys = { version = "0.26.0", features = ["gettext-system"] }
|
gettext-sys = { version = "0.26.0", features = ["gettext-system"] }
|
||||||
gio = { version = "0.21.0", features = ["v2_74"] }
|
gio = { version = "0.21.0", features = ["v2_74"] }
|
||||||
glycin = { version = "3.0.4", features = ["gdk4", "unstable-config"] }
|
glycin = { version = "3.0.4", features = [
|
||||||
|
"gdk4",
|
||||||
|
"unstable-config",
|
||||||
|
], git = "https://gitlab.gnome.org/GNOME/glycin" }
|
||||||
gtk = { package = "gtk4", version = "0.10.0", features = [
|
gtk = { package = "gtk4", version = "0.10.0", features = [
|
||||||
"v4_14",
|
"v4_14",
|
||||||
"xml_validation",
|
"xml_validation",
|
||||||
|
|
|
||||||
1
news.d/50.alpha/added-error-message-file-loading
Normal file
1
news.d/50.alpha/added-error-message-file-loading
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Add a specific error message when loading the image file fails. Usually when the file does not exists or there are no rights to access the file.
|
||||||
|
|
@ -59,32 +59,21 @@ pub enum DecoderUpdate {
|
||||||
Redraw,
|
Redraw,
|
||||||
/// Start animation
|
/// Start animation
|
||||||
Animated,
|
Animated,
|
||||||
Error(DecoderError),
|
Error(Option<DecoderError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum DecoderError {
|
pub enum DecoderError {
|
||||||
/// Image format not supported or unknown
|
/// Image format not supported or unknown
|
||||||
UnsupportedFormat,
|
UnsupportedFormat(String, glycin::ErrorCtx),
|
||||||
/// No glycin-loaders installed
|
/// No glycin-loaders installed
|
||||||
NoLoadersConfigured,
|
NoLoadersConfigured(glycin::ErrorCtx),
|
||||||
/// Memory limit exceeded
|
/// Memory limit exceeded
|
||||||
OutOfMemory,
|
OutOfMemory(glycin::ErrorCtx),
|
||||||
|
/// Failed to load image
|
||||||
|
ImageSource(glib::Error, glycin::ErrorCtx),
|
||||||
|
|
||||||
Generic(String),
|
Generic(glycin::ErrorCtx),
|
||||||
//ImageSource(String),
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DecoderError {
|
|
||||||
pub fn is_err(&self) -> bool {
|
|
||||||
matches!(self, Self::None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_ok(&self) -> bool {
|
|
||||||
!matches!(self, Self::None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdateSender {
|
impl UpdateSender {
|
||||||
|
|
@ -116,27 +105,34 @@ impl UpdateSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_error(&self, err: glycin::ErrorCtx) {
|
pub fn send_error(&self, err: glycin::ErrorCtx) {
|
||||||
dbg!(&err);
|
|
||||||
if let Some(mime_type) = err.unsupported_format() {
|
if let Some(mime_type) = err.unsupported_format() {
|
||||||
let mut metadata = Metadata::default();
|
let mut metadata = Metadata::default();
|
||||||
metadata.set_mime_type(mime_type);
|
metadata.set_mime_type(mime_type.clone());
|
||||||
|
|
||||||
self.send(DecoderUpdate::Metadata(Box::new(metadata)));
|
self.send(DecoderUpdate::Metadata(Box::new(metadata)));
|
||||||
self.send(DecoderUpdate::Error(DecoderError::UnsupportedFormat));
|
self.send(DecoderUpdate::Error(Some(DecoderError::UnsupportedFormat(
|
||||||
}
|
mime_type, err,
|
||||||
if err.is_out_of_memory() {
|
))));
|
||||||
self.send(DecoderUpdate::Error(DecoderError::OutOfMemory));
|
} else if err.is_out_of_memory() {
|
||||||
|
self.send(DecoderUpdate::Error(Some(DecoderError::OutOfMemory(err))));
|
||||||
} else if matches!(err.error(), glycin::Error::NoLoadersConfigured(_)) {
|
} else if matches!(err.error(), glycin::Error::NoLoadersConfigured(_)) {
|
||||||
self.send(DecoderUpdate::Error(DecoderError::NoLoadersConfigured));
|
self.send(DecoderUpdate::Error(Some(
|
||||||
|
DecoderError::NoLoadersConfigured(err),
|
||||||
|
)));
|
||||||
|
} else if let glycin::Error::ImageSource(glib_err) = err.error() {
|
||||||
|
self.send(DecoderUpdate::Error(Some(DecoderError::ImageSource(
|
||||||
|
glib_err.clone(),
|
||||||
|
err,
|
||||||
|
))));
|
||||||
|
} else {
|
||||||
|
self.send(DecoderUpdate::Error(Some(DecoderError::Generic(err))));
|
||||||
}
|
}
|
||||||
self.send(DecoderUpdate::Error(DecoderError::Generic(err.to_string())));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Decoder {
|
pub struct Decoder {
|
||||||
decoder: FormatDecoder,
|
decoder: FormatDecoder,
|
||||||
update_sender: UpdateSender,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -171,7 +167,6 @@ impl Decoder {
|
||||||
return (
|
return (
|
||||||
Self {
|
Self {
|
||||||
decoder: FormatDecoder::Failed,
|
decoder: FormatDecoder::Failed,
|
||||||
update_sender,
|
|
||||||
},
|
},
|
||||||
receiver,
|
receiver,
|
||||||
);
|
);
|
||||||
|
|
@ -182,7 +177,6 @@ impl Decoder {
|
||||||
|
|
||||||
let decoder = Self {
|
let decoder = Self {
|
||||||
decoder: format_decoder,
|
decoder: format_decoder,
|
||||||
update_sender,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
(decoder, receiver)
|
(decoder, receiver)
|
||||||
|
|
@ -209,8 +203,7 @@ impl Decoder {
|
||||||
pub fn request(&self, tile_request: TileRequest) {
|
pub fn request(&self, tile_request: TileRequest) {
|
||||||
if let FormatDecoder::Svg(svg) = &self.decoder {
|
if let FormatDecoder::Svg(svg) = &self.decoder {
|
||||||
if let Err(err) = svg.request(tile_request) {
|
if let Err(err) = svg.request(tile_request) {
|
||||||
self.update_sender
|
tracing::error!("Failed to request new SVG tiles: {err}");
|
||||||
.send(DecoderUpdate::Error(DecoderError::Generic(err.to_string())));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ use glib::subclass::Signal;
|
||||||
use glib::{Properties, SignalGroup};
|
use glib::{Properties, SignalGroup};
|
||||||
use gufo_common::orientation::Orientation;
|
use gufo_common::orientation::Orientation;
|
||||||
|
|
||||||
|
use crate::decoder::tiling::SharedFrameBuffer;
|
||||||
use crate::decoder::{self, Decoder, DecoderUpdate, tiling};
|
use crate::decoder::{self, Decoder, DecoderUpdate, tiling};
|
||||||
use crate::deps::*;
|
use crate::deps::*;
|
||||||
use crate::metadata::Metadata;
|
use crate::metadata::Metadata;
|
||||||
|
|
@ -137,13 +138,11 @@ pub enum FitMode {
|
||||||
ExactVolatile,
|
ExactVolatile,
|
||||||
}
|
}
|
||||||
|
|
||||||
mod imp {
|
use decoder::DecoderError;
|
||||||
use decoder::DecoderError;
|
use glycin::Operations;
|
||||||
use glycin::Operations;
|
|
||||||
|
|
||||||
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::decoder::tiling::SharedFrameBuffer;
|
|
||||||
use crate::editing;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Properties)]
|
#[derive(Debug, Default, Properties)]
|
||||||
#[properties(wrapper_type = super::LpImage)]
|
#[properties(wrapper_type = super::LpImage)]
|
||||||
|
|
@ -155,9 +154,9 @@ mod imp {
|
||||||
#[property(get)]
|
#[property(get)]
|
||||||
pub(super) is_deleted: Cell<bool>,
|
pub(super) is_deleted: Cell<bool>,
|
||||||
/// Set if an error has occurred, shown on error_page
|
/// Set if an error has occurred, shown on error_page
|
||||||
pub(super) specific_error: RefCell<DecoderError>,
|
pub(super) specific_error: RefCell<Option<DecoderError>>,
|
||||||
#[property(get=Self::is_error)]
|
#[property(get=Self::is_error)]
|
||||||
pub(super) is_error: Cell<bool>,
|
pub(super) is_error: PhantomData<bool>,
|
||||||
/// Set to true when image is ready for displaying
|
/// Set to true when image is ready for displaying
|
||||||
#[property(get)]
|
#[property(get)]
|
||||||
pub(super) is_loaded: Cell<bool>,
|
pub(super) is_loaded: Cell<bool>,
|
||||||
|
|
@ -257,7 +256,7 @@ mod imp {
|
||||||
pub(super) nth_snapshot: Cell<u8>,
|
pub(super) nth_snapshot: Cell<u8>,
|
||||||
|
|
||||||
/// Editing queue
|
/// Editing queue
|
||||||
pub(super) editing_queue: editing::Queue,
|
pub(super) editing_queue: crate::editing::Queue,
|
||||||
pub(super) operations: RefCell<Option<Arc<Operations>>>,
|
pub(super) operations: RefCell<Option<Arc<Operations>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ impl imp::LpImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_error(&self) -> bool {
|
pub(super) fn is_error(&self) -> bool {
|
||||||
self.specific_error.borrow().is_err()
|
self.specific_error.borrow().is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_loaded(&self, is_loaded: bool) {
|
fn set_loaded(&self, is_loaded: bool) {
|
||||||
|
|
@ -279,7 +279,7 @@ impl imp::LpImage {
|
||||||
obj.notify_is_loaded();
|
obj.notify_is_loaded();
|
||||||
|
|
||||||
if is_loaded {
|
if is_loaded {
|
||||||
obj.set_specific_error(DecoderError::None);
|
obj.set_specific_error(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -357,20 +357,19 @@ impl LpImage {
|
||||||
imp.decoder.replace(Some(Arc::new(decoder)));
|
imp.decoder.replace(Some(Arc::new(decoder)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn specific_error(&self) -> DecoderError {
|
pub fn specific_error(&self) -> Option<DecoderError> {
|
||||||
self.imp().specific_error.borrow().clone()
|
self.imp().specific_error.borrow().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_specific_error(&self, error: DecoderError) {
|
pub fn set_specific_error(&self, error: Option<DecoderError>) {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
|
|
||||||
// Keeping first error instead of replacing with new error
|
// Keeping first error instead of replacing with new error or return
|
||||||
if error.is_err() && self.is_error() {
|
if error.is_some() == self.is_error() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.specific_error() != error {
|
let is_err = error.is_some();
|
||||||
let is_err = error.is_err();
|
|
||||||
imp.specific_error.replace(error);
|
imp.specific_error.replace(error);
|
||||||
self.notify_is_error();
|
self.notify_is_error();
|
||||||
|
|
||||||
|
|
@ -378,5 +377,4 @@ impl LpImage {
|
||||||
imp.set_loaded(false);
|
imp.set_loaded(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -214,72 +214,79 @@ impl LpImagePage {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
let image = self.image();
|
let image = self.image();
|
||||||
|
|
||||||
match image.specific_error() {
|
if let Some(error) = image.specific_error() {
|
||||||
DecoderError::NoLoadersConfigured => {
|
let details;
|
||||||
|
let description;
|
||||||
|
|
||||||
|
match error {
|
||||||
|
DecoderError::NoLoadersConfigured(err) => {
|
||||||
// Translators: {} is replaced with a version number
|
// Translators: {} is replaced with a version number
|
||||||
let message = gettext_f(
|
description = gettext_f(
|
||||||
"No image loaders available. Maybe the “glycin-loaders” package with compatibility version “{}” is not installed.",
|
"No image loaders available. Maybe the “glycin-loaders” package with compatibility version “{}” is not installed.",
|
||||||
[format!("{}+", glycin::COMPAT_VERSION)],
|
[format!("{}+", glycin::COMPAT_VERSION)],
|
||||||
);
|
);
|
||||||
|
|
||||||
imp.error_page.set_description(Some(&message));
|
details = err.to_string();
|
||||||
imp.stack.set_visible_child(&*imp.error_page);
|
|
||||||
imp.error_more_info.set_visible(false);
|
|
||||||
}
|
}
|
||||||
DecoderError::UnsupportedFormat => {
|
DecoderError::UnsupportedFormat(mime_type, err) => {
|
||||||
let mime_type = image.metadata().mime_type().unwrap_or_default();
|
let content_type =
|
||||||
let content_type = gio::content_type_from_mime_type(&mime_type).unwrap_or_default();
|
gio::content_type_from_mime_type(&mime_type).unwrap_or_default();
|
||||||
let description = gio::content_type_get_description(&content_type).to_string();
|
let mime_type_description =
|
||||||
|
gio::content_type_get_description(&content_type).to_string();
|
||||||
|
|
||||||
let message = if glycin::Loader::DEFAULT_MIME_TYPES.contains(&mime_type.as_str()) {
|
description =
|
||||||
// Translators: The first occurance of {} is replace with a description of the
|
if glycin::Loader::DEFAULT_MIME_TYPES.contains(&mime_type.as_str()) {
|
||||||
// format and the second with an id (mime-type) of the format.
|
// Translators: The first occurance of {} is replace with a description
|
||||||
|
// of the format and the second with an id
|
||||||
|
// (mime-type) of the format.
|
||||||
gettext_f(
|
gettext_f(
|
||||||
"The image format “{} ({})” is known but not installed.",
|
"The image format “{} ({})” is known but not installed.",
|
||||||
[&description, &mime_type],
|
[&mime_type_description, &mime_type],
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Translators: The first occurance of {} is replace with a description of the
|
// Translators: The first occurance of {} is replace with a description
|
||||||
// format and the second with an id (mime-type) of the format.
|
// of the format and the second with an id
|
||||||
|
// (mime-type) of the format.
|
||||||
gettext_f(
|
gettext_f(
|
||||||
"Unknown image format “{} ({}).”",
|
"Unknown image format “{} ({}).”",
|
||||||
[&description, &mime_type],
|
[&mime_type_description, &mime_type],
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
imp.error_more_info.connect_clicked(glib::clone!(
|
details = err.to_string();
|
||||||
#[weak(rename_to = obj)]
|
|
||||||
self,
|
|
||||||
move |_| {
|
|
||||||
LpErrorDetails::new(&obj.root().unwrap(), &err, ErrorType::Loader);
|
|
||||||
}
|
}
|
||||||
));
|
DecoderError::OutOfMemory(err) => {
|
||||||
|
description =
|
||||||
imp.error_page.set_description(Some(&message));
|
gettext("There is not enough free memory available to load this image.");
|
||||||
imp.stack.set_visible_child(&*imp.error_page);
|
details = err.to_string();
|
||||||
|
}
|
||||||
|
DecoderError::ImageSource(glib_err, err) => {
|
||||||
|
// Translators: The first occurance of {} is replace with a
|
||||||
|
// translated, more detailled error message.
|
||||||
|
description =
|
||||||
|
gettext_f("Failed to load image file: {}", [&glib_err.to_string()]);
|
||||||
|
details = err.to_string();
|
||||||
}
|
}
|
||||||
DecoderError::Generic(err) => {
|
DecoderError::Generic(err) => {
|
||||||
|
description = gettext(
|
||||||
|
"Either the image file is corrupted or it contains unsupported elements.",
|
||||||
|
);
|
||||||
|
details = err.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
imp.error_more_info.connect_clicked(glib::clone!(
|
imp.error_more_info.connect_clicked(glib::clone!(
|
||||||
#[weak(rename_to = obj)]
|
#[weak(rename_to = obj)]
|
||||||
self,
|
self,
|
||||||
move |_| {
|
move |_| {
|
||||||
LpErrorDetails::new(&obj.root().unwrap(), &err, ErrorType::Loader);
|
LpErrorDetails::new(&obj.root().unwrap(), &details, ErrorType::Loader);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
if matches!(image.specific_error(), DecoderError::OutOfMemory) {
|
imp.error_page.set_description(Some(&description));
|
||||||
imp.error_page.set_description(Some(&gettext(
|
|
||||||
"There is not enough free memory available to load this image.",
|
|
||||||
)));
|
|
||||||
} else {
|
|
||||||
imp.error_page.set_description(Some(&gettext(
|
|
||||||
"Either the image file is corrupted or it contains unsupported elements.",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
imp.error_more_info.set_visible(true);
|
imp.error_more_info.set_visible(true);
|
||||||
imp.stack.set_visible_child(&*imp.error_page);
|
imp.stack.set_visible_child(&*imp.error_page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -650,7 +650,10 @@ impl LpImageView {
|
||||||
#[weak(rename_to = obj)]
|
#[weak(rename_to = obj)]
|
||||||
self,
|
self,
|
||||||
move |image| {
|
move |image| {
|
||||||
if matches!(image.specific_error(), DecoderError::UnsupportedFormat) {
|
if matches!(
|
||||||
|
image.specific_error(),
|
||||||
|
Some(DecoderError::UnsupportedFormat(_, _))
|
||||||
|
) {
|
||||||
if obj.current_image().as_ref() == Some(image) {
|
if obj.current_image().as_ref() == Some(image) {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Image format unsupported but not removing since current image"
|
"Image format unsupported but not removing since current image"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue