You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
723 lines
25 KiB
723 lines
25 KiB
use crate::backend::{room, HandleError}; |
|
use crate::model::fileinfo::ExtraContent; |
|
use comrak::{markdown_to_html, ComrakOptions}; |
|
use gdk_pixbuf::Pixbuf; |
|
use gio::prelude::FileExt; |
|
use glib::source::Continue; |
|
use gtk::prelude::*; |
|
use lazy_static::lazy_static; |
|
use log::error; |
|
use matrix_sdk::identifiers::{EventId, RoomId}; |
|
use matrix_sdk::Client as MatrixClient; |
|
use rand::Rng; |
|
use serde_json::json; |
|
use serde_json::Value as JsonValue; |
|
use std::env::temp_dir; |
|
use std::fs; |
|
use std::path::{Path, PathBuf}; |
|
use url::Url; |
|
|
|
use crate::app::RUNTIME; |
|
use crate::appop::room::Force; |
|
use crate::appop::AppOp; |
|
use crate::App; |
|
|
|
use crate::uitypes::MessageContent; |
|
use crate::uitypes::RowType; |
|
use crate::widgets; |
|
|
|
use crate::model::message::Message; |
|
|
|
pub struct TmpMsg { |
|
pub msg: Message, |
|
pub widget: Option<gtk::Widget>, |
|
} |
|
|
|
impl AppOp { |
|
pub fn get_message_by_id(&self, room_id: &RoomId, id: &EventId) -> Option<Message> { |
|
let room = self.rooms.get(room_id)?; |
|
let id = Some(id); |
|
room.messages.iter().find(|m| m.id.as_ref() == id).cloned() |
|
} |
|
|
|
/// This function is used to mark as read the last message of a room when the focus comes in, |
|
/// so we need to force the mark_as_read because the window isn't active yet |
|
pub fn mark_active_room_messages(&mut self) { |
|
self.mark_last_message_as_read(Force(true)); |
|
} |
|
|
|
pub fn add_room_message(&mut self, msg: &Message) -> Option<()> { |
|
let session_client = self.login_data.as_ref()?.session_client.clone(); |
|
if let Some(ui_msg) = self.create_new_room_message(msg) { |
|
if let Some(ref mut history) = self.history { |
|
history.add_new_message(session_client, self.user_info_cache.clone(), ui_msg); |
|
} |
|
} |
|
None |
|
} |
|
|
|
pub fn remove_room_message(&mut self, msg: &Message) { |
|
let session_client = |
|
unwrap_or_unit_return!(self.login_data.as_ref().map(|ld| ld.session_client.clone())); |
|
if let Some(ui_msg) = self.create_new_room_message(msg) { |
|
if let Some(ref mut history) = self.history { |
|
history.remove_message(session_client, self.user_info_cache.clone(), ui_msg); |
|
} |
|
} |
|
} |
|
|
|
pub fn add_tmp_room_message(&mut self, msg: Message) -> Option<()> { |
|
let login_data = self.login_data.clone()?; |
|
let messages = self.history.as_ref()?.get_listbox(); |
|
if let Some(ui_msg) = self.create_new_room_message(&msg) { |
|
let mb = widgets::MessageBox::new( |
|
login_data.session_client.homeserver().clone(), |
|
login_data.access_token, |
|
) |
|
.tmpwidget( |
|
login_data.session_client.clone(), |
|
self.user_info_cache.clone(), |
|
&ui_msg, |
|
); |
|
let m = mb.get_listbox_row(); |
|
messages.add(m); |
|
|
|
if let Some(w) = messages.get_children().iter().last() { |
|
self.msg_queue.insert( |
|
0, |
|
TmpMsg { |
|
msg: msg.clone(), |
|
widget: Some(w.clone()), |
|
}, |
|
); |
|
}; |
|
} |
|
None |
|
} |
|
|
|
pub fn clear_tmp_msgs(&mut self) -> Option<()> { |
|
let messages = self.history.as_ref()?.get_listbox(); |
|
for t in self.msg_queue.iter_mut() { |
|
if let Some(ref w) = t.widget { |
|
messages.remove(w); |
|
} |
|
t.widget = None; |
|
} |
|
None |
|
} |
|
|
|
pub fn append_tmp_msgs(&mut self) -> Option<()> { |
|
let login_data = self.login_data.clone()?; |
|
let messages = self.history.as_ref()?.get_listbox(); |
|
|
|
let r = self.rooms.get(self.active_room.as_ref()?)?; |
|
let mut widgets = vec![]; |
|
for t in self.msg_queue.iter().rev().filter(|m| m.msg.room == r.id) { |
|
if let Some(ui_msg) = self.create_new_room_message(&t.msg) { |
|
let mb = widgets::MessageBox::new( |
|
login_data.session_client.homeserver().clone(), |
|
login_data.access_token.clone(), |
|
) |
|
.tmpwidget( |
|
login_data.session_client.clone(), |
|
self.user_info_cache.clone(), |
|
&ui_msg, |
|
); |
|
let m = mb.get_listbox_row(); |
|
messages.add(m); |
|
|
|
if let Some(w) = messages.get_children().iter().last() { |
|
widgets.push(w.clone()); |
|
} |
|
} |
|
} |
|
|
|
for (t, w) in self.msg_queue.iter_mut().rev().zip(widgets.iter()) { |
|
t.widget = Some(w.clone()); |
|
} |
|
None |
|
} |
|
|
|
pub fn mark_last_message_as_read(&mut self, Force(force): Force) -> Option<()> { |
|
let login_data = self.login_data.clone()?; |
|
let window: gtk::Window = self |
|
.ui |
|
.builder |
|
.get_object("main_window") |
|
.expect("Can't find main_window in ui file."); |
|
if window.is_active() || force { |
|
/* Move the last viewed mark to the last message */ |
|
let active_room_id = self.active_room.as_ref()?; |
|
let room = self.rooms.get_mut(active_room_id)?; |
|
let uid = login_data.uid.clone(); |
|
room.messages.iter_mut().for_each(|msg| { |
|
if msg.receipt.contains_key(&uid) { |
|
msg.receipt.remove(&uid); |
|
} |
|
}); |
|
let last_message = room.messages.last_mut()?; |
|
last_message.receipt.insert(uid, 0); |
|
|
|
let session_client = login_data.session_client; |
|
let room_id = last_message.room.clone(); |
|
let event_id = last_message.id.clone()?; |
|
RUNTIME.spawn(async move { |
|
match room::mark_as_read(session_client, room_id, event_id).await { |
|
Ok((r, _)) => { |
|
APPOP!(clear_room_notifications, (r)); |
|
} |
|
Err(err) => { |
|
err.handle_error(); |
|
} |
|
} |
|
}); |
|
} |
|
None |
|
} |
|
|
|
pub fn msg_sent(&mut self, evid: EventId) -> Option<()> { |
|
let messages = self.history.as_ref()?.get_listbox(); |
|
if let Some(ref mut m) = self.msg_queue.pop() { |
|
if let Some(ref w) = m.widget { |
|
messages.remove(w); |
|
} |
|
m.widget = None; |
|
m.msg.id = Some(evid); |
|
self.show_room_messages(vec![m.msg.clone()]); |
|
} |
|
self.force_dequeue_message(); |
|
None |
|
} |
|
|
|
pub fn retry_send(&mut self) { |
|
glib::timeout_add_local(5000, move || { |
|
/* This will be removed once tmp messages are refactored */ |
|
APPOP!(force_dequeue_message); |
|
Continue(false) |
|
}); |
|
} |
|
|
|
pub fn force_dequeue_message(&mut self) { |
|
self.sending_message = false; |
|
self.dequeue_message(); |
|
} |
|
|
|
pub fn dequeue_message(&mut self) -> Option<()> { |
|
let session_client = self.login_data.as_ref()?.session_client.clone(); |
|
if self.sending_message { |
|
return None; |
|
} |
|
|
|
self.sending_message = true; |
|
if let Some(next) = self.msg_queue.last() { |
|
let msg = next.msg.clone(); |
|
match next.msg.mtype.as_str() { |
|
"m.image" | "m.file" | "m.audio" | "m.video" => { |
|
RUNTIME.spawn(attach_file(session_client, msg)); |
|
} |
|
_ => { |
|
RUNTIME.spawn(async move { |
|
match room::send_msg(session_client, msg).await { |
|
Ok(evid) => { |
|
APPOP!(msg_sent, (evid)); |
|
let initial = false; |
|
let number_tries = 0; |
|
APPOP!(sync, (initial, number_tries)); |
|
} |
|
Err(err) => { |
|
err.handle_error(); |
|
} |
|
} |
|
}); |
|
} |
|
} |
|
} else { |
|
self.sending_message = false; |
|
} |
|
None |
|
} |
|
|
|
pub fn send_message(&mut self, msg: String) { |
|
if msg.is_empty() { |
|
// Not sending empty messages |
|
return; |
|
} |
|
|
|
if let Some(room) = self.active_room.clone() { |
|
if let Some(sender) = self.login_data.as_ref().map(|ld| ld.uid.clone()) { |
|
let body = msg.clone(); |
|
let mtype = String::from("m.text"); |
|
let mut m = Message::new(room, sender, body, mtype, None); |
|
|
|
if msg.starts_with("/me ") { |
|
m.body = msg.trim_start_matches("/me ").to_owned(); |
|
m.mtype = String::from("m.emote"); |
|
} |
|
|
|
// Riot does not properly show emotes with Markdown; |
|
// Emotes with markdown have a newline after the username |
|
if m.mtype != "m.emote" && self.md_enabled { |
|
let mut md_options = ComrakOptions::default(); |
|
md_options.hardbreaks = true; |
|
let mut md_parsed_msg = markdown_to_html(&msg, &md_options); |
|
|
|
// Removing wrap tag: <p>..</p>\n |
|
let limit = md_parsed_msg.len() - 5; |
|
let trim = match (md_parsed_msg.get(0..3), md_parsed_msg.get(limit..)) { |
|
(Some(open), Some(close)) if open == "<p>" && close == "</p>\n" => true, |
|
_ => false, |
|
}; |
|
if trim { |
|
md_parsed_msg = md_parsed_msg |
|
.get(3..limit) |
|
.unwrap_or(&md_parsed_msg) |
|
.to_string(); |
|
} |
|
|
|
if md_parsed_msg != msg { |
|
m.formatted_body = Some(md_parsed_msg); |
|
m.format = Some(String::from("org.matrix.custom.html")); |
|
} |
|
} |
|
|
|
self.add_tmp_room_message(m); |
|
self.dequeue_message(); |
|
} else { |
|
error!("Can't send message: No user is logged in"); |
|
} |
|
} else { |
|
error!("Can't send message: No active room"); |
|
} |
|
} |
|
|
|
pub fn attach_message(&mut self, path: PathBuf) { |
|
if let Some(room) = self.active_room.clone() { |
|
if let Some(sender) = self.login_data.as_ref().map(|ld| ld.uid.clone()) { |
|
if let Ok(uri) = Url::from_file_path(&path) { |
|
if let Ok(info) = gio::File::new_for_path(&path).query_info( |
|
&gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, |
|
gio::FileQueryInfoFlags::NONE, |
|
gio::NONE_CANCELLABLE, |
|
) { |
|
// This should always return a type |
|
let mime = info |
|
.get_content_type() |
|
.expect("Could not parse content type from file"); |
|
let mtype = match mime.as_ref() { |
|
m if m.starts_with("image") => "m.image", |
|
m if m.starts_with("audio") => "m.audio", |
|
"application/x-riff" => "m.audio", |
|
m if m.starts_with("video") => "m.video", |
|
"application/x-mpegURL" => "m.video", |
|
_ => "m.file", |
|
}; |
|
let body = path |
|
.file_name() |
|
.and_then(|s| s.to_str()) |
|
.map(Into::into) |
|
.unwrap_or_default(); |
|
|
|
let mut m = Message::new(room, sender, body, mtype.to_string(), None); |
|
let info = match mtype { |
|
"m.image" => get_image_media_info(&path, mime.as_ref()), |
|
"m.audio" => get_audio_video_media_info(&uri, mime.as_ref()), |
|
"m.video" => get_audio_video_media_info(&uri, mime.as_ref()), |
|
"m.file" => get_file_media_info(&path, mime.as_ref()), |
|
_ => None, |
|
}; |
|
|
|
m.extra_content = info; |
|
m.local_path = Some(path); |
|
|
|
self.add_tmp_room_message(m); |
|
self.dequeue_message(); |
|
} else { |
|
error!("Can't send message: Could not query info"); |
|
} |
|
} else { |
|
error!("Can't send message: Path is not absolute") |
|
} |
|
} else { |
|
error!("Can't send message: No user is logged in"); |
|
} |
|
} else { |
|
error!("Can't send message: No active room"); |
|
} |
|
} |
|
|
|
/// This method is called when a tmp message with an attach is sent correctly |
|
/// to the matrix media server and we've the real url to use so we can |
|
/// replace the tmp message with the same id with this new one |
|
pub fn attached_file(&mut self, msg: Message) -> Option<()> { |
|
let messages = self.history.as_ref()?.get_listbox(); |
|
let p = self.msg_queue.iter().position(|m| m.msg == msg); |
|
if let Some(i) = p { |
|
let w = self.msg_queue.remove(i); |
|
if let Some(w) = w.widget { |
|
messages.remove(&w); |
|
} |
|
} |
|
self.add_tmp_room_message(msg); |
|
None |
|
} |
|
|
|
/* TODO: find a better name for this function */ |
|
pub fn show_room_messages(&mut self, newmsgs: Vec<Message>) -> Option<()> { |
|
let mut msgs = vec![]; |
|
|
|
for msg in newmsgs.iter() { |
|
if let Some(r) = self.rooms.get_mut(&msg.room) { |
|
if !r.messages.contains(msg) { |
|
r.messages.push(msg.clone()); |
|
msgs.push(msg.clone()); |
|
} |
|
} |
|
} |
|
|
|
let mut msg_in_active = false; |
|
let login_data = self.login_data.clone()?; |
|
let uid = login_data.uid; |
|
for msg in msgs.iter() { |
|
if !msg.redacted && self.active_room.as_ref().map_or(false, |x| x == &msg.room) { |
|
self.add_room_message(&msg); |
|
msg_in_active = true; |
|
} |
|
|
|
if msg.replace != None { |
|
/* No need to notify (and confuse the user) about edits. */ |
|
continue; |
|
} |
|
|
|
let should_notify = msg.sender != uid |
|
&& (msg.body.contains(&login_data.username.clone()?) |
|
|| self.rooms.get(&msg.room).map_or(false, |r| r.direct)); |
|
|
|
if should_notify { |
|
let window: gtk::Window = self |
|
.ui |
|
.builder |
|
.get_object("main_window") |
|
.expect("Can't find main_window in ui file."); |
|
if let (Some(app), Some(event_id)) = (window.get_application(), msg.id.clone()) { |
|
self.notify(app, msg.room.clone(), event_id); |
|
} |
|
} |
|
|
|
self.roomlist.moveup(&msg.room); |
|
self.roomlist.set_bold(msg.room.clone(), true); |
|
} |
|
|
|
if msg_in_active { |
|
self.mark_last_message_as_read(Force(false)); |
|
} |
|
|
|
None |
|
} |
|
|
|
/* TODO: find a better name for this function */ |
|
pub fn show_room_messages_top( |
|
&mut self, |
|
msgs: Vec<Message>, |
|
room_id: RoomId, |
|
prev_batch: Option<String>, |
|
) { |
|
let session_client = |
|
unwrap_or_unit_return!(self.login_data.as_ref().map(|ld| ld.session_client.clone())); |
|
if let Some(r) = self.rooms.get_mut(&room_id) { |
|
r.prev_batch = prev_batch; |
|
} |
|
|
|
let active_room = self.active_room.as_ref(); |
|
let mut list = vec![]; |
|
for item in msgs.iter().rev() { |
|
/* create a list of new messages to load to the history */ |
|
if active_room.map_or(false, |a_room| item.room == *a_room) && !item.redacted { |
|
if let Some(ui_msg) = self.create_new_room_message(item) { |
|
list.push(ui_msg); |
|
} |
|
} |
|
|
|
if let Some(r) = self.rooms.get_mut(&item.room) { |
|
r.messages.insert(0, item.clone()); |
|
} |
|
} |
|
|
|
if let Some(ref mut history) = self.history { |
|
history.add_old_messages_in_batch(session_client, self.user_info_cache.clone(), list); |
|
} |
|
} |
|
|
|
pub fn remove_message(&mut self, room_id: RoomId, id: EventId) -> Option<()> { |
|
let message = self.get_message_by_id(&room_id, &id); |
|
|
|
if let Some(msg) = message { |
|
self.remove_room_message(&msg); |
|
if let Some(ref mut room) = self.rooms.get_mut(&msg.room) { |
|
if let Some(ref mut message) = room.messages.iter_mut().find(|e| e.id == msg.id) { |
|
message.redacted = true; |
|
} |
|
} |
|
} |
|
None |
|
} |
|
|
|
/* parese a backend Message into a Message for the UI */ |
|
pub fn create_new_room_message(&self, msg: &Message) -> Option<MessageContent> { |
|
let login_data = self.login_data.clone()?; |
|
let mut highlights = vec![]; |
|
lazy_static! { |
|
static ref EMOJI_REGEX: regex::Regex = regex::Regex::new(r"(?x) |
|
^ |
|
[\p{White_Space}\p{Emoji}\p{Emoji_Presentation}\p{Emoji_Modifier}\p{Emoji_Modifier_Base}\p{Emoji_Component}]* |
|
[\p{Emoji}]+ |
|
[\p{White_Space}\p{Emoji}\p{Emoji_Presentation}\p{Emoji_Modifier}\p{Emoji_Modifier_Base}\p{Emoji_Component}]* |
|
$ |
|
# That string is made of at least one emoji, possibly more, possibly with modifiers, possibly with spaces, but nothing else |
|
").unwrap(); |
|
} |
|
|
|
let t = match msg.mtype.as_ref() { |
|
"m.emote" => RowType::Emote, |
|
"m.image" => RowType::Image, |
|
"m.sticker" => RowType::Sticker, |
|
"m.audio" => RowType::Audio, |
|
"m.video" => RowType::Video, |
|
"m.file" => RowType::File, |
|
_ => { |
|
/* set message type to mention if the body contains the username, we should |
|
* also match for MXID */ |
|
let is_mention = if let Some(user) = login_data.username.clone() { |
|
msg.sender != login_data.uid && msg.body.contains(&user) |
|
} else { |
|
false |
|
}; |
|
|
|
if is_mention { |
|
if let Some(user) = login_data.username { |
|
highlights.push(user); |
|
} |
|
highlights.push(login_data.uid.to_string()); |
|
highlights.push(String::from("message_menu")); |
|
|
|
RowType::Mention |
|
} else if EMOJI_REGEX.is_match(&msg.body) { |
|
RowType::Emoji |
|
} else { |
|
RowType::Message |
|
} |
|
} |
|
}; |
|
|
|
let room = self.rooms.get(&msg.room)?; |
|
let name = if let Some(member) = room.members.get(&msg.sender) { |
|
member.alias.clone() |
|
} else { |
|
None |
|
}; |
|
|
|
let admin = room |
|
.admins |
|
.get(&login_data.uid) |
|
.copied() |
|
.unwrap_or_default(); |
|
let redactable = admin != 0 || login_data.uid == msg.sender; |
|
|
|
let is_last_viewed = msg.receipt.contains_key(&login_data.uid); |
|
Some(create_ui_message( |
|
msg.clone(), |
|
name, |
|
t, |
|
highlights, |
|
redactable, |
|
is_last_viewed, |
|
)) |
|
} |
|
} |
|
|
|
/* FIXME: don't convert msg to ui messages here, we should later get a ui message from storage */ |
|
fn create_ui_message( |
|
msg: Message, |
|
name: Option<String>, |
|
t: RowType, |
|
highlights: Vec<String>, |
|
redactable: bool, |
|
last_viewed: bool, |
|
) -> MessageContent { |
|
MessageContent { |
|
msg: msg.clone(), |
|
id: msg.id, |
|
sender: msg.sender, |
|
sender_name: name, |
|
mtype: t, |
|
body: msg.body, |
|
date: msg.date, |
|
replace_date: if msg.replace.is_some() { |
|
Some(msg.date) |
|
} else { |
|
None |
|
}, |
|
thumb: msg.thumb, |
|
url: msg.url, |
|
local_path: msg.local_path, |
|
formatted_body: msg.formatted_body, |
|
format: msg.format, |
|
last_viewed, |
|
highlights, |
|
redactable, |
|
widget: None, |
|
} |
|
} |
|
|
|
/// This function opens the image, creates a thumbnail |
|
/// and populates the info Json with the information it has |
|
|
|
fn get_image_media_info(file: &Path, mimetype: &str) -> Option<JsonValue> { |
|
let (_, w, h) = Pixbuf::get_file_info(file)?; |
|
let size = fs::metadata(file).ok()?.len(); |
|
|
|
// make thumbnail max 800x600 |
|
let thumb = Pixbuf::from_file_at_scale(&file, 800, 600, true).ok()?; |
|
let mut rng = rand::thread_rng(); |
|
let x: u64 = rng.gen_range(1, 9_223_372_036_854_775_807); |
|
let thumb_path = format!( |
|
"{}/fractal_{}.png", |
|
temp_dir().to_str().unwrap_or_default(), |
|
x.to_string() |
|
); |
|
thumb.savev(&thumb_path, "png", &[]).ok()?; |
|
let thumb_size = fs::metadata(&thumb_path).ok()?.len(); |
|
|
|
let info = json!({ |
|
"info": { |
|
"thumbnail_url": thumb_path, |
|
"thumbnail_info": { |
|
"w": thumb.get_width(), |
|
"h": thumb.get_height(), |
|
"size": thumb_size, |
|
"mimetype": "image/png" |
|
}, |
|
"w": w, |
|
"h": h, |
|
"size": size, |
|
"mimetype": mimetype, |
|
"orientation": 0 |
|
} |
|
}); |
|
|
|
Some(info) |
|
} |
|
|
|
fn get_audio_video_media_info(uri: &Url, mimetype: &str) -> Option<JsonValue> { |
|
let size = fs::metadata(uri.to_file_path().ok()?).ok()?.len(); |
|
|
|
if let Some(duration) = widgets::inline_player::get_media_duration(uri) |
|
.ok() |
|
.and_then(|d| d.mseconds()) |
|
{ |
|
Some(json!({ |
|
"info": { |
|
"size": size, |
|
"mimetype": mimetype, |
|
"duration": duration, |
|
} |
|
})) |
|
} else { |
|
Some(json!({ |
|
"info": { |
|
"size": size, |
|
"mimetype": mimetype, |
|
} |
|
})) |
|
} |
|
} |
|
|
|
fn get_file_media_info(file: &Path, mimetype: &str) -> Option<JsonValue> { |
|
let size = fs::metadata(file).ok()?.len(); |
|
|
|
let info = json!({ |
|
"info": { |
|
"size": size, |
|
"mimetype": mimetype, |
|
} |
|
}); |
|
|
|
Some(info) |
|
} |
|
|
|
struct NonMediaMsg; |
|
|
|
async fn attach_file(session_client: MatrixClient, mut msg: Message) -> Result<(), NonMediaMsg> { |
|
let mut extra_content: Option<ExtraContent> = msg |
|
.extra_content |
|
.clone() |
|
.and_then(|c| serde_json::from_value(c).ok()); |
|
|
|
let thumb_url = extra_content.clone().and_then(|c| c.info.thumbnail_url); |
|
|
|
match (msg.url.clone(), msg.local_path.as_ref(), thumb_url) { |
|
(Some(url), _, Some(thumb)) if url.scheme() == "mxc" && thumb.scheme() == "mxc" => { |
|
send_msg_and_manage(session_client, msg).await; |
|
|
|
Ok(()) |
|
} |
|
(_, Some(local_path), _) => { |
|
if let Some(ref local_path_thumb) = msg.local_path_thumb { |
|
let response = room::upload_file(session_client.clone(), local_path_thumb) |
|
.await |
|
.and_then(|response| Url::parse(&response.content_uri).map_err(Into::into)); |
|
|
|
match response { |
|
Ok(thumb_uri) => { |
|
msg.thumb = Some(thumb_uri.clone()); |
|
if let Some(ref mut xctx) = extra_content { |
|
xctx.info.thumbnail_url = Some(thumb_uri); |
|
} |
|
msg.extra_content = serde_json::to_value(&extra_content).ok(); |
|
} |
|
Err(err) => { |
|
err.handle_error(); |
|
} |
|
} |
|
|
|
if let Err(_e) = std::fs::remove_file(local_path_thumb) { |
|
error!("Can't remove thumbnail: {}", local_path_thumb.display()); |
|
} |
|
} |
|
|
|
let query = room::upload_file(session_client.clone(), &local_path) |
|
.await |
|
.and_then(|response| { |
|
msg.url = Some(Url::parse(&response.content_uri)?); |
|
RUNTIME.spawn(send_msg_and_manage(session_client, msg.clone())); |
|
|
|
Ok(msg) |
|
}); |
|
|
|
match query { |
|
Ok(msg) => { |
|
APPOP!(attached_file, (msg)); |
|
} |
|
Err(err) => { |
|
err.handle_error(); |
|
} |
|
}; |
|
|
|
Ok(()) |
|
} |
|
_ => Err(NonMediaMsg), |
|
} |
|
} |
|
|
|
async fn send_msg_and_manage(session_client: MatrixClient, msg: Message) { |
|
match room::send_msg(session_client, msg).await { |
|
Ok(evid) => { |
|
APPOP!(msg_sent, (evid)); |
|
let initial = false; |
|
let number_tries = 0; |
|
APPOP!(sync, (initial, number_tries)); |
|
} |
|
Err(err) => { |
|
err.handle_error(); |
|
} |
|
}; |
|
}
|
|
|