Add a setting to disable favicon fetching

Fixes #330
This commit is contained in:
Bilal Elmoussaoui 2022-12-27 11:45:17 +01:00
parent 377d768c64
commit 553857ad31
8 changed files with 165 additions and 53 deletions

View File

@ -34,5 +34,15 @@
<key name="keyrings-migrated" type="b">
<default>false</default>
</key>
<key name="download-favicons" type="b">
<default>true</default>
<summary>Download Favicons</summary>
<description>Whether the application should attempt to find an icon for the providers.</description>
</key>
<key name="download-favicons-metered" type="b">
<default>true</default>
<summary>Download Favicons over metered connections</summary>
<description>Whether the application should download favicons over a metered connection.</description>
</key>
</schema>
</schemalist>

View File

@ -84,6 +84,37 @@
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Network</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">_Download Favicons</property>
<property name="use-underline">True</property>
<property name="activatable-widget">download_favicons_switch</property>
<property name="subtitle" translatable="yes">Automatically attempt fetching a website icon</property>
<child>
<object class="GtkSwitch" id="download_favicons_switch">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">_Metered Connection</property>
<property name="use-underline">True</property>
<property name="activatable-widget">download_favicons_metered_switch</property>
<property name="subtitle" translatable="yes">Fetch a website icon on a metered connection</property>
<child>
<object class="GtkSwitch" id="download_favicons_metered_switch">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
@ -104,4 +135,3 @@
</child>
</template>
</interface>

View File

@ -11,6 +11,7 @@ use crate::{
config,
models::{
keyring, Account, OTPUri, Provider, ProvidersModel, FAVICONS_PATH, RUNTIME, SECRET_SERVICE,
SETTINGS,
},
utils::spawn_tokio_blocking,
widgets::{PreferencesWindow, ProvidersDialog, Window},
@ -21,7 +22,7 @@ mod imp {
use adw::subclass::prelude::*;
use glib::{ParamSpec, ParamSpecBoolean, Value, WeakRef};
use once_cell::sync::{Lazy, OnceCell};
use once_cell::sync::Lazy;
use super::*;
@ -34,7 +35,6 @@ mod imp {
pub locked: Cell<bool>,
pub lock_timeout_id: RefCell<Option<glib::SourceId>>,
pub can_be_locked: Cell<bool>,
pub settings: OnceCell<gio::Settings>,
pub search_provider: RefCell<Option<SearchProvider<super::Application>>>,
}
@ -96,7 +96,6 @@ mod imp {
.activate(|app: &Self::Type, _, _| {
let model = &app.imp().model;
let window = app.active_window();
let preferences = PreferencesWindow::new(model.clone());
preferences.set_has_set_password(app.can_be_locked());
preferences.connect_restore_completed(clone!(@weak window =>move |_| {
@ -182,7 +181,7 @@ mod imp {
}
});
self.settings.get().unwrap().connect_changed(
SETTINGS.connect_changed(
None,
clone!(@weak app => move |settings, key| {
match key {
@ -279,8 +278,7 @@ impl Application {
std::fs::create_dir_all(&*FAVICONS_PATH).ok();
// To be removed in the upcoming release
let settings = gio::Settings::new(config::APP_ID);
if !settings.boolean("keyrings-migrated") {
if !SETTINGS.boolean("keyrings-migrated") {
tracing::info!("Migrating the secrets to the file backend");
let output: oo7::Result<()> = RUNTIME.block_on(async {
oo7::migrate(
@ -295,7 +293,7 @@ impl Application {
});
match output {
Ok(_) => {
settings
SETTINGS
.set_boolean("keyrings-migrated", true)
.expect("Failed to update settings");
tracing::info!("Secrets were migrated successfully");
@ -330,7 +328,6 @@ impl Application {
if !has_set_password {
app.imp().model.load();
}
app.imp().settings.set(settings).unwrap();
ApplicationExtManual::run(&app);
}
@ -390,8 +387,8 @@ impl Application {
/// Starts or restarts the lock timeout.
pub fn restart_lock_timeout(&self) {
let imp = self.imp();
let auto_lock = imp.settings.get().unwrap().boolean("auto-lock");
let timeout = imp.settings.get().unwrap().uint("auto-lock-timeout") * 60;
let auto_lock = SETTINGS.boolean("auto-lock");
let timeout = SETTINGS.uint("auto-lock-timeout") * 60;
if !auto_lock {
return;
@ -420,7 +417,7 @@ impl Application {
fn update_color_scheme(&self) {
let manager = self.style_manager();
if !manager.system_supports_color_schemes() {
let color_scheme = if self.imp().settings.get().unwrap().boolean("dark-theme") {
let color_scheme = if SETTINGS.boolean("dark-theme") {
adw::ColorScheme::PreferDark
} else {
adw::ColorScheme::PreferLight

View File

@ -11,9 +11,11 @@ mod otp_uri;
mod provider;
mod provider_sorter;
mod providers;
mod settings;
pub static RUNTIME: Lazy<tokio::runtime::Runtime> =
Lazy::new(|| tokio::runtime::Runtime::new().unwrap());
pub static SETTINGS: Lazy<Settings> = Lazy::new(|| Settings::default());
pub static FAVICONS_PATH: Lazy<std::path::PathBuf> = Lazy::new(|| {
gtk::glib::user_cache_dir()
.join("authenticator")
@ -30,4 +32,5 @@ pub use self::{
provider::{Provider, ProviderPatch},
provider_sorter::ProviderSorter,
providers::ProvidersModel,
settings::Settings,
};

52
src/models/settings.rs Normal file
View File

@ -0,0 +1,52 @@
use std::ops::Deref;
use gtk::{gio, glib, prelude::*};
use crate::config;
pub struct Settings(gio::Settings);
impl Settings {
pub fn download_favicons(&self) -> bool {
self.boolean("download-favicons")
}
pub fn connect_download_favicons_changed<F>(&self, callback: F) -> glib::SignalHandlerId
where
F: Fn(bool) + 'static,
{
self.connect_changed(Some("download-favicons"), move |settings, _key| {
callback(settings.boolean("download-favicons"))
})
}
pub fn download_favicons_metered(&self) -> bool {
self.boolean("download-favicons-metered")
}
pub fn connect_download_favicons_metered_changed<F>(&self, callback: F) -> glib::SignalHandlerId
where
F: Fn(bool) + 'static,
{
self.connect_changed(Some("download-favicons-metered"), move |settings, _key| {
callback(settings.boolean("download-favicons-metered"))
})
}
}
impl Default for Settings {
fn default() -> Self {
Self(gio::Settings::new(config::APP_ID))
}
}
impl Deref for Settings {
type Target = gio::Settings;
fn deref(&self) -> &Self::Target {
&self.0
}
}
unsafe impl Send for Settings {}
unsafe impl Sync for Settings {}

View File

@ -11,8 +11,7 @@ use crate::{
Aegis, AndOTP, Backupable, Bitwarden, FreeOTP, Google, LegacyAuthenticator, Operation,
Restorable, RestorableItem,
},
config,
models::ProvidersModel,
models::{ProvidersModel, SETTINGS},
};
mod imp {
@ -33,7 +32,6 @@ mod imp {
#[derive(Debug, CompositeTemplate)]
#[template(resource = "/com/belmoussaoui/Authenticator/preferences.ui")]
pub struct PreferencesWindow {
pub settings: gio::Settings,
pub model: OnceCell<ProvidersModel>,
pub has_set_password: Cell<bool>,
pub actions: gio::SimpleActionGroup,
@ -49,6 +47,10 @@ mod imp {
pub auto_lock: TemplateChild<gtk::Switch>,
#[template_child(id = "dark_mode_switch")]
pub dark_mode: TemplateChild<gtk::Switch>,
#[template_child(id = "download_favicons_switch")]
pub download_favicons: TemplateChild<gtk::Switch>,
#[template_child(id = "download_favicons_metered_switch")]
pub download_favicons_metered: TemplateChild<gtk::Switch>,
#[template_child]
pub dark_mode_group: TemplateChild<adw::PreferencesGroup>,
#[template_child(id = "lock_timeout_spin_btn")]
@ -63,11 +65,9 @@ mod imp {
type ParentType = adw::PreferencesWindow;
fn new() -> Self {
let settings = gio::Settings::new(config::APP_ID);
let actions = gio::SimpleActionGroup::new();
Self {
settings,
has_set_password: Cell::default(), // Synced from the application
camera_page: CameraPage::new(actions.clone()),
password_page: PasswordPage::new(actions.clone()),
@ -77,6 +77,8 @@ mod imp {
restore_actions: gio::SimpleActionGroup::new(),
auto_lock: TemplateChild::default(),
dark_mode: TemplateChild::default(),
download_favicons: TemplateChild::default(),
download_favicons_metered: TemplateChild::default(),
lock_timeout: TemplateChild::default(),
backup_group: TemplateChild::default(),
restore_group: TemplateChild::default(),
@ -193,13 +195,23 @@ impl PreferencesWindow {
imp.dark_mode_group
.set_visible(!style_manager.system_supports_color_schemes());
imp.settings
SETTINGS
.bind("dark-theme", &*imp.dark_mode, "active")
.build();
imp.settings
SETTINGS
.bind("download-favicons", &*imp.download_favicons, "active")
.build();
SETTINGS
.bind(
"download-favicons-metered",
&*imp.download_favicons_metered,
"active",
)
.build();
SETTINGS
.bind("auto-lock", &*imp.auto_lock, "active")
.build();
imp.settings
SETTINGS
.bind("auto-lock-timeout", &*imp.lock_timeout, "value")
.build();

View File

@ -3,7 +3,7 @@ use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
use gtk_macros::send;
use tracing::error;
use crate::models::{Provider, FAVICONS_PATH, RUNTIME};
use crate::models::{Provider, FAVICONS_PATH, RUNTIME, SETTINGS};
pub enum ImageAction {
Ready(String),
@ -22,6 +22,7 @@ mod imp {
#[template(resource = "/com/belmoussaoui/Authenticator/provider_image.ui")]
pub struct ProviderImage {
pub size: Cell<u32>,
pub was_downloaded: Cell<bool>,
pub sender: Sender<ImageAction>,
pub receiver: RefCell<Option<Receiver<ImageAction>>>,
pub provider: RefCell<Option<Provider>>,
@ -48,6 +49,7 @@ mod imp {
sender,
receiver,
size: Cell::new(96),
was_downloaded: Cell::new(false),
stack: TemplateChild::default(),
image: TemplateChild::default(),
spinner: TemplateChild::default(),
@ -166,6 +168,7 @@ impl ProviderImage {
} else {
imp.image.set_from_file(large_file.path());
}
imp.was_downloaded.set(true);
imp.stack.set_visible_child_name("image");
}
_ => {
@ -176,6 +179,16 @@ impl ProviderImage {
fn fetch(&self) {
let imp = self.imp();
let network_monitor = gio::NetworkMonitor::default();
if network_monitor.is_network_metered() && !SETTINGS.download_favicons_metered() {
imp.image.set_from_icon_name(Some("provider-fallback"));
imp.stack.set_visible_child_name("image");
return;
} else if !SETTINGS.download_favicons() {
imp.image.set_from_icon_name(Some("provider-fallback"));
imp.stack.set_visible_child_name("image");
return;
}
if let Some(handle) = imp.join_handle.borrow_mut().take() {
handle.abort();
}
@ -203,6 +216,7 @@ impl ProviderImage {
glib::MainContext::default().spawn_local(clone!(@weak self as this => async move {
let imp = this.imp();
imp.was_downloaded.set(true);
match receiver.await {
Ok(Some(cache_name)) => {
send!(imp.sender.clone(), ImageAction::Ready(cache_name));
@ -247,6 +261,24 @@ impl ProviderImage {
self.bind_property("size", &*imp.image, "pixel-size")
.sync_create()
.build();
SETTINGS.connect_download_favicons_changed(clone!(@weak self as image => move |state| {
if state && !image.imp().was_downloaded.get() {
image.fetch();
}
}));
SETTINGS.connect_download_favicons_metered_changed(
clone!(@weak self as image => move |state| {
let network_monitor = gio::NetworkMonitor::default();
if !image.imp().was_downloaded.get() {
if network_monitor.is_network_metered() && state {
image.fetch();
} else if !network_monitor.is_network_metered() {
image.fetch();
}
}
}),
);
}
fn do_action(&self, action: ImageAction) -> glib::Continue {

View File

@ -7,7 +7,7 @@ use once_cell::sync::OnceCell;
use crate::{
application::Application,
config,
models::{keyring, Account, OTPUri, ProvidersModel},
models::{keyring, Account, OTPUri, ProvidersModel, SETTINGS},
utils::spawn_tokio_blocking,
widgets::{
accounts::AccountDetailsPage,
@ -29,10 +29,9 @@ mod imp {
use super::*;
#[derive(Debug, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/com/belmoussaoui/Authenticator/window.ui")]
pub struct Window {
pub settings: gio::Settings,
pub model: OnceCell<ProvidersModel>,
#[template_child]
pub main_stack: TemplateChild<gtk::Stack>,
@ -70,28 +69,6 @@ mod imp {
type Type = super::Window;
type ParentType = adw::ApplicationWindow;
fn new() -> Self {
let settings = gio::Settings::new(config::APP_ID);
Self {
settings,
providers: TemplateChild::default(),
model: OnceCell::default(),
account_details: TemplateChild::default(),
search_entry: TemplateChild::default(),
deck: TemplateChild::default(),
error_revealer: TemplateChild::default(),
empty_status_page: TemplateChild::default(),
search_btn: TemplateChild::default(),
password_entry: TemplateChild::default(),
accounts_stack: TemplateChild::default(),
locked_img: TemplateChild::default(),
title_stack: TemplateChild::default(),
main_stack: TemplateChild::default(),
unlock_button: TemplateChild::default(),
toast_overlay: TemplateChild::default(),
}
}
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
klass.bind_template_instance_callbacks();
@ -267,14 +244,14 @@ impl Window {
imp.locked_img.set_from_icon_name(Some(config::APP_ID));
// load latest window state
let width = imp.settings.int("window-width");
let height = imp.settings.int("window-height");
let width = SETTINGS.int("window-width");
let height = SETTINGS.int("window-height");
if width > -1 && height > -1 {
self.set_default_size(width, height);
}
let is_maximized = imp.settings.boolean("is-maximized");
let is_maximized = SETTINGS.boolean("is-maximized");
if is_maximized {
self.maximize();
}
@ -316,12 +293,11 @@ impl Window {
}
fn save_window_state(&self) -> anyhow::Result<()> {
let settings = &self.imp().settings;
let size = self.default_size();
settings.set_int("window-width", size.0)?;
settings.set_int("window-height", size.1)?;
SETTINGS.set_int("window-width", size.0)?;
SETTINGS.set_int("window-height", size.1)?;
settings.set_boolean("is-maximized", self.is_maximized())?;
SETTINGS.set_boolean("is-maximized", self.is_maximized())?;
Ok(())
}