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:
parent
032707a210
commit
fad8bb7939
15 changed files with 119 additions and 107 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()));
|
||||||
|
|
|
||||||
|
|
@ -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(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue