fix: move avatars loading to new thread

This commit is contained in:
MeexReay 2025-07-05 03:27:38 +03:00
parent b4d82f0e32
commit 1af2810dc0

View File

@ -4,8 +4,8 @@ use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::hash::{DefaultHasher, Hasher}; use std::hash::{DefaultHasher, Hasher};
use std::sync::atomic::AtomicU64; use std::sync::atomic::{AtomicBool, AtomicU64};
use std::sync::RwLockWriteGuard; use std::sync::{Mutex, RwLockWriteGuard};
use std::sync::{atomic::Ordering, mpsc::channel, Arc, RwLock}; use std::sync::{atomic::Ordering, mpsc::channel, Arc, RwLock};
use std::thread; use std::thread;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
@ -53,7 +53,7 @@ struct UiModel {
#[cfg(all(not(feature = "libnotify"), not(feature = "notify-rust")))] #[cfg(all(not(feature = "libnotify"), not(feature = "notify-rust")))]
notifications: Arc<RwLock<Vec<String>>>, notifications: Arc<RwLock<Vec<String>>>,
default_avatar: Pixbuf, default_avatar: Pixbuf,
avatars: Arc<RwLock<HashMap<u64, Pixbuf>>>, avatars: Arc<Mutex<HashMap<u64, Vec<Picture>>>>,
latest_sign: Arc<AtomicU64> latest_sign: Arc<AtomicU64>
} }
@ -231,44 +231,25 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
vbox.append(&save_button); vbox.append(&save_button);
save_button.connect_clicked(clone!( save_button.connect_clicked(clone!(
#[weak] #[weak] ctx,
ctx, #[weak] host_entry,
#[weak] #[weak] name_entry,
host_entry, #[weak] message_format_entry,
#[weak] #[weak] update_time_entry,
name_entry, #[weak] max_messages_entry,
#[weak] #[weak] hide_my_ip_entry,
message_format_entry, #[weak] show_other_ip_entry,
#[weak] #[weak] chunked_enabled_entry,
update_time_entry, #[weak] formatting_enabled_entry,
#[weak] #[weak] commands_enabled_entry,
max_messages_entry, #[weak] notifications_enabled_entry,
#[weak] #[weak] proxy_entry,
hide_my_ip_entry, #[weak] debug_logs_entry,
#[weak] #[weak] oof_update_time_entry,
show_other_ip_entry, #[weak] konata_size_entry,
#[weak] #[weak] remove_gui_shit_entry,
chunked_enabled_entry, #[weak] new_ui_enabled_entry,
#[weak] #[weak] avatar_entry,
formatting_enabled_entry,
#[weak]
commands_enabled_entry,
#[weak]
notifications_enabled_entry,
#[weak]
proxy_entry,
#[weak]
debug_logs_entry,
#[weak]
oof_update_time_entry,
#[weak]
konata_size_entry,
#[weak]
remove_gui_shit_entry,
#[weak]
new_ui_enabled_entry,
#[weak]
avatar_entry,
move |_| { move |_| {
let config = Config { let config = Config {
host: host_entry.text().to_string(), host: host_entry.text().to_string(),
@ -365,44 +346,25 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
vbox.append(&reset_button); vbox.append(&reset_button);
reset_button.connect_clicked(clone!( reset_button.connect_clicked(clone!(
#[weak] #[weak] ctx,
ctx, #[weak] host_entry,
#[weak] #[weak] name_entry,
host_entry, #[weak] message_format_entry,
#[weak] #[weak] update_time_entry,
name_entry, #[weak] max_messages_entry,
#[weak] #[weak] hide_my_ip_entry,
message_format_entry, #[weak] show_other_ip_entry,
#[weak] #[weak] chunked_enabled_entry,
update_time_entry, #[weak] formatting_enabled_entry,
#[weak] #[weak] commands_enabled_entry,
max_messages_entry, #[weak] notifications_enabled_entry,
#[weak] #[weak] proxy_entry,
hide_my_ip_entry, #[weak] debug_logs_entry,
#[weak] #[weak] oof_update_time_entry,
show_other_ip_entry, #[weak] konata_size_entry,
#[weak] #[weak] remove_gui_shit_entry,
chunked_enabled_entry, #[weak] new_ui_enabled_entry,
#[weak] #[weak] avatar_entry,
formatting_enabled_entry,
#[weak]
commands_enabled_entry,
#[weak]
notifications_enabled_entry,
#[weak]
proxy_entry,
#[weak]
debug_logs_entry,
#[weak]
oof_update_time_entry,
#[weak]
konata_size_entry,
#[weak]
remove_gui_shit_entry,
#[weak]
new_ui_enabled_entry,
#[weak]
avatar_entry,
move |_| { move |_| {
let config = Config::default(); let config = Config::default();
ctx.set_config(&config); ctx.set_config(&config);
@ -494,21 +456,7 @@ fn build_menu(ctx: Arc<Context>, app: &Application) {
AboutDialog::builder() AboutDialog::builder()
.application(&app) .application(&app)
.authors(["MeexReay"]) .authors(["MeexReay"])
.license( .license(include_str!("../../LICENSE"))
" DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.",
)
.comments("better RAC client") .comments("better RAC client")
.website("https://github.com/MeexReay/bRAC") .website("https://github.com/MeexReay/bRAC")
.website_label("source code") .website_label("source code")
@ -814,7 +762,7 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
#[cfg(all(not(feature = "libnotify"), not(feature = "notify-rust")))] #[cfg(all(not(feature = "libnotify"), not(feature = "notify-rust")))]
notifications: Arc::new(RwLock::new(Vec::<String>::new())), notifications: Arc::new(RwLock::new(Vec::<String>::new())),
default_avatar: load_pixbuf(include_bytes!("images/avatar.png")).unwrap(), default_avatar: load_pixbuf(include_bytes!("images/avatar.png")).unwrap(),
avatars: Arc::new(RwLock::new(HashMap::new())), avatars: Arc::new(Mutex::new(HashMap::new())),
latest_sign: Arc::new(AtomicU64::new(0)) latest_sign: Arc::new(AtomicU64::new(0))
} }
} }
@ -868,21 +816,62 @@ fn setup(_: &Application, ctx: Arc<Context>, ui: UiModel) {
move || { move || {
while let Ok((messages, clear)) = receiver.recv() { while let Ok((messages, clear)) = receiver.recv() {
let ctx = ctx.clone(); let ctx = ctx.clone();
let messages = Arc::new(messages);
let added = Arc::new(AtomicBool::new(false));
timeout_add_once(Duration::ZERO, move || { timeout_add_once(Duration::ZERO, {
GLOBAL.with(|global| { let messages = messages.clone();
if let Some(ui) = &*global.borrow() { let added = added.clone();
if clear {
while let Some(row) = ui.chat_box.last_child() { move || {
ui.chat_box.remove(&row); GLOBAL.with(|global| {
if let Some(ui) = &*global.borrow() {
if clear {
while let Some(row) = ui.chat_box.last_child() {
ui.chat_box.remove(&row);
}
}
for message in messages.iter() {
on_add_message(ctx.clone(), &ui, message.to_string(), !clear);
}
added.store(true, Ordering::SeqCst)
}
});
}
});
let mut avatars = HashMap::new();
for message in messages.iter() {
let Some(avatar_url) = grab_avatar(message) else { continue };
let avatar_id = get_avatar_id(&avatar_url);
let Some(avatar) = load_avatar(&avatar_url) else { continue };
avatars.insert(avatar_id, avatar);
}
timeout_add_once(Duration::ZERO, {
move || {
while !added.load(Ordering::SeqCst) {}
GLOBAL.with(|global| {
if let Some(ui) = &*global.borrow() {
for (id, avatar) in avatars.iter() {
if let Some(pics) = ui.avatars.lock().unwrap().remove(id) {
for pic in pics {
pic.set_pixbuf(
load_pixbuf(avatar).ok()
.and_then(|o| o.scale_simple(
32, 32, InterpType::Bilinear
)).as_ref());
}
}
} }
} }
for message in messages.iter() { });
prepare_avatar(&mut ui.avatars.write().unwrap(), message); // TODO: fuck }
on_add_message(ctx.clone(), &ui, message.to_string(), !clear);
}
}
});
}); });
} }
} }
@ -1063,37 +1052,16 @@ fn get_message_box(
hbox hbox
} }
fn prepare_avatar(avatars: &mut RwLockWriteGuard<'_, HashMap<u64, Pixbuf>>, message: &str) { fn get_avatar_id(url: &str) -> u64 {
if let Some(url) = grab_avatar(message) {
let mut hasher = DefaultHasher::new();
hasher.write(url.as_bytes());
let id = hasher.finish();
if !avatars.contains_key(&id) {
let Ok(data) = reqwest::blocking::get(&url).and_then(|o| o.bytes()) else {
return
};
let Ok(pixbuf) = load_pixbuf(&data.to_vec()) else {
return
};
let Some(pixbuf) = pixbuf.scale_simple(32, 32, InterpType::Bilinear) else {
return
};
avatars.insert(id, pixbuf);
}
}
}
fn get_avatar_or_default(ui: &UiModel, url: &str) -> Pixbuf {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
hasher.write(url.as_bytes()); hasher.write(url.as_bytes());
let id = hasher.finish(); hasher.finish()
}
if let Some(pixbuf) = ui.avatars.read().unwrap().get(&id) { fn load_avatar(url: &str) -> Option<Vec<u8>> {
pixbuf.clone() // FIXME: cloning pixbufs is a dangerous shit reqwest::blocking::get(url).ok()
} else { .and_then(|resp| resp.bytes().ok())
ui.default_avatar.clone() .map(|bytes| bytes.to_vec())
}
} }
fn get_new_message_box( fn get_new_message_box(
@ -1128,9 +1096,7 @@ fn get_new_message_box(
nick.as_ref() nick.as_ref()
.map(|o| o.1.to_string()) .map(|o| o.1.to_string())
.unwrap_or("#DDDDDD".to_string()), .unwrap_or("#DDDDDD".to_string()),
avatar avatar.map(|o| get_avatar_id(&o)).unwrap_or_default()
.map(|o| get_avatar_or_default(ui, &o))
.unwrap_or(ui.default_avatar.clone()),
) )
} else { } else {
( (
@ -1139,7 +1105,7 @@ fn get_new_message_box(
message, message,
"System".to_string(), "System".to_string(),
"#DDDDDD".to_string(), "#DDDDDD".to_string(),
ui.default_avatar.clone(), 0
) )
}; };
@ -1170,7 +1136,7 @@ fn get_new_message_box(
let fixed = Fixed::new(); let fixed = Fixed::new();
fixed.set_can_target(false); fixed.set_can_target(false);
let avatar_picture = Picture::for_pixbuf(&avatar); let avatar_picture = Picture::for_pixbuf(&ui.default_avatar.clone());
avatar_picture.set_css_classes(&["message-avatar"]); avatar_picture.set_css_classes(&["message-avatar"]);
avatar_picture.set_vexpand(false); avatar_picture.set_vexpand(false);
avatar_picture.set_hexpand(false); avatar_picture.set_hexpand(false);
@ -1178,6 +1144,16 @@ fn get_new_message_box(
avatar_picture.set_halign(Align::Start); avatar_picture.set_halign(Align::Start);
avatar_picture.set_size_request(32, 32); avatar_picture.set_size_request(32, 32);
if avatar != 0 {
let mut lock = ui.avatars.lock().unwrap();
if let Some(pics) = lock.get_mut(&avatar) {
pics.push(avatar_picture.clone());
} else {
lock.insert(avatar, vec![avatar_picture.clone()]);
}
}
fixed.put(&avatar_picture, 0.0, 4.0); fixed.put(&avatar_picture, 0.0, 4.0);
overlay.add_overlay(&fixed); overlay.add_overlay(&fixed);