mirror of
https://gitlab.gnome.org/World/Authenticator.git
synced 2025-03-04 08:44:40 +01:00
parent
b7f39888bf
commit
11ba294816
5 changed files with 139 additions and 68 deletions
|
@ -19,7 +19,7 @@
|
|||
<child>
|
||||
<object class="ProviderImage" id="image">
|
||||
<property name="halign">start</property>
|
||||
<property name="size">24</property>
|
||||
<property name="size">32</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
|
|
@ -111,9 +111,8 @@ pub enum Favicon {
|
|||
impl fmt::Debug for Favicon {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Data(bytes, metadata) => f
|
||||
Self::Data(_, metadata) => f
|
||||
.debug_struct("Favicon")
|
||||
.field("data", bytes)
|
||||
.field("type", &metadata.type_())
|
||||
.field("size", &metadata.size())
|
||||
.finish(),
|
||||
|
@ -153,7 +152,7 @@ impl Favicon {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn cache(&self, icon_name: &str) -> anyhow::Result<PathBuf> {
|
||||
pub async fn cache(&self, icon_name: &str) -> anyhow::Result<()> {
|
||||
let body = match self {
|
||||
Self::Data(bytes, _) => bytes.to_owned(),
|
||||
Self::Url(url, _) => {
|
||||
|
@ -161,30 +160,22 @@ impl Favicon {
|
|||
res.bytes().await?.to_vec()
|
||||
}
|
||||
};
|
||||
let cache_path = FAVICONS_PATH.join(icon_name);
|
||||
let mut dest = tokio::fs::File::create(cache_path.clone()).await?;
|
||||
|
||||
if self.metadata().type_() == &Type::Ico {
|
||||
log::debug!("Found a .ico favicon, converting to PNG");
|
||||
|
||||
let cache_path = FAVICONS_PATH.join(format!("{}.png", icon_name));
|
||||
let mut dest = tokio::fs::File::create(cache_path.clone()).await?;
|
||||
|
||||
if let Ok(ico) = image::load_from_memory_with_format(&body, image::ImageFormat::Ico) {
|
||||
let mut cursor = std::io::Cursor::new(vec![]);
|
||||
ico.write_to(&mut cursor, image::ImageOutputFormat::Png)?;
|
||||
dest.write_all(cursor.get_ref()).await?;
|
||||
return Ok(());
|
||||
} else {
|
||||
log::debug!("It seems to not be a .ICO favicon, fallback to PNG");
|
||||
dest.write_all(&body).await?;
|
||||
};
|
||||
|
||||
Ok(cache_path)
|
||||
} else {
|
||||
let cache_path = FAVICONS_PATH.join(icon_name);
|
||||
let mut dest = tokio::fs::File::create(cache_path.clone()).await?;
|
||||
dest.write_all(&body).await?;
|
||||
|
||||
Ok(cache_path)
|
||||
}
|
||||
dest.write_all(&body).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn size(&self) -> Option<(u32, u32)> {
|
||||
|
@ -267,7 +258,6 @@ impl std::fmt::Display for FaviconError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FaviconScrapper(Vec<Favicon>);
|
||||
|
||||
impl FaviconScrapper {
|
||||
|
@ -315,6 +305,20 @@ impl FaviconScrapper {
|
|||
self.0.is_empty()
|
||||
}
|
||||
|
||||
pub async fn find_size(&self, size: u32) -> Option<&Favicon> {
|
||||
let mut best = None;
|
||||
for favicon in self.0.iter() {
|
||||
if let Some(current_size) = favicon.size().await {
|
||||
// Only store the width & assumes it has the same height here to simplify things
|
||||
if current_size.0 == size {
|
||||
best = Some(favicon);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
best
|
||||
}
|
||||
|
||||
pub async fn find_best(&self) -> Option<&Favicon> {
|
||||
let mut largest_size = 0;
|
||||
let mut best = None;
|
||||
|
@ -502,6 +506,12 @@ impl FaviconScrapper {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FaviconScrapper {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list().entries(&self.0).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<usize> for FaviconScrapper {
|
||||
type Output = Favicon;
|
||||
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use super::algorithm::{Algorithm, OTPMethod};
|
||||
use crate::{
|
||||
models::{database, otp, Account, AccountsModel, FaviconError, FaviconScrapper},
|
||||
models::{
|
||||
database, otp, Account, AccountsModel, FaviconError, FaviconScrapper, Type, FAVICONS_PATH,
|
||||
},
|
||||
schema::providers,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use core::cmp::Ordering;
|
||||
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
use glib::{Cast, StaticType, ToValue};
|
||||
use gtk::{gio, glib, prelude::*, subclass::prelude::*};
|
||||
use gtk::{gdk_pixbuf, gio, glib, prelude::*, subclass::prelude::*};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
str::FromStr,
|
||||
|
@ -351,19 +353,77 @@ impl Provider {
|
|||
website: String,
|
||||
name: String,
|
||||
id: u32,
|
||||
) -> Result<gio::File, Box<dyn std::error::Error>> {
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let website_url = Url::parse(&website)?;
|
||||
let favicon = FaviconScrapper::from_url(website_url).await?;
|
||||
log::debug!("Found the following icons {:#?} for {}", favicon, name);
|
||||
|
||||
let icon_name = format!("{}_{}", id, name.replace(' ', "_"));
|
||||
let icon_name = glib::base64_encode(icon_name.as_bytes());
|
||||
let small_icon_name = format!("{icon_name}_32x32");
|
||||
let large_icon_name = format!("{icon_name}_96x96");
|
||||
// We need two sizes:
|
||||
// - 32x32 for the accounts lists
|
||||
// - 96x96 elsewhere
|
||||
// either we find them in the list of available icons
|
||||
// or we scale down the "best" one we find.
|
||||
let mut found_small = false;
|
||||
let mut found_large = false;
|
||||
|
||||
match (favicon.find_size(32).await, favicon.find_size(96).await) {
|
||||
(Some(small), Some(large)) => {
|
||||
if small.cache(&small_icon_name).await.is_ok()
|
||||
&& large.cache(&large_icon_name).await.is_ok()
|
||||
{
|
||||
return Ok(icon_name.to_string());
|
||||
}
|
||||
}
|
||||
(Some(small), _) => {
|
||||
log::debug!("Found a 32x32 variant with no 96x96 variant");
|
||||
found_small = true;
|
||||
small.cache(&small_icon_name).await?;
|
||||
}
|
||||
(_, Some(large)) => {
|
||||
log::debug!("Found a 96x96 variant with no 32x32 variant");
|
||||
found_large = true;
|
||||
large.cache(&large_icon_name).await?;
|
||||
}
|
||||
// We found none, we fallback to getting the best icon
|
||||
_ => (),
|
||||
};
|
||||
|
||||
log::debug!("Trying to find the highest resolution favicon");
|
||||
if let Some(best_favicon) = favicon.find_best().await {
|
||||
log::debug!("Best favicon found is {:#?}", best_favicon);
|
||||
let cache_path = best_favicon.cache(&icon_name).await?;
|
||||
Ok(gio::File::for_path(cache_path))
|
||||
log::debug!("Largest favicon found is {:#?}", best_favicon);
|
||||
best_favicon.cache(&icon_name).await?;
|
||||
let cache_path = FAVICONS_PATH.join(&*icon_name);
|
||||
// Don't try to scale down svg variants
|
||||
if best_favicon.metadata().type_() != &Type::Svg {
|
||||
log::debug!("Creating scaled down variants for {:#?}", cache_path);
|
||||
{
|
||||
let pixbuf = gdk_pixbuf::Pixbuf::from_file(cache_path.clone())?;
|
||||
if !found_small {
|
||||
log::debug!("Creating a 32x32 variant of the favicon");
|
||||
let small_pixbuf = pixbuf
|
||||
.scale_simple(32, 32, gdk_pixbuf::InterpType::Bilinear)
|
||||
.unwrap();
|
||||
|
||||
let mut small_cache = cache_path.clone();
|
||||
small_cache.set_file_name(small_icon_name);
|
||||
small_pixbuf.savev(small_cache.clone(), "png", &[])?;
|
||||
}
|
||||
if !found_large {
|
||||
log::debug!("Creating a 96x96 variant of the favicon");
|
||||
let large_pixbuf = pixbuf
|
||||
.scale_simple(96, 96, gdk_pixbuf::InterpType::Bilinear)
|
||||
.unwrap();
|
||||
let mut large_cache = cache_path.clone();
|
||||
large_cache.set_file_name(large_icon_name);
|
||||
large_pixbuf.savev(large_cache.clone(), "png", &[])?;
|
||||
}
|
||||
};
|
||||
tokio::fs::remove_file(cache_path).await?;
|
||||
}
|
||||
Ok(icon_name.to_string())
|
||||
} else {
|
||||
Err(Box::new(FaviconError::NoResults))
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use crate::models::Provider;
|
||||
use crate::models::RUNTIME;
|
||||
use crate::models::{Provider, FAVICONS_PATH};
|
||||
use glib::{clone, Receiver, Sender};
|
||||
use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
|
||||
use gtk_macros::send;
|
||||
|
||||
pub enum ImageAction {
|
||||
Ready(gio::File),
|
||||
Ready(String),
|
||||
Failed,
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ mod imp {
|
|||
"size",
|
||||
"size",
|
||||
"Image size",
|
||||
24,
|
||||
32,
|
||||
96,
|
||||
48,
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT,
|
||||
|
@ -154,13 +154,19 @@ impl ProviderImage {
|
|||
return;
|
||||
}
|
||||
|
||||
let file = gio::File::for_uri(&uri);
|
||||
if !file.query_exists(gio::Cancellable::NONE) {
|
||||
let small_file = gio::File::for_path(&FAVICONS_PATH.join(format!("{uri}_32x32")));
|
||||
let large_file = gio::File::for_path(&FAVICONS_PATH.join(format!("{uri}_96x96")));
|
||||
if !small_file.query_exists(gio::Cancellable::NONE)
|
||||
|| !large_file.query_exists(gio::Cancellable::NONE)
|
||||
{
|
||||
self.fetch();
|
||||
return;
|
||||
}
|
||||
|
||||
imp.image.set_from_file(file.path());
|
||||
if imp.size.get() == 32 {
|
||||
imp.image.set_from_file(small_file.path());
|
||||
} else {
|
||||
imp.image.set_from_file(large_file.path());
|
||||
}
|
||||
imp.stack.set_visible_child_name("image");
|
||||
}
|
||||
_ => {
|
||||
|
@ -181,8 +187,8 @@ impl ProviderImage {
|
|||
let (sender, receiver) = futures::channel::oneshot::channel();
|
||||
RUNTIME.spawn(async move {
|
||||
match Provider::favicon(website, name, id).await {
|
||||
Ok(file) => {
|
||||
sender.send(Some(file)).unwrap();
|
||||
Ok(cache_name) => {
|
||||
sender.send(Some(cache_name)).unwrap();
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to load favicon {}", err);
|
||||
|
@ -193,8 +199,8 @@ impl ProviderImage {
|
|||
glib::MainContext::default().spawn_local(clone!(@weak self as this => async move {
|
||||
let imp = this.imp();
|
||||
let response = receiver.await.unwrap();
|
||||
if let Some(file) = response {
|
||||
send!(imp.sender.clone(), ImageAction::Ready(file));
|
||||
if let Some(cache_name) = response {
|
||||
send!(imp.sender.clone(), ImageAction::Ready(cache_name));
|
||||
} else {
|
||||
send!(imp.sender.clone(), ImageAction::Failed);
|
||||
}
|
||||
|
@ -241,10 +247,15 @@ impl ProviderImage {
|
|||
imp.image.set_from_icon_name(Some("provider-fallback"));
|
||||
"invalid".to_string()
|
||||
}
|
||||
ImageAction::Ready(image) => {
|
||||
imp.image.set_from_file(image.path());
|
||||
let image_uri = image.uri();
|
||||
image_uri.to_string()
|
||||
ImageAction::Ready(cache_name) => {
|
||||
if imp.size.get() == 32 {
|
||||
imp.image
|
||||
.set_from_file(Some(&FAVICONS_PATH.join(format!("{cache_name}_32x32"))));
|
||||
} else {
|
||||
imp.image
|
||||
.set_from_file(Some(&FAVICONS_PATH.join(format!("{cache_name}_96x96"))));
|
||||
}
|
||||
cache_name
|
||||
}
|
||||
};
|
||||
if let Some(provider) = self.provider() {
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
use adw::prelude::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::{clone, translate::IntoGlib};
|
||||
use gtk::{gio, glib, subclass::prelude::*, CompositeTemplate};
|
||||
use gtk::{gio, gdk_pixbuf, glib, subclass::prelude::*, CompositeTemplate};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
@ -254,36 +254,26 @@ impl ProviderPage {
|
|||
|
||||
let image_uri = if let Some(file) = imp.selected_image.borrow().clone() {
|
||||
let basename = file.basename().unwrap();
|
||||
let extension = basename
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.split('.')
|
||||
.last()
|
||||
.unwrap_or("png");
|
||||
let icon_name = glib::base64_encode(basename.to_str().unwrap().as_bytes());
|
||||
let small_icon_name = format!("{icon_name}_32x32");
|
||||
let large_icon_name = format!("{icon_name}_96x96");
|
||||
|
||||
let icon_name = format!(
|
||||
"{}.{}",
|
||||
glib::base64_encode(basename.to_str().unwrap().as_bytes()),
|
||||
extension
|
||||
);
|
||||
// Create a 96x96 & 32x32 variants
|
||||
let stream = file.read(gio::Cancellable::NONE)?;
|
||||
let pixbuf = gdk_pixbuf::Pixbuf::from_stream(&stream, gio::Cancellable::NONE)?;
|
||||
log::debug!("Creating a 32x32 variant of the selected favicon");
|
||||
let small_pixbuf = pixbuf
|
||||
.scale_simple(32, 32, gdk_pixbuf::InterpType::Bilinear)
|
||||
.unwrap();
|
||||
small_pixbuf.savev(FAVICONS_PATH.join(small_icon_name), "png", &[])?;
|
||||
|
||||
let image_dest = FAVICONS_PATH.join(icon_name.as_str());
|
||||
log::debug!("Creating a 96x96 variant of the selected favicon");
|
||||
let large_pixbuf = pixbuf
|
||||
.scale_simple(96, 96, gdk_pixbuf::InterpType::Bilinear)
|
||||
.unwrap();
|
||||
large_pixbuf.savev(FAVICONS_PATH.join(large_icon_name), "png", &[])?;
|
||||
|
||||
let dest_file = gio::File::for_path(image_dest);
|
||||
dest_file
|
||||
.create(
|
||||
gio::FileCreateFlags::REPLACE_DESTINATION,
|
||||
gio::Cancellable::NONE,
|
||||
)
|
||||
.ok();
|
||||
file.copy(
|
||||
&dest_file,
|
||||
gio::FileCopyFlags::OVERWRITE,
|
||||
gio::Cancellable::NONE,
|
||||
None,
|
||||
)?;
|
||||
|
||||
Some(dest_file.uri().to_string())
|
||||
Some(icon_name.to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue