1 /**
2 * Whiteboard session manager
3 *
4 * Authors:
5 * David Yip <yipdw@rose-hulman.edu>
6 * Bob Jamison (Pedro port)
7 *
8 * Copyright (c) 2005 Authors
9 *
10 * Released under GNU GPL, read the file 'COPYING' for more information
11 */
13 #include <functional>
14 #include <algorithm>
15 #include <iostream>
17 #include <gtkmm.h>
18 #include <glibmm/i18n.h>
20 #include "xml/node-observer.h"
22 #include "pedro/pedrodom.h"
24 #include "document.h"
25 #include "desktop.h"
26 #include "desktop-handles.h"
28 #include "jabber_whiteboard/message-verifier.h"
29 #include "jabber_whiteboard/session-manager.h"
30 #include "jabber_whiteboard/inkboard-document.h"
31 #include "jabber_whiteboard/new-inkboard-document.h"
33 #include "jabber_whiteboard/dialog/choose-desktop.h"
35 #define INKBOARD_XMLNS "http://inkscape.org/inkboard"
37 namespace Inkscape {
39 namespace Whiteboard {
41 //#########################################################################
42 //# S E S S I O N M A N A G E R
43 //#########################################################################
45 SessionManager *sessionManagerInstance = NULL;
47 void SessionManager::showClient()
48 {
49 SessionManager::instance().gui.show();
50 }
52 SessionManager&
53 SessionManager::instance()
54 {
55 if (!sessionManagerInstance)
56 sessionManagerInstance = new SessionManager();
57 return *sessionManagerInstance;
58 }
60 SessionManager::SessionManager()
61 {
62 sequenceNumber = 0L;
63 getClient().addXmppEventListener(*this);
65 this->_check_pending_invitations = Glib::signal_timeout().connect(sigc::mem_fun(*this, &SessionManager::_checkInvitationQueue), 50);
66 this->_check_invitation_responses = Glib::signal_timeout().connect(sigc::mem_fun(*this, &SessionManager::_checkInvitationResponseQueue), 50);
67 }
69 SessionManager::~SessionManager()
70 {
71 getClient().removeXmppEventListener(*this);
72 getClient().disconnect();
73 }
75 unsigned long SessionManager::getSequenceNumber()
76 {
77 return sequenceNumber++;
78 }
80 bool
81 SessionManager::send(const Glib::ustring &destJid,
82 const MessageType type,
83 const Glib::ustring &data)
84 {
85 Pedro::DOMString xmlData = Pedro::Parser::encode(data);
86 char *fmt=
87 "<message type='chat' from='%s' to='%s' id='ink_%d'>"
88 "<w xmlns='%s' "
89 "protocol='%d' type='%d' seq='%d'><x:inkboard-data>%s</x:inkboard-data></inkboard>"
90 "<body></body>"
91 "</message>";
92 if (!getClient().write(fmt,
93 getClient().getJid().c_str(),
94 destJid.c_str(),
95 getClient().getMsgId(),
96 INKBOARD_XMLNS,
97 2,
98 (MessageType)type,
99 getSequenceNumber(),
100 xmlData.c_str()
101 ))
102 {
103 return false;
104 }
106 return true;
107 }
109 bool
110 SessionManager::sendGroup(const Glib::ustring &groupJid,
111 const MessageType type,
112 const Glib::ustring &data)
113 {
114 Pedro::DOMString xmlData = Pedro::Parser::encode(data);
115 char *fmt=
116 "<message type='groupchat' from='%s' to='%s' id='ink_%d'>"
117 "<inkboard xmlns='%s' "
118 "protocol='%d' type='%d' seq='%d'><x:inkboard-data>%s</x:inkboard-data></inkboard>"
119 "<body></body>"
120 "</message>";
121 if (!getClient().write(fmt,
122 getClient().getJid().c_str(),
123 groupJid.c_str(),
124 getClient().getMsgId(),
125 INKBOARD_XMLNS,
126 2,
127 type,
128 getSequenceNumber(),
129 xmlData.c_str()
130 ))
131 {
132 return false;
133 }
135 return true;
136 }
138 void
139 SessionManager::processXmppEvent(const Pedro::XmppEvent &event)
140 {
141 int type = event.getType();
143 switch (type) {
144 case Pedro::XmppEvent::EVENT_STATUS:
145 {
146 break;
147 }
148 case Pedro::XmppEvent::EVENT_ERROR:
149 {
150 break;
151 }
152 case Pedro::XmppEvent::EVENT_CONNECTED:
153 {
154 break;
155 }
156 case Pedro::XmppEvent::EVENT_DISCONNECTED:
157 {
158 break;
159 }
160 case Pedro::XmppEvent::EVENT_MESSAGE:
161 {
162 printf("## SM message:%s\n", event.getFrom().c_str());
163 Pedro::Element *root = event.getDOM();
165 if (root)
166 {
167 if (root->getTagAttribute("inkboard", "xmlns") ==
168 INKBOARD_XMLNS)
169 {
170 _processInkboardEvent(event);
171 }
172 }
173 break;
174 }
175 case Pedro::XmppEvent::EVENT_PRESENCE:
176 {
177 break;
178 }
179 case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
180 {
181 printf("## SM MUC message:%s\n", event.getFrom().c_str());
182 Pedro::Element *root = event.getDOM();
183 if (root)
184 {
185 if (root->getTagAttribute("inkboard", "xmlns") ==
186 INKBOARD_XMLNS)
187 {
188 _processInkboardEvent(event);
189 }
190 }
191 break;
192 }
193 case Pedro::XmppEvent::EVENT_MUC_JOIN:
194 {
195 break;
196 }
197 case Pedro::XmppEvent::EVENT_MUC_LEAVE:
198 {
199 break;
200 }
201 case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
202 {
203 break;
204 }
205 default:
206 {
207 break;
208 }
209 }
210 }
212 /**
213 * Initiates a shared session with a user or conference room.
214 *
215 * \param to The recipient to which this desktop will be linked, specified as a JID.
216 * \param type Type of the session; i.e. private message or group chat.
217 */
218 void
219 SessionManager::doShare(Glib::ustring const& to, SessionType type)
220 {
221 InkboardDocument* doc;
222 SPDesktop* dt;
224 switch (type)
225 {
226 // Just create a new blank canvas for MUC sessions
227 case INKBOARD_MUC:
229 dt = createInkboardDesktop(to, type);
231 if (dt != NULL)
232 {
233 doc = dynamic_cast< InkboardDocument* >(sp_desktop_document(dt)->rdoc);
235 if (doc != NULL)
236 {
237 doc->startSessionNegotiation();
238 }
239 }
240 break;
242 // Let the user pick the document which to start a peer ro peer session
243 // with, or a blank one, then create a blank document, copy over the contents
244 // and initialise session
245 case INKBOARD_PRIVATE:
246 default:
248 ChooseDesktop dialog;
249 int result = dialog.run();
251 if(result == Gtk::RESPONSE_OK)
252 {
253 SPDesktop *desktop = dialog.getDesktop();
254 dt = createInkboardDesktop(to, type);
256 if (dt != NULL)
257 {
258 doc = dynamic_cast< InkboardDocument* >(sp_desktop_document(dt)->rdoc);
260 if (doc != NULL)
261 {
262 if(desktop != NULL)
263 {
264 Inkscape::XML::Document *old_doc = sp_desktop_document(desktop)->rdoc;
265 doc->root()->mergeFrom(old_doc->root(),"id");
266 }
268 doc->startSessionNegotiation();
269 }
270 }
271 }
272 break;
273 }
274 }
276 /**
277 * Clone of sp_file_new and all related subroutines and functions,
278 * with appropriate modifications to use the Inkboard document class.
279 *
280 * \param to The JID to which this Inkboard document will be connected.
281 * \return A pointer to the created desktop, or NULL if a new desktop could not be created.
282 */
283 SPDesktop*
284 SessionManager::createInkboardDesktop(Glib::ustring const& to, SessionType type)
285 {
286 // Create document (sp_repr_document_new clone)
287 SPDocument* doc = makeInkboardDocument(g_quark_from_static_string("xml"), "svg:svg", type, to);
288 g_return_val_if_fail(doc != NULL, NULL);
290 InkboardDocument* inkdoc = dynamic_cast< InkboardDocument* >(doc->rdoc);
291 if (inkdoc == NULL) { // this shouldn't ever happen...
292 return NULL;
293 }
295 // Create desktop and attach document
296 SPDesktop *dt = makeInkboardDesktop(doc);
297 _inkboards.push_back(Inkboard_record_type(to, inkdoc));
298 return dt;
299 }
301 void
302 SessionManager::terminateInkboardSession(Glib::ustring const& to)
303 {
304 std::cout << "Terminating Inkboard session to " << to << std::endl;
305 Inkboards_type::iterator i = _inkboards.begin();
306 for(; i != _inkboards.end(); ++i) {
307 if ((*i).first == to) {
308 break;
309 }
310 }
312 if (i != _inkboards.end()) {
313 std::cout << "Erasing Inkboard session to " << to << std::endl;
314 (*i).second->terminateSession();
315 _inkboards.erase(i);
316 }
317 }
319 InkboardDocument*
320 SessionManager::getInkboardSession(Glib::ustring const& to)
321 {
322 Inkboards_type::iterator i = _inkboards.begin();
323 for(; i != _inkboards.end(); ++i) {
324 if ((*i).first == to) {
325 return (*i).second;
326 }
327 }
328 return NULL;
329 }
331 void
332 SessionManager::_processInkboardEvent(Pedro::XmppEvent const& event)
333 {
334 Pedro::Element* root = event.getDOM();
336 if (root == NULL) {
337 g_warning("Received null DOM; ignoring message.");
338 return;
339 }
341 Pedro::DOMString type = root->getTagAttribute("inkboard", "type");
342 Pedro::DOMString seq = root->getTagAttribute("inkboard", "seq");
343 Pedro::DOMString protover = root->getTagAttribute("inkboard", "protocol");
345 if (type.empty() || seq.empty() || protover.empty()) {
346 g_warning("Received incomplete Inkboard message (missing type, protocol, or sequence number); ignoring message.");
347 return;
348 }
350 MessageType mtype = static_cast< MessageType >(atoi(type.c_str()));
352 // Messages that deal with the creation and destruction of sessions should be handled
353 // here in the SessionManager.
354 //
355 // These events are listed below, along with rationale.
356 //
357 // - CONNECT_REQUEST_USER: when we begin to process this message, we will not have an
358 // Inkboard session available to send the message to. Therefore, this message needs
359 // to be handled by the SessionManager.
360 //
361 // - CONNECT_REQUEST_REFUSED_BY_PEER: this message means that the recipient of a
362 // private invitation refused the invitation. In this case, we need to destroy the
363 // Inkboard desktop, document, and session associated with that invitation.
364 // Destruction of these components seems to be more naturally done in the SessionManager
365 // than in the Inkboard document itself (especially since the document may be associated
366 // with multiple desktops).
367 //
368 // - UNSUPPORTED_PROTOCOL_VERSION: this message means that the recipient of an invitation
369 // does not support the version of the Inkboard protocol we are using. In this case,
370 // we have to destroy the Inkboard desktop, document, and session associated with that
371 // invitation. The rationale for doing it in the SessionManager is the same as that
372 // given above.
373 //
374 // - ALREADY_IN_SESSION: similar rationale to above.
375 //
376 // - DISCONNECTED_FROM_USER_SIGNAL: similar rationale to above.
377 //
378 //
379 // All other events can be handled inside an Inkboard session.
381 // The message we are handling will have come from some Jabber ID. We need to verify
382 // that the Inkboard session associated with that JID is in the correct state for the
383 // incoming message (or, in some cases, that the session correctly exists / does not
384 // exist).
385 InkboardDocument* doc = getInkboardSession(event.getFrom());
387 // NOTE: This line refers to a class that hasn't been written yet
388 // MessageValidityTestResult res = MessageVerifier::verifyMessageValidity(event, mtype, doc);
390 MessageValidityTestResult res = RESULT_INVALID;
392 switch (res) {
393 case RESULT_VALID:
394 {
395 switch (mtype) {
396 case CONNECT_REQUEST_USER:
397 case CONNECT_REQUEST_REFUSED_BY_PEER:
398 case UNSUPPORTED_PROTOCOL_VERSION:
399 case ALREADY_IN_SESSION:
400 _handleSessionEvent(mtype, event);
401 break;
402 case DISCONNECTED_FROM_USER_SIGNAL:
403 break;
404 default:
405 if (doc != NULL) {
406 unsigned int seqnum = atoi(seq.c_str());
407 doc->processInkboardEvent(mtype, seqnum, event.getData());
408 }
409 break;
410 }
411 break;
412 }
413 case RESULT_INVALID:
414 default:
415 // FIXME: better warning message
416 g_warning("Received message in invalid context.");
417 break;
418 }
419 }
421 void
422 SessionManager::_handleSessionEvent(MessageType mtype, Pedro::XmppEvent const& event)
423 {
424 switch (mtype) {
425 case CONNECT_REQUEST_USER:
426 _handleIncomingInvitation(event.getFrom());
427 break;
428 case CONNECT_REQUEST_REFUSED_BY_PEER:
429 _handleInvitationResponse(event.getFrom(), DECLINE_INVITATION);
430 break;
431 case ALREADY_IN_SESSION:
432 _handleInvitationResponse(event.getFrom(), PEER_ALREADY_IN_SESSION);
433 break;
434 case UNSUPPORTED_PROTOCOL_VERSION:
435 _handleInvitationResponse(event.getFrom(), UNSUPPORTED_PROTOCOL);
436 break;
437 default:
438 break;
439 }
440 }
442 void
443 SessionManager::_handleIncomingInvitation(Glib::ustring const& from)
444 {
445 // don't insert duplicate invitations
446 if (std::find(_pending_invitations.begin(), _pending_invitations.end(), from) != _pending_invitations.end()) {
447 return;
448 }
450 // We need to do the invitation confirm/deny dialog elsewhere --
451 // when this method is called, we're still executing in Pedro's context,
452 // which causes issues when we run a dialog main loop.
453 //
454 // The invitation handling is done in a poller timeout that executes approximately
455 // every 50ms. It calls _checkInvitationQueue.
456 _pending_invitations.push_back(from);
458 }
460 void
461 SessionManager::_handleInvitationResponse(Glib::ustring const& from, InvitationResponses resp)
462 {
463 // only handle one response per invitation sender
464 //
465 // TODO: this could have one huge bug: say that Alice sends an invite to Bob, but
466 // Bob is doing something else at the moment and doesn't want to get in an Inkboard
467 // session. Eve intercepts Bob's "reject invitation" message and passes a
468 // "accept invitation" message to Alice that comes before Bob's "reject invitation"
469 // message.
470 //
471 // Does XMPP prevent this sort of attack? Need to investigate that.
472 if (std::find_if(_invitation_responses.begin(), _invitation_responses.end(), CheckInvitationSender(from)) != _invitation_responses.end()) {
473 return;
474 }
476 // We need to do the invitation confirm/deny dialog elsewhere --
477 // when this method is called, we're still executing in Pedro's context,
478 // which causes issues when we run a dialog main loop.
479 //
480 // The invitation handling is done in a poller timeout that executes approximately
481 // every 50ms. It calls _checkInvitationResponseQueue.
482 _invitation_responses.push_back(Invitation_response_type(from, resp));
484 }
486 } // namespace Whiteboard
488 } // namespace Inkscape
491 /*
492 Local Variables:
493 mode:c++
494 c-file-style:"stroustrup"
495 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
496 indent-tabs-mode:nil
497 fill-column:99
498 End:
499 */
500 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :