metadata: Drop unreliable mime-type

Only use a reliable mime-type. This requires that we load images earlier
in the pipe.

This also fixes warnings about mime types not being available on network
drives like SMB.

Fixes #513
Issue #534
This commit is contained in:
Sophie Herold 2025-12-03 22:35:13 +01:00
parent 40d68e898d
commit 3b11b0534e
8 changed files with 57 additions and 68 deletions

View file

@ -0,0 +1 @@
Showing wrong mime type in error message if the file ending for an image is wrong.

View file

@ -124,7 +124,11 @@ impl UpdateSender {
} }
pub fn send_error(&self, err: glycin::ErrorCtx) { pub fn send_error(&self, err: glycin::ErrorCtx) {
if err.unsupported_format().is_some() { if let Some(mime_type) = err.unsupported_format() {
let mut metadata = Metadata::default();
metadata.set_mime_type(mime_type);
self.send(DecoderUpdate::Metadata(Box::new(metadata)));
self.send(DecoderUpdate::SpecificError( self.send(DecoderUpdate::SpecificError(
DecoderError::UnsupportedFormat, DecoderError::UnsupportedFormat,
)); ));
@ -150,6 +154,7 @@ pub struct Decoder {
enum FormatDecoder { enum FormatDecoder {
Glycin(Glycin), Glycin(Glycin),
Svg(Svg), Svg(Svg),
Failed,
} }
impl Decoder { impl Decoder {
@ -159,7 +164,6 @@ impl Decoder {
/// The renderer should listen to updates from the returned receiver. /// The renderer should listen to updates from the returned receiver.
pub async fn new( pub async fn new(
file: gio::File, file: gio::File,
mime_type: Option<String>,
tiles: Arc<SharedFrameBuffer>, tiles: Arc<SharedFrameBuffer>,
) -> (Self, mpsc::Receiver<DecoderUpdate>) { ) -> (Self, mpsc::Receiver<DecoderUpdate>) {
let (sender, receiver) = mpsc::unbounded(); let (sender, receiver) = mpsc::unbounded();
@ -167,7 +171,26 @@ impl Decoder {
let update_sender = UpdateSender { sender }; let update_sender = UpdateSender { sender };
tiles.set_update_sender(update_sender.clone()); tiles.set_update_sender(update_sender.clone());
let format_decoder = Self::format_decoder(update_sender.clone(), file, mime_type, tiles); log::trace!("Setting up loader");
let mut loader = glycin::Loader::new(file);
loader.apply_transformations(false);
let image = match loader.load().await {
Ok(image) => image,
Err(err) => {
update_sender.send_error(err);
return (
Self {
decoder: FormatDecoder::Failed,
update_sender,
},
receiver,
);
}
};
let format_decoder = Self::format_decoder(update_sender.clone(), image, tiles);
let decoder = Self { let decoder = Self {
decoder: format_decoder, decoder: format_decoder,
update_sender, update_sender,
@ -178,22 +201,21 @@ impl Decoder {
fn format_decoder( fn format_decoder(
update_sender: UpdateSender, update_sender: UpdateSender,
file: gio::File, image: glycin::Image,
mime_type: Option<String>,
tiles: Arc<SharedFrameBuffer>, tiles: Arc<SharedFrameBuffer>,
) -> FormatDecoder { ) -> FormatDecoder {
if let Some(mime_type) = mime_type { let mime_type = image.mime_type().to_string();
// Known things we want to match here are // Known things we want to match here are
// - image/svg+xml // - image/svg+xml
// - image/svg+xml-compressed // - image/svg+xml-compressed
if mime_type.split('+').next() == Some("image/svg") { if mime_type.split('+').next() == Some("image/svg") {
return FormatDecoder::Svg(Svg::new(file, update_sender, tiles)); return FormatDecoder::Svg(Svg::new(image, update_sender, tiles));
} else {
FormatDecoder::Glycin(Glycin::new(image, update_sender, tiles))
} }
} }
FormatDecoder::Glycin(Glycin::new(file, update_sender, tiles))
}
/// Request missing tiles /// Request missing tiles
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 {

View file

@ -42,22 +42,12 @@ impl Drop for Glycin {
} }
impl Glycin { impl Glycin {
pub fn new(file: gio::File, updater: UpdateSender, tiles: Arc<SharedFrameBuffer>) -> Self { pub fn new(image: glycin::Image, updater: UpdateSender, tiles: Arc<SharedFrameBuffer>) -> Self {
let cancellable = gio::Cancellable::new(); let cancellable = image.cancellable();
let cancellable_ = cancellable.clone();
let (next_frame_send, next_frame_recv) = async_channel::bounded(2); let (next_frame_send, next_frame_recv) = async_channel::bounded(2);
updater.clone().spawn_error_handled(async move { updater.clone().spawn_error_handled(async move {
log::trace!("Setting up loader");
let mut loader = glycin::Loader::new(file);
loader.apply_transformations(false);
loader.cancellable(cancellable_);
let image = loader.load().await?;
log::trace!("Image info received");
let mut metadata: Metadata = Metadata::default(); let mut metadata: Metadata = Metadata::default();
metadata.set_image_info(image.details().clone()); metadata.set_image_info(image.details().clone());
metadata.set_mime_type(image.mime_type().to_string()); metadata.set_mime_type(image.mime_type().to_string());

View file

@ -61,21 +61,14 @@ impl Drop for Svg {
} }
impl Svg { impl Svg {
pub fn new(file: gio::File, updater: UpdateSender, tiles: Arc<SharedFrameBuffer>) -> Self { pub fn new(image: glycin::Image, updater: UpdateSender, tiles: Arc<SharedFrameBuffer>) -> Self {
let current_request: Arc<std::sync::RwLock<Request>> = Default::default(); let current_request: Arc<std::sync::RwLock<Request>> = Default::default();
let request_store = current_request.clone(); let request_store = current_request.clone();
let cancellable = gio::Cancellable::new(); let cancellable = image.cancellable();
let cancellable_ = cancellable.clone(); let cancellable_ = cancellable.clone();
let (wakeup, wakeup_resv) = mpsc::unbounded(); let (wakeup, wakeup_resv) = mpsc::unbounded();
updater.clone().spawn_error_handled(async move { updater.clone().spawn_error_handled(async move {
log::trace!("Setting up SVG loader");
let mut loader = glycin::Loader::new(file);
loader.cancellable(cancellable.clone());
let image = loader.load().await?;
let mut metadata: Metadata = Metadata::default(); let mut metadata: Metadata = Metadata::default();
metadata.set_image_info(image.details().clone()); metadata.set_image_info(image.details().clone());
metadata.set_mime_type(image.mime_type().to_string()); metadata.set_mime_type(image.mime_type().to_string());

View file

@ -116,25 +116,11 @@ impl Metadata {
self.mime_type.clone() self.mime_type.clone()
} }
/// Unreliable mime type
///
/// Before the image is successfully loaded, this mime type might be wrong
/// since it is not yet based on the more complex mime type detection used
/// by glyin.
pub fn unreliable_mime_type(&self) -> Option<String> {
self.mime_type().or_else(|| {
self.file_info
.as_ref()
.and_then(|x| x.mime_type.as_ref())
.map(|x| x.to_string())
})
}
pub fn format_name(&self) -> Option<String> { pub fn format_name(&self) -> Option<String> {
self.image_info self.image_info
.as_ref() .as_ref()
.and_then(|x| x.info_format_name().map(|x| x.to_string())) .and_then(|x| x.info_format_name().map(|x| x.to_string()))
.or_else(|| self.unreliable_mime_type()) .or_else(|| self.mime_type())
} }
pub fn alpha_channel(&self) -> Option<bool> { pub fn alpha_channel(&self) -> Option<bool> {

View file

@ -23,7 +23,6 @@ const ATTRIBUTE_HOST_PATH: &str = "xattr::document-portal.host-path";
pub struct FileInfo { pub struct FileInfo {
pub(super) display_name: glib::GString, pub(super) display_name: glib::GString,
pub(super) file_size: Option<u64>, pub(super) file_size: Option<u64>,
pub(super) mime_type: Option<glib::GString>,
pub(super) created: Option<glib::DateTime>, pub(super) created: Option<glib::DateTime>,
pub(super) modified: Option<glib::DateTime>, pub(super) modified: Option<glib::DateTime>,
pub(super) host_path: Option<glib::GString>, pub(super) host_path: Option<glib::GString>,
@ -50,9 +49,6 @@ impl FileInfo {
Ok(Self { Ok(Self {
display_name: file_info.display_name(), display_name: file_info.display_name(),
file_size, file_size,
mime_type: file_info
.content_type()
.and_then(|x| gio::content_type_get_mime_type(&x)),
created: file_info.creation_date_time(), created: file_info.creation_date_time(),
modified: file_info.modification_date_time(), modified: file_info.modification_date_time(),
host_path: file_info.attribute_as_string(ATTRIBUTE_HOST_PATH), host_path: file_info.attribute_as_string(ATTRIBUTE_HOST_PATH),

View file

@ -32,17 +32,22 @@ impl imp::LpImage {
} }
/// Set filename after filename changed /// Set filename after filename changed
async fn set_file_changed(&self, file: &gio::File) { async fn set_file_changed(&self, old_file: &gio::File, new_file: &gio::File) {
let obj = self.obj(); let obj = self.obj();
let prev_mime_type = obj.metadata().unreliable_mime_type(); let old_filename_ext = old_file
.basename()
.map(|x| x.extension().map(|x| x.to_owned()));
let new_filename_ext = new_file
.basename()
.map(|x| x.extension().map(|x| x.to_owned()));
self.file.replace(Some(file.clone())); self.file.replace(Some(new_file.clone()));
self.reload_file_info().await; self.reload_file_info().await;
// Reload if mime type changed since other decoding path might be responsible // Reload if file extension changed since other decoding path might be responsible
// now // now
if obj.metadata().unreliable_mime_type() != prev_mime_type { if old_filename_ext != new_filename_ext {
obj.load(file).await; obj.load(new_file).await;
return; return;
} }
@ -223,6 +228,7 @@ impl imp::LpImage {
obj.load(&file).await; obj.load(&file).await;
}); });
} else { } else {
let old_file = file_a.clone();
glib::spawn_future_local(async move { glib::spawn_future_local(async move {
if obj.error().is_some() { if obj.error().is_some() {
// Tmp files might be renamed quickly after creation. In this case // Tmp files might be renamed quickly after creation. In this case
@ -230,7 +236,7 @@ impl imp::LpImage {
// Therefore reload on name change loading caused an error. // Therefore reload on name change loading caused an error.
obj.load(&file).await; obj.load(&file).await;
} else { } else {
obj.imp().set_file_changed(&file).await; obj.imp().set_file_changed(&old_file, &file).await;
} }
}); });
} }
@ -365,12 +371,7 @@ impl LpImage {
// Reset background color for reloads // Reset background color for reloads
imp.set_background_color(None); imp.set_background_color(None);
let (decoder, decoder_update) = Decoder::new( let (decoder, decoder_update) = Decoder::new(file.clone(), tiles.clone()).await;
file.clone(),
self.metadata().unreliable_mime_type(),
tiles.clone(),
)
.await;
let weak_obj = self.downgrade(); let weak_obj = self.downgrade();
glib::spawn_future_local(async move { glib::spawn_future_local(async move {

View file

@ -231,7 +231,7 @@ impl LpImagePage {
imp.stack.set_visible_child(&*imp.error_page); imp.stack.set_visible_child(&*imp.error_page);
imp.error_more_info.set_visible(false); imp.error_more_info.set_visible(false);
} else if image.specific_error() == DecoderError::UnsupportedFormat { } else if image.specific_error() == DecoderError::UnsupportedFormat {
let mime_type = image.metadata().unreliable_mime_type().unwrap_or_default(); let mime_type = image.metadata().mime_type().unwrap_or_default();
let content_type = gio::content_type_from_mime_type(&mime_type).unwrap_or_default(); let content_type = gio::content_type_from_mime_type(&mime_type).unwrap_or_default();
let description = gio::content_type_get_description(&content_type).to_string(); let description = gio::content_type_get_description(&content_type).to_string();