1 use crate::backend::{dw_media, media, room, ContentType, ThreadPool};
2 use fractal_api::identifiers::RoomId;
3 use fractal_api::r0::AccessToken;
4 use glib::clone;
5 use log::error;
6 use std::cell::RefCell;
7 use std::fs;
8 use std::process::Command;
9 use std::rc::Rc;
10 use std::sync::mpsc::channel;
11 use std::sync::mpsc::TryRecvError;
12 use std::sync::mpsc::{Receiver, Sender};
13 use std::thread;
14 
15 use crate::actions::AppState;
16 use crate::backend::HandleError;
17 use crate::i18n::i18n;
18 use crate::types::Message;
19 use crate::uibuilder::UI;
20 use crate::App;
21 use fractal_api::url::Url;
22 use gio::ActionGroupExt;
23 use gio::ActionMapExt;
24 use gio::SimpleAction;
25 use gio::SimpleActionGroup;
26 use glib::source::Continue;
27 use gtk::prelude::*;
28 
29 use super::global::{get_event_id, get_message_by_id, get_room_id};
30 
31 use crate::widgets::ErrorDialog;
32 use crate::widgets::FileDialog::save;
33 use crate::widgets::SourceDialog;
34 
35 /* This creates all actions the room history can perform */
new( thread_pool: ThreadPool, server_url: Url, access_token: AccessToken, ui: UI, back_history: Rc<RefCell<Vec<AppState>>>, ) -> gio::SimpleActionGroup36 pub fn new(
37     thread_pool: ThreadPool,
38     server_url: Url,
39     access_token: AccessToken,
40     ui: UI,
41     back_history: Rc<RefCell<Vec<AppState>>>,
42 ) -> gio::SimpleActionGroup {
43     let actions = SimpleActionGroup::new();
44     /* Action for each message */
45     let reply = SimpleAction::new("reply", glib::VariantTy::new("s").ok());
46     let open_with = SimpleAction::new("open_with", glib::VariantTy::new("s").ok());
47     let save_as = SimpleAction::new("save_as", glib::VariantTy::new("s").ok());
48     let copy_image = SimpleAction::new("copy_image", glib::VariantTy::new("s").ok());
49     let copy_text = SimpleAction::new("copy_text", glib::VariantTy::new("s").ok());
50     let delete = SimpleAction::new("delete", glib::VariantTy::new("s").ok());
51     let show_source = SimpleAction::new("show_source", glib::VariantTy::new("s").ok());
52 
53     /* TODO: use stateful action to keep  track if the user already requested new messages */
54     let load_more_messages =
55         SimpleAction::new("request_older_messages", glib::VariantTy::new("s").ok());
56 
57     actions.add_action(&reply);
58     actions.add_action(&open_with);
59     actions.add_action(&save_as);
60     actions.add_action(&copy_image);
61     actions.add_action(&copy_text);
62     actions.add_action(&delete);
63     actions.add_action(&show_source);
64     actions.add_action(&load_more_messages);
65 
66     let parent: gtk::Window = ui
67         .builder
68         .get_object("main_window")
69         .expect("Can't find main_window in ui file.");
70     show_source.connect_activate(clone!(@weak parent => move |_, data| {
71         let viewer = SourceDialog::new();
72         viewer.set_parent_window(&parent);
73         if let Some(m) = get_message(data) {
74             let error = i18n("This message has no source.");
75             let source = m.source.as_ref().unwrap_or(&error);
76 
77             viewer.show(source);
78         }
79     }));
80 
81     let window = ui
82         .builder
83         .get_object::<gtk::ApplicationWindow>("main_window")
84         .expect("Couldn't find main_window in ui file.");
85     reply.connect_activate(clone!(
86     @weak back_history,
87     @weak window,
88     @weak ui.sventry.view as msg_entry
89     => move |_, data| {
90         let state = back_history.borrow().last().cloned();
91         if let Some(AppState::MediaViewer) = state {
92             if let Some(action_group) = window.get_action_group("app") {
93                 action_group.activate_action("back", None);
94             } else {
95                 error!("The action group app is not attached to the main window.");
96             }
97         }
98         if let Some(buffer) = msg_entry.get_buffer() {
99             let mut start = buffer.get_start_iter();
100             if let Some(m) = get_message(data) {
101                 let quote = m
102                     .body
103                     .lines()
104                     .map(|l| "> ".to_owned() + l)
105                     .collect::<Vec<String>>()
106                     .join("\n")
107                     + "\n"
108                     + "\n";
109                 buffer.insert(&mut start, &quote);
110                 msg_entry.grab_focus();
111             }
112         }
113     }));
114 
115     open_with.connect_activate(clone!(@strong server_url => move |_, data| {
116         if let Some(m) = get_message(data) {
117             if let Some(url) = m.url {
118                 thread::spawn(clone!(@strong server_url => move || {
119                     match dw_media(server_url, &url, ContentType::Download, None) {
120                         Ok(fname) => {
121                             Command::new("xdg-open")
122                                 .arg(&fname)
123                                 .spawn()
124                                 .expect("failed to execute process");
125                         }
126                         Err(err) => {
127                             err.handle_error()
128                         }
129                     }
130                 }));
131             }
132         }
133     }));
134 
135     save_as.connect_activate(clone!(
136     @strong server_url,
137     @strong thread_pool,
138     @weak parent as window
139     => move |_, data| {
140         if let Some(m) = get_message(data) {
141             if let Some(url) = m.url {
142                 let name = m.body;
143 
144                 let (tx, rx): (
145                     Sender<media::MediaResult>,
146                     Receiver<media::MediaResult>,
147                 ) = channel();
148 
149                 media::get_media_async(thread_pool.clone(), server_url.clone(), url, tx);
150 
151                 gtk::timeout_add(
152                     50,
153                     clone!(
154                         @strong name,
155                         @weak window
156                         => @default-return Continue(true), move || match rx.try_recv() {
157                             Err(TryRecvError::Empty) => Continue(true),
158                             Err(TryRecvError::Disconnected) => {
159                                 let msg = i18n("Could not download the file");
160                                 ErrorDialog::new(false, &msg);
161 
162                                 Continue(true)
163                             },
164                             Ok(Ok(fname)) => {
165                                 if let Some(path) = save(&window, &name, &[]) {
166                                     // TODO use glib to copy file
167                                     if fs::copy(fname, path).is_err() {
168                                         ErrorDialog::new(false, &i18n("Couldn’t save file"));
169                                     }
170                                 }
171                                 Continue(false)
172                             }
173                             Ok(Err(err)) => {
174                                 error!("Media path could not be found due to error: {:?}", err);
175                                 Continue(false)
176                             }
177                         }
178                     ),
179                 );
180             }
181         }
182     }));
183 
184     copy_image.connect_activate(clone!(@strong server_url => move |_, data| {
185         if let Some(m) = get_message(data) {
186             if let Some(url) = m.url {
187                 let (tx, rx): (
188                     Sender<media::MediaResult>,
189                     Receiver<media::MediaResult>,
190                 ) = channel();
191 
192                 media::get_media_async(thread_pool.clone(), server_url.clone(), url, tx);
193 
194                 gtk::timeout_add(50, move || match rx.try_recv() {
195                     Err(TryRecvError::Empty) => Continue(true),
196                     Err(TryRecvError::Disconnected) => {
197                         let msg = i18n("Could not download the file");
198                         ErrorDialog::new(false, &msg);
199 
200                         Continue(true)
201                     }
202                     Ok(Ok(fname)) => {
203                         if let Ok(pixbuf) = gdk_pixbuf::Pixbuf::new_from_file(fname) {
204                             let atom = gdk::Atom::intern("CLIPBOARD");
205                             let clipboard = gtk::Clipboard::get(&atom);
206 
207                             clipboard.set_image(&pixbuf);
208                         }
209 
210                         Continue(false)
211                     }
212                     Ok(Err(err)) => {
213                         error!("Image path could not be found due to error: {:?}", err);
214                         Continue(false)
215                     }
216                 });
217             }
218         }
219     }));
220 
221     copy_text.connect_activate(move |_, data| {
222         if let Some(m) = get_message(data) {
223             let atom = gdk::Atom::intern("CLIPBOARD");
224             let clipboard = gtk::Clipboard::get(&atom);
225 
226             clipboard.set_text(&m.body);
227         }
228     });
229 
230     let s = server_url.clone();
231     let tk = access_token.clone();
232     delete.connect_activate(clone!(
233     @weak back_history,
234     @weak window
235     => move |_, data| {
236         let state = back_history.borrow().last().cloned();
237         if let Some(AppState::MediaViewer) = state {
238             if let Some(action_group) = window.get_action_group("app") {
239                 action_group.activate_action("back", None);
240             } else {
241                 error!("The action group app is not attached to the main window.");
242             }
243         }
244         if let Some(msg) = get_message(data) {
245             let server = s.clone();
246             let access_token = tk.clone();
247             thread::spawn(move || {
248                 let query = room::redact_msg(server, access_token, msg);
249                 if let Err(err) = query {
250                     err.handle_error();
251                 }
252             });
253         }
254     }));
255 
256     load_more_messages.connect_activate(clone!(
257     @strong server_url,
258     @strong access_token
259     => move |_, data| {
260         let id = get_room_id(data);
261         request_more_messages(server_url.clone(), access_token.clone(), id);
262     }));
263 
264     actions
265 }
266 
get_message(id: Option<&glib::Variant>) -> Option<Message>267 fn get_message(id: Option<&glib::Variant>) -> Option<Message> {
268     get_event_id(id).as_ref().and_then(get_message_by_id)
269 }
270 
request_more_messages( server_url: Url, access_token: AccessToken, id: Option<RoomId>, ) -> Option<()>271 fn request_more_messages(
272     server_url: Url,
273     access_token: AccessToken,
274     id: Option<RoomId>,
275 ) -> Option<()> {
276     let op = App::get_op()?;
277     let op = op.lock().unwrap();
278     let id = id?;
279     let r = op.rooms.get(&id)?;
280     if let Some(prev_batch) = r.prev_batch.clone() {
281         thread::spawn(move || {
282             match room::get_room_messages(server_url, access_token, id, prev_batch) {
283                 Ok((msgs, room, prev_batch)) => {
284                     APPOP!(show_room_messages_top, (msgs, room, prev_batch));
285                 }
286                 Err(err) => {
287                     err.handle_error();
288                 }
289             }
290         });
291     } else if let Some(msg) = r.messages.iter().next().cloned() {
292         // no prev_batch so we use the last message to calculate that in the backend
293         thread::spawn(move || {
294             match room::get_room_messages_from_msg(server_url, access_token, id, msg) {
295                 Ok((msgs, room, prev_batch)) => {
296                     APPOP!(show_room_messages_top, (msgs, room, prev_batch));
297                 }
298                 Err(err) => {
299                     err.handle_error();
300                 }
301             }
302         });
303     } else if let Some(from) = op.since.clone() {
304         // no messages and no prev_batch so we use the last since
305         thread::spawn(
306             move || match room::get_room_messages(server_url, access_token, id, from) {
307                 Ok((msgs, room, prev_batch)) => {
308                     APPOP!(show_room_messages_top, (msgs, room, prev_batch));
309                 }
310                 Err(err) => {
311                     err.handle_error();
312                 }
313             },
314         );
315     }
316     None
317 }
318