decoder: Use GFile instead of PathBuf

This allows to show images from locations that don't
have a representation with path.

This also works around not being able to read files in trash:///

Closes #56

Part-of: <https://gitlab.gnome.org/Incubator/loupe/-/merge_requests/174>
This commit is contained in:
Sophie Herold 2023-04-12 04:13:30 +02:00 • committed by Marge Bot
parent 032707a210
commit fad8bb7939
15 changed files with 119 additions and 107 deletions

View file

@ -59,7 +59,7 @@
<property name="title" translatable="yes">Loupe</property> <property name="title" translatable="yes">Loupe</property>
<binding name="title"> <binding name="title">
<closure type="gchararray" function="window_title"> <closure type="gchararray" function="window_title">
<lookup name="path"> <lookup name="file">
<lookup name="image"> <lookup name="image">
<lookup name="current-page">image_view</lookup> <lookup name="current-page">image_view</lookup>
</lookup> </lookup>

View file

@ -73,7 +73,7 @@ mod imp {
let application = self.obj(); let application = self.obj();
for file in files { for file in files {
let win = LpWindow::new(&*application); let win = LpWindow::new(&*application);
win.set_image_from_file(&file); win.set_image_from_file(file);
} }
} }
} }

View file

@ -3,14 +3,14 @@ use super::*;
use crate::decoder::tiling::{self, FrameBufferExt}; use crate::decoder::tiling::{self, FrameBufferExt};
use crate::deps::*; use crate::deps::*;
use crate::util::gettext::*; use crate::util::gettext::*;
use gtk::prelude::*; use crate::util::ToBufRead;
use anyhow::Context; use anyhow::Context;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use gtk::prelude::*;
use libheif_rs::{ColorSpace, HeifContext, LibHeif, Plane, RgbChroma}; use libheif_rs::{ColorSpace, HeifContext, LibHeif, Plane, RgbChroma};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
#[derive(Debug)] #[derive(Debug)]
@ -21,14 +21,24 @@ static LIBHEIF: Lazy<LibHeif> = Lazy::new(LibHeif::new);
impl Heif { impl Heif {
pub fn new( pub fn new(
path: PathBuf, file: gio::File,
updater: UpdateSender, updater: UpdateSender,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>, tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
) -> Self { ) -> Self {
log::debug!("Loading HEIF"); log::debug!("Loading HEIF");
updater.spawn_error_handled(move || { updater.spawn_error_handled(move || {
let ctx = HeifContext::read_from_file(&path.display().to_string()) let file_size = file
.context(gettext("Failed to read image"))?; .query_info(
gio::FILE_ATTRIBUTE_STANDARD_SIZE,
gio::FileQueryInfoFlags::NONE,
gio::Cancellable::NONE,
)
.context(gettext("Failed to read image file information"))?
.size();
let buf_reader = file.to_buf_read()?;
let stream_reader = libheif_rs::StreamReader::new(buf_reader, file_size as u64);
let ctx = HeifContext::read_from_reader(Box::new(stream_reader))
.context(gettext("Failed to decode image"))?;
let handle = ctx.primary_image_handle()?; let handle = ctx.primary_image_handle()?;
tiles.set_original_dimensions((handle.width(), handle.height())); tiles.set_original_dimensions((handle.width(), handle.height()));

View file

@ -3,15 +3,13 @@ use super::*;
use crate::decoder::tiling::{self, FrameBufferExt}; use crate::decoder::tiling::{self, FrameBufferExt};
use crate::deps::*; use crate::deps::*;
use crate::util::gettext::*; use crate::util::gettext::*;
use crate::util::{BufReadSeek, ToBufRead};
use anyhow::Context; use anyhow::Context;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use gtk::prelude::*; use gtk::prelude::*;
use image_rs::AnimationDecoder; use image_rs::AnimationDecoder;
use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
#[derive(Debug)] #[derive(Debug)]
@ -32,17 +30,17 @@ impl Drop for ImageRsOther {
impl ImageRsOther { impl ImageRsOther {
fn reader( fn reader(
path: &Path, file: &gio::File,
format: image_rs::ImageFormat, format: image_rs::ImageFormat,
) -> anyhow::Result<image_rs::io::Reader<BufReader<File>>> { ) -> anyhow::Result<image_rs::io::Reader<Box<dyn BufReadSeek>>> {
let mut reader = let buf_read = file.to_buf_read()?;
image_rs::io::Reader::open(path).context(gettext("Failed to open image"))?; let mut reader = image_rs::io::Reader::new(buf_read);
reader.set_format(format); reader.set_format(format);
Ok(reader) Ok(reader)
} }
pub fn new( pub fn new(
path: PathBuf, file: gio::File,
format: image_rs::ImageFormat, format: image_rs::ImageFormat,
updater: UpdateSender, updater: UpdateSender,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>, tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
@ -53,14 +51,14 @@ impl ImageRsOther {
image_rs::ImageFormat::Gif image_rs::ImageFormat::Gif
| image_rs::ImageFormat::WebP | image_rs::ImageFormat::WebP
| image_rs::ImageFormat::Png => Some(Self::new_animated( | image_rs::ImageFormat::Png => Some(Self::new_animated(
path, file,
format, format,
updater, updater,
tiles, tiles,
abort.clone(), abort.clone(),
)), )),
_ => { _ => {
Self::new_static(path, format, updater, tiles); Self::new_static(file, format, updater, tiles);
None None
} }
}; };
@ -72,20 +70,20 @@ impl ImageRsOther {
} }
fn new_static( fn new_static(
path: PathBuf, file: gio::File,
format: image_rs::ImageFormat, format: image_rs::ImageFormat,
updater: UpdateSender, updater: UpdateSender,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>, tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
) { ) {
updater.spawn_error_handled(move || { updater.spawn_error_handled(move || {
let reader = Self::reader(&path, format)?; let reader = Self::reader(&file, format)?;
let dimensions = reader let dimensions = reader
.into_dimensions() .into_dimensions()
.context("Failed to read image dimensions")?; .context("Failed to read image dimensions")?;
tiles.set_original_dimensions(dimensions); tiles.set_original_dimensions(dimensions);
let mut reader = Self::reader(&path, format)?; let mut reader = Self::reader(&file, format)?;
reader.limits(image_rs::io::Limits::no_limits()); reader.limits(image_rs::io::Limits::no_limits());
@ -107,7 +105,7 @@ impl ImageRsOther {
} }
fn new_animated( fn new_animated(
path: PathBuf, file: gio::File,
format: image_rs::ImageFormat, format: image_rs::ImageFormat,
updater: UpdateSender, updater: UpdateSender,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>, tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
@ -119,30 +117,30 @@ impl ImageRsOther {
// We are currently decoding for each repetition of the animation // We are currently decoding for each repetition of the animation
// TODO: Check if/how that can be solved differently // TODO: Check if/how that can be solved differently
loop { loop {
let file = std::fs::File::open(&path)?; let buf_reader = file.to_buf_read()?;
let (frames, animated_format) = match format { let (frames, animated_format) = match format {
image_rs::ImageFormat::Gif => ( image_rs::ImageFormat::Gif => (
image_rs::codecs::gif::GifDecoder::new(file)?.into_frames(), image_rs::codecs::gif::GifDecoder::new(buf_reader)?.into_frames(),
ImageFormat::AnimatedGif, ImageFormat::AnimatedGif,
), ),
image_rs::ImageFormat::WebP => { image_rs::ImageFormat::WebP => {
let decoder = image_rs::codecs::webp::WebPDecoder::new(file)?; let decoder = image_rs::codecs::webp::WebPDecoder::new(buf_reader)?;
if decoder.has_animation() { if decoder.has_animation() {
(decoder.into_frames(), ImageFormat::AnimatedWebP) (decoder.into_frames(), ImageFormat::AnimatedWebP)
} else { } else {
// Static WebP images need a different decoder // Static WebP images need a different decoder
Self::new_static(path, format, updater, tiles); Self::new_static(file, format, updater, tiles);
return Ok(()); return Ok(());
} }
} }
image_rs::ImageFormat::Png => { image_rs::ImageFormat::Png => {
let decoder = image_rs::codecs::png::PngDecoder::new(file)?; let decoder = image_rs::codecs::png::PngDecoder::new(buf_reader)?;
if decoder.is_apng() { if decoder.is_apng() {
(decoder.apng().into_frames(), ImageFormat::AnimatedPng) (decoder.apng().into_frames(), ImageFormat::AnimatedPng)
} else { } else {
// Static PNG images need a different decoder // Static PNG images need a different decoder
Self::new_static(path, format, updater, tiles); Self::new_static(file, format, updater, tiles);
return Ok(()); return Ok(());
} }
} }

View file

@ -8,7 +8,6 @@ use anyhow::Context;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use gtk::prelude::*; use gtk::prelude::*;
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
/// Current librsvg limit on maximum dimensions. See /// Current librsvg limit on maximum dimensions. See
@ -45,7 +44,7 @@ impl Drop for Svg {
impl Svg { impl Svg {
pub fn new( pub fn new(
path: PathBuf, file: gio::File,
updater: UpdateSender, updater: UpdateSender,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>, tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
) -> Self { ) -> Self {
@ -53,7 +52,7 @@ impl Svg {
let request_store = current_request.clone(); let request_store = current_request.clone();
let thread_handle = updater.spawn_error_handled(move || { let thread_handle = updater.spawn_error_handled(move || {
let handle = rsvg::Loader::new().read_path(path)?; let handle = rsvg::Loader::new().read_file(&file, gio::Cancellable::NONE)?;
let renderer = rsvg::CairoRenderer::new(&handle); let renderer = rsvg::CairoRenderer::new(&handle);
let (original_width, original_height) = svg_dimensions(&renderer); let (original_width, original_height) = svg_dimensions(&renderer);
@ -85,7 +84,7 @@ impl Svg {
tile_size: tiling::TILE_SIZE, tile_size: tiling::TILE_SIZE,
original_dimensions: (original_width, original_height), original_dimensions: (original_width, original_height),
zoom: tile_request.zoom, zoom: tile_request.zoom,
bleed: 2, bleed: 0,
}; };
let relevant_tiles = tiling.relevant_tiles(&tile_request.viewport); let relevant_tiles = tiling.relevant_tiles(&tile_request.viewport);

View file

@ -7,16 +7,16 @@ pub use formats::ImageDimensionDetails;
use crate::deps::*; use crate::deps::*;
use crate::image_metadata::ImageMetadata; use crate::image_metadata::ImageMetadata;
use crate::util::gettext::*; use crate::util::gettext::*;
use crate::util::ToBufRead;
use formats::*; use formats::*;
use tiling::FrameBufferExt; use tiling::FrameBufferExt;
use anyhow::{anyhow, bail, Context}; use anyhow::{anyhow, bail, Context, Result};
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use futures::channel::mpsc; use futures::channel::mpsc;
use gio::prelude::*; use gio::prelude::*;
use std::io::Read; use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
pub use formats::{ImageFormat, RSVG_MAX_SIZE}; pub use formats::{ImageFormat, RSVG_MAX_SIZE};
@ -95,13 +95,12 @@ impl Decoder {
file: gio::File, file: gio::File,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>, tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
) -> anyhow::Result<(Self, mpsc::UnboundedReceiver<DecoderUpdate>)> { ) -> anyhow::Result<(Self, mpsc::UnboundedReceiver<DecoderUpdate>)> {
let path = file.path().context("Need a file path")?;
let (sender, receiver) = mpsc::unbounded(); let (sender, receiver) = mpsc::unbounded();
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 decoder = gio::spawn_blocking(move || Self::format_decoder(update_sender, path, tiles)) let decoder = gio::spawn_blocking(move || Self::format_decoder(update_sender, file, tiles))
.await .await
.map_err(|_| anyhow!("Constructing the FormatDecoder failed unexpectedly"))??; .map_err(|_| anyhow!("Constructing the FormatDecoder failed unexpectedly"))??;
@ -110,31 +109,23 @@ impl Decoder {
fn format_decoder( fn format_decoder(
update_sender: UpdateSender, update_sender: UpdateSender,
path: PathBuf, file: gio::File,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>, tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
) -> anyhow::Result<FormatDecoder> { ) -> anyhow::Result<FormatDecoder> {
update_sender.send(DecoderUpdate::Metadata(ImageMetadata::load(&path))); update_sender.send(DecoderUpdate::Metadata(ImageMetadata::load(&file)));
let mut buf = Vec::new(); let format = Self::guess_format(&file)?;
let file = std::fs::File::open(&path).context(gettext("Could not open image"))?;
file.take(64)
.read_to_end(&mut buf)
.context(gettext("Could not read image"))?;
// Try magic bytes first and than file name extension if let Some(format) = format {
let format =
image_rs::guess_format(&buf).or_else(|_| image_rs::ImageFormat::from_path(&path));
if let Ok(format) = format {
match format { match format {
image_rs::ImageFormat::Avif => { image_rs::ImageFormat::Avif => {
update_sender.send(DecoderUpdate::Format(ImageFormat::Heif)); update_sender.send(DecoderUpdate::Format(ImageFormat::Heif));
return Ok(FormatDecoder::Heif(Heif::new(path, update_sender, tiles))); return Ok(FormatDecoder::Heif(Heif::new(file, update_sender, tiles)));
} }
format => { format => {
update_sender.send(DecoderUpdate::Format(ImageFormat::ImageRs(format))); update_sender.send(DecoderUpdate::Format(ImageFormat::ImageRs(format)));
return Ok(FormatDecoder::ImageRsOther(ImageRsOther::new( return Ok(FormatDecoder::ImageRsOther(ImageRsOther::new(
path, file,
format, format,
update_sender, update_sender,
tiles, tiles,
@ -142,7 +133,7 @@ impl Decoder {
} }
} }
} else { } else {
let file_info = gio::File::for_path(&path) let file_info = file
.query_info( .query_info(
gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
gio::FileQueryInfoFlags::NONE, gio::FileQueryInfoFlags::NONE,
@ -159,12 +150,12 @@ impl Decoder {
// - image/svg+xml-compressed // - image/svg+xml-compressed
if content_type.split('+').next() == Some("image/svg") { if content_type.split('+').next() == Some("image/svg") {
update_sender.send(DecoderUpdate::Format(ImageFormat::Svg)); update_sender.send(DecoderUpdate::Format(ImageFormat::Svg));
return Ok(FormatDecoder::Svg(Svg::new(path, update_sender, tiles))); return Ok(FormatDecoder::Svg(Svg::new(file, update_sender, tiles)));
} else if ["image/avif", "image/heif", "image/heic"] } else if ["image/avif", "image/heif", "image/heic"]
.contains(&content_type.as_str()) .contains(&content_type.as_str())
{ {
update_sender.send(DecoderUpdate::Format(ImageFormat::Heif)); update_sender.send(DecoderUpdate::Format(ImageFormat::Heif));
return Ok(FormatDecoder::Heif(Heif::new(path, update_sender, tiles))); return Ok(FormatDecoder::Heif(Heif::new(file, update_sender, tiles)));
} else { } else {
bail!(gettext_f( bail!(gettext_f(
"Unknown image format: {}", "Unknown image format: {}",
@ -193,4 +184,25 @@ impl Decoder {
log::error!("Trying to fill frame buffer for decoder without animation support"); log::error!("Trying to fill frame buffer for decoder without animation support");
} }
} }
fn guess_format(file: &gio::File) -> anyhow::Result<Option<image_rs::ImageFormat>> {
let mut buf = Vec::new();
let fs_file = file
.to_buf_read()
.context(gettext("Could not open image"))?;
fs_file
.take(64)
.read_to_end(&mut buf)
.context(gettext("Could not read image"))?;
// Try magic bytes first and than file name extension
if let Ok(format) = image_rs::guess_format(&buf) {
Ok(Some(format))
} else if let Some(basename) = file.basename() {
Ok(image_rs::ImageFormat::from_path(basename).ok())
} else {
Ok(None)
}
}
} }

View file

@ -25,7 +25,7 @@ use anyhow::Context;
use gio::prelude::*; use gio::prelude::*;
use gio::subclass::prelude::*; use gio::subclass::prelude::*;
use glib::GString; use glib::GString;
use indexmap::{IndexMap, IndexSet}; use indexmap::IndexMap;
use once_cell::sync::{Lazy, OnceCell}; use once_cell::sync::{Lazy, OnceCell};
use std::cell::RefCell; use std::cell::RefCell;
@ -35,8 +35,8 @@ mod imp {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct LpFileModel { pub struct LpFileModel {
/// Use and IndexSet such that we can put the elements into an arbitrary order /// Use and IndexMap such that we can put the elements into an arbitrary order
/// while still having fast hash access via `PathBuf`. /// while still having fast hash access via file URI.
pub(super) files: RefCell<IndexMap<GString, gio::File>>, pub(super) files: RefCell<IndexMap<GString, gio::File>>,
pub(super) directory: OnceCell<gio::File>, pub(super) directory: OnceCell<gio::File>,
/// Track file changes. /// Track file changes.
@ -167,7 +167,7 @@ impl LpFileModel {
} }
pub fn before(&self, file: &gio::File) -> Option<gio::File> { pub fn before(&self, file: &gio::File) -> Option<gio::File> {
let index = self.index_of(&file)?; let index = self.index_of(file)?;
self.imp() self.imp()
.files .files
@ -188,7 +188,7 @@ impl LpFileModel {
.cloned() .cloned()
} }
/// Returns `n` elements before and after given path /// Returns `n` elements before and after given file
pub fn files_around(&self, file: &gio::File, n: usize) -> IndexMap<GString, gio::File> { pub fn files_around(&self, file: &gio::File, n: usize) -> IndexMap<GString, gio::File> {
let Some(index) = self.index_of(file) else { let Some(index) = self.index_of(file) else {
log::error!("URI not in model: {}", file.uri()); log::error!("URI not in model: {}", file.uri());
@ -207,12 +207,12 @@ impl LpFileModel {
.collect() .collect()
} }
/// Return first path /// Return first file
pub fn first(&self) -> Option<gio::File> { pub fn first(&self) -> Option<gio::File> {
self.imp().files.borrow().first().map(|x| x.1).cloned() self.imp().files.borrow().first().map(|x| x.1).cloned()
} }
/// Returns last path /// Returns last file
pub fn last(&self) -> Option<gio::File> { pub fn last(&self) -> Option<gio::File> {
self.imp().files.borrow().last().map(|x| x.1).cloned() self.imp().files.borrow().last().map(|x| x.1).cloned()
} }
@ -232,7 +232,7 @@ impl LpFileModel {
fn is_image_file(file: &gio::File) -> bool { fn is_image_file(file: &gio::File) -> bool {
let Ok(info) = util::query_attributes(file, vec![gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE]) else { let Ok(info) = util::query_attributes(file, vec![gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE]) else {
log::warn!("Could not query file info: {:?}", file.path()); log::warn!("Could not query file info: {}", file.uri());
return false; return false;
}; };

View file

@ -9,12 +9,11 @@ mod obj;
pub use gps::GPSLocation; pub use gps::GPSLocation;
pub use obj::LpImageMetadata; pub use obj::LpImageMetadata;
use crate::util;
use crate::util::gettext::*; use crate::util::gettext::*;
use crate::util::{self, ToBufRead};
use gtk::glib; use crate::deps::*;
use gio::prelude::*;
use std::path::Path;
#[derive(Default)] #[derive(Default)]
pub enum ImageMetadata { pub enum ImageMetadata {
@ -45,10 +44,9 @@ impl std::fmt::Debug for ImageMetadata {
} }
impl ImageMetadata { impl ImageMetadata {
pub fn load(path: &Path) -> Self { pub fn load(file: &gio::File) -> Self {
log::debug!("Loading metadata for {:?}", path); log::debug!("Loading metadata for {}", file.uri());
if let Ok(file) = std::fs::File::open(path) { if let Ok(mut bufreader) = file.to_buf_read() {
let mut bufreader = std::io::BufReader::new(&file);
let exifreader = exif::Reader::new(); let exifreader = exif::Reader::new();
if let Ok(exif) = exifreader.read_from_container(&mut bufreader) { if let Ok(exif) = exifreader.read_from_container(&mut bufreader) {

View file

@ -7,17 +7,16 @@ use gtk::glib;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::cell::RefCell; use std::cell::RefCell;
use std::path::Path;
glib::wrapper! { glib::wrapper! {
pub struct LpImageMetadata(ObjectSubclass<imp::LpImageMetadata>); pub struct LpImageMetadata(ObjectSubclass<imp::LpImageMetadata>);
} }
impl LpImageMetadata { impl LpImageMetadata {
pub fn load(path: &Path) -> Self { pub fn load(file: &gio::File) -> Self {
let obj = glib::Object::new::<Self>(); let obj = glib::Object::new::<Self>();
let metadata = ImageMetadata::load(path); let metadata = ImageMetadata::load(file);
obj.imp().metadata.replace(metadata); obj.imp().metadata.replace(metadata);
obj obj

View file

@ -154,6 +154,25 @@ pub async fn untrash(path: &Path) -> anyhow::Result<()> {
error error
} }
pub trait BufReadSeek: std::io::BufRead + std::io::Seek {}
impl BufReadSeek for std::io::BufReader<gio::InputStreamRead<gio::FileInputStream>> {}
pub trait ToBufRead {
fn to_buf_read(&self) -> anyhow::Result<Box<dyn BufReadSeek>>;
}
impl ToBufRead for gio::File {
fn to_buf_read(&self) -> anyhow::Result<Box<dyn BufReadSeek>> {
let read = self
.read(gio::Cancellable::NONE)
.context(gettext("Failed to open image"))?
.into_read();
Ok(Box::new(std::io::BufReader::new(read)))
}
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Position { pub enum Position {
First, First,

View file

@ -32,7 +32,6 @@ use crate::deps::*;
use crate::decoder::tiling::FrameBufferExt; use crate::decoder::tiling::FrameBufferExt;
use crate::decoder::{self, tiling, Decoder, DecoderUpdate}; use crate::decoder::{self, tiling, Decoder, DecoderUpdate};
use crate::image_metadata::LpImageMetadata; use crate::image_metadata::LpImageMetadata;
use crate::util::gettext::*;
use crate::util::Gesture; use crate::util::Gesture;
use crate::util::spawn; use crate::util::spawn;
@ -43,7 +42,6 @@ use once_cell::sync::Lazy;
use once_cell::unsync::OnceCell; use once_cell::unsync::OnceCell;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
/// Default background color around images and behind transparent images /// Default background color around images and behind transparent images
@ -107,7 +105,6 @@ mod imp {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct LpImage { pub struct LpImage {
pub(super) file: RefCell<Option<gio::File>>, pub(super) file: RefCell<Option<gio::File>>,
pub(super) path: RefCell<Option<PathBuf>>,
pub(super) is_deleted: Cell<bool>, pub(super) is_deleted: Cell<bool>,
/// Track changes to this image /// Track changes to this image
pub(super) file_monitor: RefCell<Option<gio::FileMonitor>>, pub(super) file_monitor: RefCell<Option<gio::FileMonitor>>,
@ -194,9 +191,6 @@ mod imp {
glib::ParamSpecObject::builder::<gio::File>("file") glib::ParamSpecObject::builder::<gio::File>("file")
.read_only() .read_only()
.build(), .build(),
glib::ParamSpecVariant::builder("path", glib::VariantTy::BYTE_STRING)
.read_only()
.build(),
glib::ParamSpecBoolean::builder("is-deleted") glib::ParamSpecBoolean::builder("is-deleted")
.read_only() .read_only()
.build(), .build(),
@ -242,7 +236,6 @@ mod imp {
let obj = self.obj(); let obj = self.obj();
match pspec.name() { match pspec.name() {
"file" => obj.file().to_value(), "file" => obj.file().to_value(),
"path" => obj.path().to_variant().to_value(),
"is-deleted" => obj.is_deleted().to_value(), "is-deleted" => obj.is_deleted().to_value(),
"is-loaded" => obj.is_loaded().to_value(), "is-loaded" => obj.is_loaded().to_value(),
"error" => obj.error().to_value(), "error" => obj.error().to_value(),
@ -743,15 +736,9 @@ glib::wrapper! {
impl LpImage { impl LpImage {
pub async fn load(&self, file: &gio::File) { pub async fn load(&self, file: &gio::File) {
let path = file.path().unwrap(); log::debug!("Loading file {}", file.uri());
self.set_file(&file);
if !path.is_file() { self.set_file(file);
self.set_error(anyhow::Error::msg(gettext("File does not exist")));
return;
}
log::debug!("Loading file {path:?}");
let tiles = &self.imp().frame_buffer; let tiles = &self.imp().frame_buffer;
@ -920,16 +907,10 @@ impl LpImage {
self.imp().file.borrow().clone() self.imp().file.borrow().clone()
} }
pub fn path(&self) -> Option<PathBuf> {
self.imp().path.borrow().clone()
}
pub(super) fn set_file(&self, file: &gio::File) { pub(super) fn set_file(&self, file: &gio::File) {
let imp = self.imp(); let imp = self.imp();
imp.file.replace(Some(file.clone())); imp.file.replace(Some(file.clone()));
imp.path.replace(file.path());
self.notify("path");
let monitor = file.monitor_file(gio::FileMonitorFlags::WATCH_MOVES, gio::Cancellable::NONE); let monitor = file.monitor_file(gio::FileMonitorFlags::WATCH_MOVES, gio::Cancellable::NONE);
if let Ok(m) = &monitor { if let Ok(m) = &monitor {
@ -953,7 +934,7 @@ impl LpImage {
match event { match event {
gio::FileMonitorEvent::Renamed => { gio::FileMonitorEvent::Renamed => {
if let Some(file) = file_b { if let Some(file) = file_b {
log::debug!("Moved to {:?}", file.path()); log::debug!("Moved to {}", file.uri());
self.set_file(file); self.set_file(file);
} }
} }

View file

@ -31,8 +31,6 @@ use gtk::prelude::*;
use gtk::CompositeTemplate; use gtk::CompositeTemplate;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::path::PathBuf;
mod imp { mod imp {
use super::*; use super::*;
@ -151,7 +149,7 @@ impl LpImagePage {
pub fn from_file(file: &gio::File) -> Self { pub fn from_file(file: &gio::File) -> Self {
let obj = glib::Object::new::<Self>(); let obj = glib::Object::new::<Self>();
obj.imp().image.set_file(&file); obj.imp().image.set_file(file);
spawn(clone!(@weak obj, @strong file => async move { spawn(clone!(@weak obj, @strong file => async move {
let imp = obj.imp(); let imp = obj.imp();

View file

@ -44,7 +44,6 @@ use gtk::CompositeTemplate;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::path::{Path, PathBuf};
// The number of pages we want to buffer // The number of pages we want to buffer
// on either side of the current page. // on either side of the current page.
@ -139,7 +138,7 @@ mod imp {
let view = &*obj; let view = &*obj;
signal_group.connect_closure( signal_group.connect_closure(
"notify::path", "notify::file",
true, true,
glib::closure_local!(@watch view => move |_: LpImage, _: glib::ParamSpec| { glib::closure_local!(@watch view => move |_: LpImage, _: glib::ParamSpec| {
view.current_image_path_changed(); view.current_image_path_changed();
@ -333,7 +332,7 @@ impl LpImageView {
.and_then(|x| self.model().index_of(&x)) .and_then(|x| self.model().index_of(&x))
.unwrap_or_default(); .unwrap_or_default();
let page = if let Some(page) = sliding_view.get(&new_file) { let page = if let Some(page) = sliding_view.get(new_file) {
page page
} else { } else {
let page = LpImagePage::from_file(new_file); let page = LpImagePage::from_file(new_file);
@ -362,22 +361,22 @@ impl LpImageView {
self.imp().preserve_content.set(false); self.imp().preserve_content.set(false);
let existing = dbg!(sliding_view.pages()); let existing = sliding_view.pages();
let target = dbg!(self.model().files_around(current_file, BUFFER)); let target = self.model().files_around(current_file, BUFFER);
// remove old pages // remove old pages
for (uri, page) in &existing { for (uri, page) in &existing {
if !target.contains_key(uri) { if !target.contains_key(uri) {
sliding_view.remove(dbg!(page)); sliding_view.remove(page);
} }
} }
// add missing pages or put in correct position // add missing pages or put in correct position
for (position, (_, file)) in target.iter().enumerate() { for (position, (_, file)) in target.iter().enumerate() {
if let Some(page) = existing.get(&file.uri()) { if let Some(page) = existing.get(&file.uri()) {
sliding_view.move_to(page, dbg!(position)); sliding_view.move_to(page, position);
} else { } else {
sliding_view.insert(&LpImagePage::from_file(file), dbg!(position)); sliding_view.insert(&LpImagePage::from_file(file), position);
} }
} }

View file

@ -11,7 +11,6 @@ use indexmap::IndexMap;
use once_cell::sync::{Lazy, OnceCell}; use once_cell::sync::{Lazy, OnceCell};
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
//use std::path::{Path, PathBuf};
static SCROLL_DAMPING_RATIO: f64 = 1.; static SCROLL_DAMPING_RATIO: f64 = 1.;
static SCROLL_MASS: f64 = 0.5; static SCROLL_MASS: f64 = 0.5;

View file

@ -678,9 +678,9 @@ impl LpWindow {
// have multiple `match` or `if let` branches, and without needing // have multiple `match` or `if let` branches, and without needing
// to unwrap. // to unwrap.
#[template_callback] #[template_callback]
fn window_title(&self, path: glib::variant::Variant) -> String { fn window_title(&self, file: Option<&gio::File>) -> String {
// ensure that templates are initialized // ensure that templates are initialized
if path.as_maybe().is_none() { if file.is_none() {
gettext("Loupe") gettext("Loupe")
} else { } else {
self.imp() self.imp()