Compare commits
3 Commits
main
...
load-avata
Author | SHA1 | Date | |
---|---|---|---|
60c22a282e | |||
c9323b38de | |||
d5af178c91 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
/target
|
/target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
/data
|
||||||
|
|
|
@ -16,3 +16,5 @@ sha1 = "0.10.6"
|
||||||
tokio = { version = "1", features = [ "rt" ] }
|
tokio = { version = "1", features = [ "rt" ] }
|
||||||
xmpp = { git = "https://gitlab.com/xmpp-rs/xmpp-rs" }
|
xmpp = { git = "https://gitlab.com/xmpp-rs/xmpp-rs" }
|
||||||
async-channel = "2.3.1"
|
async-channel = "2.3.1"
|
||||||
|
pretty_env_logger = "0.5"
|
||||||
|
log = "0.4"
|
||||||
|
|
41
src/main.rs
41
src/main.rs
|
@ -15,6 +15,7 @@
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
|
use adw::subclass::prelude::ObjectSubclassIsExt;
|
||||||
use gtk::{gio, glib};
|
use gtk::{gio, glib};
|
||||||
|
|
||||||
mod message;
|
mod message;
|
||||||
|
@ -41,6 +42,8 @@ fn xep_0392(input: &str) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
pretty_env_logger::init();
|
||||||
|
|
||||||
let mut args = std::env::args();
|
let mut args = std::env::args();
|
||||||
let _ = args.next().unwrap();
|
let _ = args.next().unwrap();
|
||||||
let username = args.next().expect("Please give username argument 1");
|
let username = args.next().expect("Please give username argument 1");
|
||||||
|
@ -52,21 +55,32 @@ fn main() {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let tabs_store = gio::ListStore::new::<Tab>();
|
let tabs_store = gio::ListStore::new::<Tab>();
|
||||||
|
|
||||||
let (xmpp_receiver, cmd_sender) = xmpp_client::client(&username, &password);
|
|
||||||
let tabs_store_copy = tabs_store.clone();
|
let tabs_store_copy = tabs_store.clone();
|
||||||
|
let (xmpp_receiver, cmd_sender) = xmpp_client::client(&username, &password);
|
||||||
glib::spawn_future_local(async move {
|
glib::spawn_future_local(async move {
|
||||||
while let Ok(event) = xmpp_receiver.recv().await {
|
while let Ok(event) = xmpp_receiver.recv().await {
|
||||||
match event {
|
match event {
|
||||||
xmpp::Event::ContactAdded(jid) => {
|
xmpp_client::XMPPEvent::Avatar(jid, avatar) => {
|
||||||
let tab = Tab::new(jid.jid.as_str(), jid.jid.as_str());
|
log::info!("AVATAR");
|
||||||
|
if let Some(tab) = tabs_store_copy.iter().find(|tab| {
|
||||||
|
let tab: &glib::object::Object = tab.as_ref().unwrap();
|
||||||
|
let tab: &Tab = tab.downcast_ref().unwrap();
|
||||||
|
tab.jid() == jid.as_str()
|
||||||
|
}) {
|
||||||
|
let tab: &Tab = tab.as_ref().unwrap().downcast_ref().unwrap();
|
||||||
|
tab.set_avatar_hash(avatar.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xmpp_client::XMPPEvent::Contact(jid, avatar) => {
|
||||||
|
log::info!("CONTACT");
|
||||||
|
let tab = Tab::new(jid.as_str(), jid.as_str());
|
||||||
|
|
||||||
|
if let Some(avatar) = avatar {
|
||||||
|
tab.set_avatar_hash(avatar);
|
||||||
|
}
|
||||||
|
|
||||||
tabs_store_copy.append(&tab);
|
tabs_store_copy.append(&tab);
|
||||||
}
|
}
|
||||||
xmpp::Event::ChatMessage(_id, _from, _body, _time) => {
|
|
||||||
// TODO: Insert message into tab history
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_ => continue,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -125,9 +139,18 @@ fn main() {
|
||||||
list_item.set_child(Some(&tab_widget));
|
list_item.set_child(Some(&tab_widget));
|
||||||
});
|
});
|
||||||
tabs_factory.connect_bind(move |_, list_item| {
|
tabs_factory.connect_bind(move |_, list_item| {
|
||||||
|
// executed when switch tab, one time for previous tab and one time for new tab
|
||||||
|
// but also when create new tab
|
||||||
let list_item: >k::ListItem = list_item.downcast_ref().unwrap();
|
let list_item: >k::ListItem = list_item.downcast_ref().unwrap();
|
||||||
let tab: Tab = list_item.item().and_downcast().unwrap();
|
let tab: Tab = list_item.item().and_downcast().unwrap();
|
||||||
let tab_widget: widgets::ChatTab = list_item.child().and_downcast().unwrap();
|
let tab_widget: widgets::ChatTab = list_item.child().and_downcast().unwrap();
|
||||||
|
tab.bind_property(
|
||||||
|
"avatar_hash",
|
||||||
|
&tab_widget.imp().avatar.try_get().unwrap(),
|
||||||
|
"file",
|
||||||
|
)
|
||||||
|
.sync_create()
|
||||||
|
.build();
|
||||||
|
|
||||||
tab_widget.set_name(&tab.name());
|
tab_widget.set_name(&tab.name());
|
||||||
tab_widget.set_jid(&tab.jid());
|
tab_widget.set_jid(&tab.jid());
|
||||||
|
|
|
@ -25,8 +25,8 @@ mod imp {
|
||||||
#[derive(glib::Properties)]
|
#[derive(glib::Properties)]
|
||||||
#[properties(wrapper_type = super::Tab)]
|
#[properties(wrapper_type = super::Tab)]
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
//#[property(get, set)]
|
#[property(get, set)]
|
||||||
//avatar: RefCell<String>,
|
avatar_hash: RefCell<Option<String>>,
|
||||||
#[property(get, construct_only)]
|
#[property(get, construct_only)]
|
||||||
jid: RefCell<String>,
|
jid: RefCell<String>,
|
||||||
#[property(get, set)]
|
#[property(get, set)]
|
||||||
|
@ -36,6 +36,7 @@ mod imp {
|
||||||
impl Default for Tab {
|
impl Default for Tab {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Tab {
|
Tab {
|
||||||
|
avatar_hash: RefCell::new(None),
|
||||||
jid: RefCell::new(String::new()),
|
jid: RefCell::new(String::new()),
|
||||||
name: RefCell::new(String::new()),
|
name: RefCell::new(String::new()),
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,13 +68,21 @@ impl ChatTab {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_jid(&self, jid: &str) {
|
pub fn set_jid(&self, jid: &str) {
|
||||||
let hash = "123456789abcdef123456789abcdef123456789a";
|
|
||||||
self.imp().avatar.set_from_file(Some(format!(
|
|
||||||
"/home/linkmauve/cache/poezio/avatars/{jid}/{hash}"
|
|
||||||
)));
|
|
||||||
self.set_tooltip_text(Some(jid));
|
self.set_tooltip_text(Some(jid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_avatar_hash(&self, hash: &str) {
|
||||||
|
// let xdg_cache = std::env::var("XDG_CACHE_HOME").unwrap_or(format!(
|
||||||
|
// "{}/.cache",
|
||||||
|
// std::env::var("HOME").expect("NO $HOME?!")
|
||||||
|
// ));
|
||||||
|
// let xdg_cache = PathBuf::from(xdg_cache);
|
||||||
|
// let entry = xdg_cache.join("poezio/avatars").join(self.tooltip_text().unwrap()).join(hash);
|
||||||
|
|
||||||
|
// self.imp().avatar.set_from_file(Some(entry.to_str().unwrap().to_string()));
|
||||||
|
self.imp().avatar.set_from_file(Some(hash));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_name(&self, name: &str) {
|
pub fn set_name(&self, name: &str) {
|
||||||
self.imp().name.set_label(name);
|
self.imp().name.set_label(name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use async_channel::{Receiver, Sender};
|
use async_channel::{Receiver, Sender};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use xmpp::{BareJid, ClientBuilder, ClientFeature, Event};
|
use xmpp::{BareJid, ClientBuilder, ClientFeature};
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -9,13 +10,76 @@ pub enum XMPPCommand {
|
||||||
SendPM(BareJid, String),
|
SendPM(BareJid, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tokio_runtime() -> &'static Runtime {
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum XMPPEvent {
|
||||||
|
Avatar(BareJid, String),
|
||||||
|
Contact(BareJid, Option<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tokio_runtime() -> &'static Runtime {
|
||||||
static RUNTIME: OnceLock<Runtime> = OnceLock::new();
|
static RUNTIME: OnceLock<Runtime> = OnceLock::new();
|
||||||
RUNTIME.get_or_init(|| Runtime::new().expect("Setting up tokio runtime needs to succeed."))
|
RUNTIME.get_or_init(|| Runtime::new().expect("Setting up tokio runtime needs to succeed."))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn client(jid: &str, password: &str) -> (Receiver<xmpp::Event>, Sender<XMPPCommand>) {
|
pub async fn handle_xmpp_event(event: xmpp::Event) -> Option<XMPPEvent> {
|
||||||
let (event_sender, event_receiver) = async_channel::bounded::<xmpp::Event>(1);
|
match event {
|
||||||
|
xmpp::Event::AvatarRetrieved(jid, hash) => {
|
||||||
|
// need update latest symlink?
|
||||||
|
// xmpp-rs stores avatar in data/JID/HASH... we create data/JID/latest symlink points to relative HASH
|
||||||
|
|
||||||
|
let mut latest_link = PathBuf::from(&hash);
|
||||||
|
let hash_name = latest_link
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
latest_link.set_file_name("latest");
|
||||||
|
|
||||||
|
if let Ok(metadata) = tokio::fs::metadata(&latest_link).await {
|
||||||
|
if metadata.is_symlink() {
|
||||||
|
if let Ok(previous_latest) = tokio::fs::read_link(&latest_link).await {
|
||||||
|
if previous_latest == PathBuf::from(&hash_name) {
|
||||||
|
// We already have latest symlink
|
||||||
|
log::info!("avatar Already have latest link {jid} to hash {hash}");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting new latest symlink
|
||||||
|
let _ = tokio::fs::symlink(hash_name, latest_link).await;
|
||||||
|
|
||||||
|
if jid.is_bare() {
|
||||||
|
Some(XMPPEvent::Avatar(jid.to_bare(), hash))
|
||||||
|
} else {
|
||||||
|
// no avatar for FullJid (yet)
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xmpp::Event::ContactAdded(jid) => {
|
||||||
|
let avatar_jid = jid.jid.clone();
|
||||||
|
if let Ok(hash_name) =
|
||||||
|
tokio::fs::read_link(&format!("data/{}/latest", &avatar_jid)).await
|
||||||
|
{
|
||||||
|
let avatar = format!("data/{}/{}", &avatar_jid, hash_name.display());
|
||||||
|
log::info!("Found existing avatar for contact: {avatar_jid}");
|
||||||
|
Some(XMPPEvent::Contact(avatar_jid, Some(avatar)))
|
||||||
|
} else {
|
||||||
|
Some(XMPPEvent::Contact(avatar_jid, None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xmpp::Event::ChatMessage(_id, _from, _body, _time) => {
|
||||||
|
// TODO: Insert message into tab history
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn client(jid: &str, password: &str) -> (Receiver<XMPPEvent>, Sender<XMPPCommand>) {
|
||||||
|
let (event_sender, event_receiver) = async_channel::bounded::<XMPPEvent>(1);
|
||||||
let (cmd_sender, cmd_receiver) = async_channel::bounded::<XMPPCommand>(1);
|
let (cmd_sender, cmd_receiver) = async_channel::bounded::<XMPPCommand>(1);
|
||||||
|
|
||||||
let jid = jid.to_string();
|
let jid = jid.to_string();
|
||||||
|
@ -24,6 +88,7 @@ pub(crate) fn client(jid: &str, password: &str) -> (Receiver<xmpp::Event>, Sende
|
||||||
tokio_runtime().spawn(async move {
|
tokio_runtime().spawn(async move {
|
||||||
let mut client = ClientBuilder::new(BareJid::new(&jid).unwrap(), &password)
|
let mut client = ClientBuilder::new(BareJid::new(&jid).unwrap(), &password)
|
||||||
.set_default_nick("xmpp-client")
|
.set_default_nick("xmpp-client")
|
||||||
|
.enable_feature(ClientFeature::Avatars)
|
||||||
.enable_feature(ClientFeature::ContactList)
|
.enable_feature(ClientFeature::ContactList)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -31,16 +96,8 @@ pub(crate) fn client(jid: &str, password: &str) -> (Receiver<xmpp::Event>, Sende
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
Some(events) = client.wait_for_events() => {
|
Some(events) = client.wait_for_events() => {
|
||||||
for event in events {
|
for event in events {
|
||||||
match event {
|
if let Some(parsed_event) = handle_xmpp_event(event).await {
|
||||||
Event::ContactAdded(_) => {
|
let _ = event_sender.send(parsed_event).await;
|
||||||
event_sender.send(event).await.expect("BOOOOOOHOOOO");
|
|
||||||
}
|
|
||||||
Event::ChatMessage(_, _, _, _) => {
|
|
||||||
event_sender.send(event).await.expect("BOOOHOOOO");
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user