1 use crate::backend::{room, HandleError};
2 use crate::types::ExtraContent;
3 use comrak::{markdown_to_html, ComrakOptions};
4 use fractal_api::identifiers::{EventId, RoomId};
5 use fractal_api::r0::AccessToken;
6 use fractal_api::url::Url;
7 use gdk_pixbuf::Pixbuf;
8 use gio::prelude::FileExt;
9 use glib::clone;
10 use glib::source::Continue;
11 use gtk::prelude::*;
12 use lazy_static::lazy_static;
13 use log::error;
14 use rand::Rng;
15 use serde_json::json;
16 use serde_json::Value as JsonValue;
17 use std::env::temp_dir;
18 use std::fs;
19 use std::path::{Path, PathBuf};
20 use std::thread;
21 
22 use crate::appop::room::Force;
23 use crate::appop::AppOp;
24 use crate::App;
25 
26 use crate::uitypes::MessageContent;
27 use crate::uitypes::RowType;
28 use crate::widgets;
29 
30 use crate::types::Message;
31 
32 pub struct TmpMsg {
33     pub msg: Message,
34     pub widget: Option<gtk::Widget>,
35 }
36 
37 impl AppOp {
get_message_by_id(&self, room_id: &RoomId, id: &EventId) -> Option<Message>38     pub fn get_message_by_id(&self, room_id: &RoomId, id: &EventId) -> Option<Message> {
39         let room = self.rooms.get(room_id)?;
40         let id = Some(id);
41         room.messages.iter().find(|m| m.id.as_ref() == id).cloned()
42     }
43 
44     /// This function is used to mark as read the last message of a room when the focus comes in,
45     /// so we need to force the mark_as_read because the window isn't active yet
mark_active_room_messages(&mut self)46     pub fn mark_active_room_messages(&mut self) {
47         self.mark_last_message_as_read(Force(true));
48     }
49 
add_room_message(&mut self, msg: &Message) -> Option<()>50     pub fn add_room_message(&mut self, msg: &Message) -> Option<()> {
51         if let Some(ui_msg) = self.create_new_room_message(msg) {
52             if let Some(ref mut history) = self.history {
53                 history.add_new_message(
54                     self.thread_pool.clone(),
55                     self.user_info_cache.clone(),
56                     ui_msg,
57                 );
58             }
59         }
60         None
61     }
62 
remove_room_message(&mut self, msg: &Message)63     pub fn remove_room_message(&mut self, msg: &Message) {
64         if let Some(ui_msg) = self.create_new_room_message(msg) {
65             if let Some(ref mut history) = self.history {
66                 history.remove_message(
67                     self.thread_pool.clone(),
68                     self.user_info_cache.clone(),
69                     ui_msg,
70                 );
71             }
72         }
73     }
74 
add_tmp_room_message(&mut self, msg: Message) -> Option<()>75     pub fn add_tmp_room_message(&mut self, msg: Message) -> Option<()> {
76         let login_data = self.login_data.clone()?;
77         let messages = self.history.as_ref()?.get_listbox();
78         if let Some(ui_msg) = self.create_new_room_message(&msg) {
79             let mb = widgets::MessageBox::new(login_data.server_url, login_data.access_token)
80                 .tmpwidget(
81                     self.thread_pool.clone(),
82                     self.user_info_cache.clone(),
83                     &ui_msg,
84                 );
85             let m = mb.get_listbox_row();
86             messages.add(m);
87 
88             if let Some(w) = messages.get_children().iter().last() {
89                 self.msg_queue.insert(
90                     0,
91                     TmpMsg {
92                         msg: msg.clone(),
93                         widget: Some(w.clone()),
94                     },
95                 );
96             };
97         }
98         None
99     }
100 
clear_tmp_msgs(&mut self)101     pub fn clear_tmp_msgs(&mut self) {
102         for t in self.msg_queue.iter_mut() {
103             if let Some(ref w) = t.widget {
104                 w.destroy();
105             }
106             t.widget = None;
107         }
108     }
109 
append_tmp_msgs(&mut self) -> Option<()>110     pub fn append_tmp_msgs(&mut self) -> Option<()> {
111         let login_data = self.login_data.clone()?;
112         let messages = self.history.as_ref()?.get_listbox();
113 
114         let r = self.rooms.get(self.active_room.as_ref()?)?;
115         let mut widgets = vec![];
116         for t in self.msg_queue.iter().rev().filter(|m| m.msg.room == r.id) {
117             if let Some(ui_msg) = self.create_new_room_message(&t.msg) {
118                 let mb = widgets::MessageBox::new(
119                     login_data.server_url.clone(),
120                     login_data.access_token.clone(),
121                 )
122                 .tmpwidget(
123                     self.thread_pool.clone(),
124                     self.user_info_cache.clone(),
125                     &ui_msg,
126                 );
127                 let m = mb.get_listbox_row();
128                 messages.add(m);
129 
130                 if let Some(w) = messages.get_children().iter().last() {
131                     widgets.push(w.clone());
132                 }
133             }
134         }
135 
136         for (t, w) in self.msg_queue.iter_mut().rev().zip(widgets.iter()) {
137             t.widget = Some(w.clone());
138         }
139         None
140     }
141 
mark_last_message_as_read(&mut self, Force(force): Force) -> Option<()>142     pub fn mark_last_message_as_read(&mut self, Force(force): Force) -> Option<()> {
143         let login_data = self.login_data.clone()?;
144         let window: gtk::Window = self
145             .ui
146             .builder
147             .get_object("main_window")
148             .expect("Can't find main_window in ui file.");
149         if window.is_active() || force {
150             /* Move the last viewed mark to the last message */
151             let active_room_id = self.active_room.as_ref()?;
152             let room = self.rooms.get_mut(active_room_id)?;
153             let uid = login_data.uid.clone();
154             room.messages.iter_mut().for_each(|msg| {
155                 if msg.receipt.contains_key(&uid) {
156                     msg.receipt.remove(&uid);
157                 }
158             });
159             let last_message = room.messages.last_mut()?;
160             last_message.receipt.insert(uid, 0);
161 
162             let room_id = last_message.room.clone();
163             let event_id = last_message.id.clone()?;
164             thread::spawn(move || {
165                 match room::mark_as_read(
166                     login_data.server_url,
167                     login_data.access_token,
168                     room_id,
169                     event_id,
170                 ) {
171                     Ok((r, _)) => {
172                         APPOP!(clear_room_notifications, (r));
173                     }
174                     Err(err) => {
175                         err.handle_error();
176                     }
177                 }
178             });
179         }
180         None
181     }
182 
msg_sent(&mut self, _txid: String, evid: Option<EventId>)183     pub fn msg_sent(&mut self, _txid: String, evid: Option<EventId>) {
184         if let Some(ref mut m) = self.msg_queue.pop() {
185             if let Some(ref w) = m.widget {
186                 w.destroy();
187             }
188             m.widget = None;
189             m.msg.id = evid;
190             self.show_room_messages(vec![m.msg.clone()]);
191         }
192         self.force_dequeue_message();
193     }
194 
retry_send(&mut self)195     pub fn retry_send(&mut self) {
196         gtk::timeout_add(5000, move || {
197             /* This will be removed once tmp messages are refactored */
198             APPOP!(force_dequeue_message);
199             Continue(false)
200         });
201     }
202 
force_dequeue_message(&mut self)203     pub fn force_dequeue_message(&mut self) {
204         self.sending_message = false;
205         self.dequeue_message();
206     }
207 
dequeue_message(&mut self) -> Option<()>208     pub fn dequeue_message(&mut self) -> Option<()> {
209         let login_data = self.login_data.clone()?;
210         if self.sending_message {
211             return None;
212         }
213 
214         self.sending_message = true;
215         if let Some(next) = self.msg_queue.last() {
216             let msg = next.msg.clone();
217             match &next.msg.mtype[..] {
218                 "m.image" | "m.file" | "m.audio" | "m.video" => {
219                     thread::spawn(move || {
220                         attach_file(login_data.server_url, login_data.access_token, msg)
221                     });
222                 }
223                 _ => {
224                     thread::spawn(move || {
225                         match room::send_msg(login_data.server_url, login_data.access_token, msg) {
226                             Ok((txid, evid)) => {
227                                 APPOP!(msg_sent, (txid, evid));
228                                 let initial = false;
229                                 let number_tries = 0;
230                                 APPOP!(sync, (initial, number_tries));
231                             }
232                             Err(err) => {
233                                 err.handle_error();
234                             }
235                         }
236                     });
237                 }
238             }
239         } else {
240             self.sending_message = false;
241         }
242         None
243     }
244 
send_message(&mut self, msg: String)245     pub fn send_message(&mut self, msg: String) {
246         if msg.is_empty() {
247             // Not sending empty messages
248             return;
249         }
250 
251         if let Some(room) = self.active_room.clone() {
252             if let Some(sender) = self.login_data.as_ref().map(|ld| ld.uid.clone()) {
253                 let body = msg.clone();
254                 let mtype = String::from("m.text");
255                 let mut m = Message::new(room, sender, body, mtype, None);
256 
257                 if msg.starts_with("/me ") {
258                     m.body = msg.trim_start_matches("/me ").to_owned();
259                     m.mtype = String::from("m.emote");
260                 }
261 
262                 // Riot does not properly show emotes with Markdown;
263                 // Emotes with markdown have a newline after the username
264                 if m.mtype != "m.emote" && self.md_enabled {
265                     let mut md_options = ComrakOptions::default();
266                     md_options.hardbreaks = true;
267                     let mut md_parsed_msg = markdown_to_html(&msg, &md_options);
268 
269                     // Removing wrap tag: <p>..</p>\n
270                     let limit = md_parsed_msg.len() - 5;
271                     let trim = match (md_parsed_msg.get(0..3), md_parsed_msg.get(limit..)) {
272                         (Some(open), Some(close)) if open == "<p>" && close == "</p>\n" => true,
273                         _ => false,
274                     };
275                     if trim {
276                         md_parsed_msg = md_parsed_msg
277                             .get(3..limit)
278                             .unwrap_or(&md_parsed_msg)
279                             .to_string();
280                     }
281 
282                     if md_parsed_msg != msg {
283                         m.formatted_body = Some(md_parsed_msg);
284                         m.format = Some(String::from("org.matrix.custom.html"));
285                     }
286                 }
287 
288                 self.add_tmp_room_message(m);
289                 self.dequeue_message();
290             } else {
291                 error!("Can't send message: No user is logged in");
292             }
293         } else {
294             error!("Can't send message: No active room");
295         }
296     }
297 
attach_message(&mut self, path: PathBuf)298     pub fn attach_message(&mut self, path: PathBuf) {
299         if let Some(room) = self.active_room.clone() {
300             if let Some(sender) = self.login_data.as_ref().map(|ld| ld.uid.clone()) {
301                 if let Ok(uri) = Url::from_file_path(&path) {
302                     if let Ok(info) = gio::File::new_for_path(&path).query_info(
303                         &gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
304                         gio::FileQueryInfoFlags::NONE,
305                         gio::NONE_CANCELLABLE,
306                     ) {
307                         // This should always return a type
308                         let mime = info
309                             .get_content_type()
310                             .expect("Could not parse content type from file");
311                         let mtype = match mime.as_ref() {
312                             m if m.starts_with("image") => "m.image",
313                             m if m.starts_with("audio") => "m.audio",
314                             "application/x-riff" => "m.audio",
315                             m if m.starts_with("video") => "m.video",
316                             "application/x-mpegURL" => "m.video",
317                             _ => "m.file",
318                         };
319                         let body = path
320                             .file_name()
321                             .and_then(|s| s.to_str())
322                             .map(Into::into)
323                             .unwrap_or_default();
324 
325                         let mut m = Message::new(room, sender, body, mtype.to_string(), None);
326                         let info = match mtype {
327                             "m.image" => get_image_media_info(&path, mime.as_ref()),
328                             "m.audio" => get_audio_video_media_info(&uri, mime.as_ref()),
329                             "m.video" => get_audio_video_media_info(&uri, mime.as_ref()),
330                             "m.file" => get_file_media_info(&path, mime.as_ref()),
331                             _ => None,
332                         };
333 
334                         m.extra_content = info;
335                         m.local_path = Some(path);
336 
337                         self.add_tmp_room_message(m);
338                         self.dequeue_message();
339                     } else {
340                         error!("Can't send message: Could not query info");
341                     }
342                 } else {
343                     error!("Can't send message: Path is not absolute")
344                 }
345             } else {
346                 error!("Can't send message: No user is logged in");
347             }
348         } else {
349             error!("Can't send message: No active room");
350         }
351     }
352 
353     /// This method is called when a tmp message with an attach is sent correctly
354     /// to the matrix media server and we've the real url to use so we can
355     /// replace the tmp message with the same id with this new one
attached_file(&mut self, msg: Message)356     pub fn attached_file(&mut self, msg: Message) {
357         let p = self.msg_queue.iter().position(|m| m.msg == msg);
358         if let Some(i) = p {
359             let w = self.msg_queue.remove(i);
360             if let Some(w) = w.widget {
361                 w.destroy()
362             }
363         }
364         self.add_tmp_room_message(msg);
365     }
366 
367     /* TODO: find a better name for this function */
show_room_messages(&mut self, newmsgs: Vec<Message>) -> Option<()>368     pub fn show_room_messages(&mut self, newmsgs: Vec<Message>) -> Option<()> {
369         let mut msgs = vec![];
370 
371         for msg in newmsgs.iter() {
372             if let Some(r) = self.rooms.get_mut(&msg.room) {
373                 if !r.messages.contains(msg) {
374                     r.messages.push(msg.clone());
375                     msgs.push(msg.clone());
376                 }
377             }
378         }
379 
380         let mut msg_in_active = false;
381         let login_data = self.login_data.clone()?;
382         let uid = login_data.uid;
383         for msg in msgs.iter() {
384             if !msg.redacted && self.active_room.as_ref().map_or(false, |x| x == &msg.room) {
385                 self.add_room_message(&msg);
386                 msg_in_active = true;
387             }
388 
389             if msg.replace != None {
390                 /* No need to notify (and confuse the user) about edits. */
391                 continue;
392             }
393 
394             let should_notify = msg.sender != uid
395                 && (msg.body.contains(&login_data.username.clone()?)
396                     || self.rooms.get(&msg.room).map_or(false, |r| r.direct));
397 
398             if should_notify {
399                 let window: gtk::Window = self
400                     .ui
401                     .builder
402                     .get_object("main_window")
403                     .expect("Can't find main_window in ui file.");
404                 if let (Some(app), Some(event_id)) = (window.get_application(), msg.id.as_ref()) {
405                     self.notify(app, &msg.room, event_id);
406                 }
407             }
408 
409             self.roomlist.moveup(msg.room.clone());
410             self.roomlist.set_bold(msg.room.clone(), true);
411         }
412 
413         if msg_in_active {
414             self.mark_last_message_as_read(Force(false));
415         }
416 
417         None
418     }
419 
420     /* TODO: find a better name for this function */
show_room_messages_top( &mut self, msgs: Vec<Message>, room_id: RoomId, prev_batch: Option<String>, )421     pub fn show_room_messages_top(
422         &mut self,
423         msgs: Vec<Message>,
424         room_id: RoomId,
425         prev_batch: Option<String>,
426     ) {
427         if let Some(r) = self.rooms.get_mut(&room_id) {
428             r.prev_batch = prev_batch;
429         }
430 
431         let active_room = self.active_room.as_ref();
432         let mut list = vec![];
433         for item in msgs.iter().rev() {
434             /* create a list of new messages to load to the history */
435             if active_room.map_or(false, |a_room| item.room == *a_room) && !item.redacted {
436                 if let Some(ui_msg) = self.create_new_room_message(item) {
437                     list.push(ui_msg);
438                 }
439             }
440 
441             if let Some(r) = self.rooms.get_mut(&item.room) {
442                 r.messages.insert(0, item.clone());
443             }
444         }
445 
446         if let Some(ref mut history) = self.history {
447             history.add_old_messages_in_batch(
448                 self.thread_pool.clone(),
449                 self.user_info_cache.clone(),
450                 list,
451             );
452         }
453     }
454 
remove_message(&mut self, room_id: RoomId, id: EventId) -> Option<()>455     pub fn remove_message(&mut self, room_id: RoomId, id: EventId) -> Option<()> {
456         let message = self.get_message_by_id(&room_id, &id);
457 
458         if let Some(msg) = message {
459             self.remove_room_message(&msg);
460             if let Some(ref mut room) = self.rooms.get_mut(&msg.room) {
461                 if let Some(ref mut message) = room.messages.iter_mut().find(|e| e.id == msg.id) {
462                     message.redacted = true;
463                 }
464             }
465         }
466         None
467     }
468 
469     /* parese a backend Message into a Message for the UI */
create_new_room_message(&self, msg: &Message) -> Option<MessageContent>470     pub fn create_new_room_message(&self, msg: &Message) -> Option<MessageContent> {
471         let login_data = self.login_data.clone()?;
472         let mut highlights = vec![];
473         lazy_static! {
474             static ref EMOJI_REGEX: regex::Regex = regex::Regex::new(r"(?x)
475                 ^
476                 [\p{White_Space}\p{Emoji}\p{Emoji_Presentation}\p{Emoji_Modifier}\p{Emoji_Modifier_Base}\p{Emoji_Component}]*
477                 [\p{Emoji}]+
478                 [\p{White_Space}\p{Emoji}\p{Emoji_Presentation}\p{Emoji_Modifier}\p{Emoji_Modifier_Base}\p{Emoji_Component}]*
479                 $
480                 # That string is made of at least one emoji, possibly more, possibly with modifiers, possibly with spaces, but nothing else
481                 ").unwrap();
482         }
483 
484         let t = match msg.mtype.as_ref() {
485             "m.emote" => RowType::Emote,
486             "m.image" => RowType::Image,
487             "m.sticker" => RowType::Sticker,
488             "m.audio" => RowType::Audio,
489             "m.video" => RowType::Video,
490             "m.file" => RowType::File,
491             _ => {
492                 /* set message type to mention if the body contains the username, we should
493                  * also match for MXID */
494                 let is_mention = if let Some(user) = login_data.username.clone() {
495                     msg.sender != login_data.uid && msg.body.contains(&user)
496                 } else {
497                     false
498                 };
499 
500                 if is_mention {
501                     if let Some(user) = login_data.username {
502                         highlights.push(user);
503                     }
504                     highlights.push(login_data.uid.to_string());
505                     highlights.push(String::from("message_menu"));
506 
507                     RowType::Mention
508                 } else if EMOJI_REGEX.is_match(&msg.body) {
509                     RowType::Emoji
510                 } else {
511                     RowType::Message
512                 }
513             }
514         };
515 
516         let room = self.rooms.get(&msg.room)?;
517         let name = if let Some(member) = room.members.get(&msg.sender) {
518             member.alias.clone()
519         } else {
520             None
521         };
522 
523         let admin = room
524             .admins
525             .get(&login_data.uid)
526             .copied()
527             .unwrap_or_default();
528         let redactable = admin != 0 || login_data.uid == msg.sender;
529 
530         let is_last_viewed = msg.receipt.contains_key(&login_data.uid);
531         Some(create_ui_message(
532             msg.clone(),
533             name,
534             t,
535             highlights,
536             redactable,
537             is_last_viewed,
538         ))
539     }
540 }
541 
542 /* FIXME: don't convert msg to ui messages here, we should later get a ui message from storage */
create_ui_message( msg: Message, name: Option<String>, t: RowType, highlights: Vec<String>, redactable: bool, last_viewed: bool, ) -> MessageContent543 fn create_ui_message(
544     msg: Message,
545     name: Option<String>,
546     t: RowType,
547     highlights: Vec<String>,
548     redactable: bool,
549     last_viewed: bool,
550 ) -> MessageContent {
551     MessageContent {
552         msg: msg.clone(),
553         id: msg.id,
554         sender: msg.sender,
555         sender_name: name,
556         mtype: t,
557         body: msg.body,
558         date: msg.date,
559         replace_date: if msg.replace.is_some() {
560             Some(msg.date)
561         } else {
562             None
563         },
564         thumb: msg.thumb,
565         url: msg.url,
566         local_path: msg.local_path,
567         formatted_body: msg.formatted_body,
568         format: msg.format,
569         last_viewed,
570         highlights,
571         redactable,
572         widget: None,
573     }
574 }
575 
576 /// This function opens the image, creates a thumbnail
577 /// and populates the info Json with the information it has
578 
get_image_media_info(file: &Path, mimetype: &str) -> Option<JsonValue>579 fn get_image_media_info(file: &Path, mimetype: &str) -> Option<JsonValue> {
580     let (_, w, h) = Pixbuf::get_file_info(file)?;
581     let size = fs::metadata(file).ok()?.len();
582 
583     // make thumbnail max 800x600
584     let thumb = Pixbuf::new_from_file_at_scale(&file, 800, 600, true).ok()?;
585     let mut rng = rand::thread_rng();
586     let x: u64 = rng.gen_range(1, 9_223_372_036_854_775_807);
587     let thumb_path = format!(
588         "{}/fractal_{}.png",
589         temp_dir().to_str().unwrap_or_default(),
590         x.to_string()
591     );
592     thumb.savev(&thumb_path, "png", &[]).ok()?;
593     let thumb_size = fs::metadata(&thumb_path).ok()?.len();
594 
595     let info = json!({
596         "info": {
597             "thumbnail_url": thumb_path,
598             "thumbnail_info": {
599                 "w": thumb.get_width(),
600                 "h": thumb.get_height(),
601                 "size": thumb_size,
602                 "mimetype": "image/png"
603             },
604             "w": w,
605             "h": h,
606             "size": size,
607             "mimetype": mimetype,
608             "orientation": 0
609         }
610     });
611 
612     Some(info)
613 }
614 
get_audio_video_media_info(uri: &Url, mimetype: &str) -> Option<JsonValue>615 fn get_audio_video_media_info(uri: &Url, mimetype: &str) -> Option<JsonValue> {
616     let size = fs::metadata(uri.to_file_path().ok()?).ok()?.len();
617 
618     if let Some(duration) = widgets::inline_player::get_media_duration(uri)
619         .ok()
620         .and_then(|d| d.mseconds())
621     {
622         Some(json!({
623             "info": {
624                 "size": size,
625                 "mimetype": mimetype,
626                 "duration": duration,
627             }
628         }))
629     } else {
630         Some(json!({
631             "info": {
632                 "size": size,
633                 "mimetype": mimetype,
634             }
635         }))
636     }
637 }
638 
get_file_media_info(file: &Path, mimetype: &str) -> Option<JsonValue>639 fn get_file_media_info(file: &Path, mimetype: &str) -> Option<JsonValue> {
640     let size = fs::metadata(file).ok()?.len();
641 
642     let info = json!({
643         "info": {
644             "size": size,
645             "mimetype": mimetype,
646         }
647     });
648 
649     Some(info)
650 }
651 
652 struct NonMediaMsg;
653 
attach_file(baseu: Url, tk: AccessToken, mut msg: Message) -> Result<(), NonMediaMsg>654 fn attach_file(baseu: Url, tk: AccessToken, mut msg: Message) -> Result<(), NonMediaMsg> {
655     let mut extra_content: Option<ExtraContent> = msg
656         .extra_content
657         .clone()
658         .and_then(|c| serde_json::from_value(c).ok());
659 
660     let thumb_url = extra_content.clone().and_then(|c| c.info.thumbnail_url);
661 
662     match (msg.url.clone(), msg.local_path.as_ref(), thumb_url) {
663         (Some(url), _, Some(thumb)) if url.scheme() == "mxc" && thumb.scheme() == "mxc" => {
664             send_msg_and_manage(baseu, tk, msg);
665 
666             Ok(())
667         }
668         (_, Some(local_path), _) => {
669             if let Some(ref local_path_thumb) = msg.local_path_thumb {
670                 let response = room::upload_file(baseu.clone(), tk.clone(), local_path_thumb)
671                     .and_then(|response| Url::parse(&response.content_uri).map_err(Into::into));
672 
673                 match response {
674                     Ok(thumb_uri) => {
675                         msg.thumb = Some(thumb_uri.clone());
676                         if let Some(ref mut xctx) = extra_content {
677                             xctx.info.thumbnail_url = Some(thumb_uri);
678                         }
679                         msg.extra_content = serde_json::to_value(&extra_content).ok();
680                     }
681                     Err(err) => {
682                         err.handle_error();
683                     }
684                 }
685 
686                 if let Err(_e) = std::fs::remove_file(local_path_thumb) {
687                     error!("Can't remove thumbnail: {}", local_path_thumb.display());
688                 }
689             }
690 
691             let query =
692                 room::upload_file(baseu.clone(), tk.clone(), local_path).and_then(|response| {
693                     msg.url = Some(Url::parse(&response.content_uri)?);
694                     thread::spawn(
695                         clone!(@strong msg => move || send_msg_and_manage(baseu, tk, msg)),
696                     );
697 
698                     Ok(msg)
699                 });
700 
701             match query {
702                 Ok(msg) => {
703                     APPOP!(attached_file, (msg));
704                 }
705                 Err(err) => {
706                     err.handle_error();
707                 }
708             };
709 
710             Ok(())
711         }
712         _ => Err(NonMediaMsg),
713     }
714 }
715 
send_msg_and_manage(baseu: Url, tk: AccessToken, msg: Message)716 fn send_msg_and_manage(baseu: Url, tk: AccessToken, msg: Message) {
717     match room::send_msg(baseu, tk, msg) {
718         Ok((txid, evid)) => {
719             APPOP!(msg_sent, (txid, evid));
720             let initial = false;
721             let number_tries = 0;
722             APPOP!(sync, (initial, number_tries));
723         }
724         Err(err) => {
725             err.handle_error();
726         }
727     };
728 }
729