feature: cache article images for offline reading

After fetching articles, all remote images referenced in article content
are downloaded to ~/.local/share/net.jeena.FeedTheMonkey/images/ and
their src attributes rewritten to file:// URIs. Subsequent loads of the
same article (including from the cache on the next startup) display
images without a network connection.

Metered-connection awareness: image caching is skipped automatically
when GIO reports the network connection as metered, regardless of the
preference setting.

A "Cache Images" toggle in Preferences lets the user disable caching
entirely (stored in the cache-images GSettings key).

After each refresh, images no longer referenced by any article in the
current unread list are deleted from the cache directory to prevent
unbounded disk growth.
This commit is contained in:
Jeena 2026-03-21 01:19:49 +00:00
parent 183191727b
commit fda441bebd
9 changed files with 186 additions and 12 deletions

View file

@ -22,6 +22,8 @@ pub mod imp {
#[derive(CompositeTemplate, Default)]
#[template(resource = "/net/jeena/FeedTheMonkey/ui/preferences_dialog.ui")]
pub struct PreferencesDialog {
#[template_child]
pub cache_images_row: TemplateChild<libadwaita::SwitchRow>,
#[template_child]
pub filters_text_view: TemplateChild<gtk4::TextView>,
}
@ -44,25 +46,28 @@ pub mod imp {
impl ObjectImpl for PreferencesDialog {
fn constructed(&self) {
self.parent_constructed();
self.load();
// Save on every text change
let settings = gio::Settings::new("net.jeena.FeedTheMonkey");
// Cache images switch
self.cache_images_row.set_active(settings.boolean("cache-images"));
let s = settings.clone();
self.cache_images_row.connect_active_notify(move |row| {
s.set_boolean("cache-images", row.is_active()).ok();
});
// Content filters text view
self.filters_text_view.buffer().set_text(&settings.string("content-filters"));
let obj_weak = self.obj().downgrade();
self.filters_text_view.buffer().connect_changed(move |_| {
if let Some(obj) = obj_weak.upgrade() {
obj.imp().save();
obj.imp().save_filters();
}
});
}
}
impl PreferencesDialog {
fn load(&self) {
let settings = gio::Settings::new("net.jeena.FeedTheMonkey");
let text = settings.string("content-filters");
self.filters_text_view.buffer().set_text(&text);
}
fn save(&self) {
fn save_filters(&self) {
let buf = self.filters_text_view.buffer();
let text = buf.text(&buf.start_iter(), &buf.end_iter(), false);
let settings = gio::Settings::new("net.jeena.FeedTheMonkey");