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::sendProtocol(const Glib::ustring &destJid,
111 const MessageType type,
112 const Glib::ustring &data)
113 {
114 Pedro::DOMString xmlData = Pedro::Parser::encode(data);
115 char *fmt=
116 "<message type='chat' from='%s' to='%s' id='ink_%d'>"
117 "<wb 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 destJid.c_str(),
124 getClient().getMsgId(),
125 INKBOARD_XMLNS,
126 2,
127 (MessageType)type,
128 getSequenceNumber(),
129 xmlData.c_str()
130 ))
131 {
132 return false;
133 }
135 return true;
136 }
138 bool
139 SessionManager::sendGroup(const Glib::ustring &groupJid,
140 const MessageType type,
141 const Glib::ustring &data)
142 {
143 Pedro::DOMString xmlData = Pedro::Parser::encode(data);
144 char *fmt=
145 "<message type='groupchat' from='%s' to='%s' id='ink_%d'>"
146 "<inkboard xmlns='%s' "
147 "protocol='%d' type='%d' seq='%d'><x:inkboard-data>%s</x:inkboard-data></inkboard>"
148 "<body></body>"
149 "</message>";
150 if (!getClient().write(fmt,
151 getClient().getJid().c_str(),
152 groupJid.c_str(),
153 getClient().getMsgId(),
154 INKBOARD_XMLNS,
155 2,
156 type,
157 getSequenceNumber(),
158 xmlData.c_str()
159 ))
160 {
161 return false;
162 }
164 return true;
165 }
167 void
168 SessionManager::processXmppEvent(const Pedro::XmppEvent &event)
169 {
170 int type = event.getType();
172 switch (type) {
173 case Pedro::XmppEvent::EVENT_STATUS:
174 {
175 break;
176 }
177 case Pedro::XmppEvent::EVENT_ERROR:
178 {
179 break;
180 }
181 case Pedro::XmppEvent::EVENT_CONNECTED:
182 {
183 break;
184 }
185 case Pedro::XmppEvent::EVENT_DISCONNECTED:
186 {
187 break;
188 }
189 case Pedro::XmppEvent::EVENT_MESSAGE:
190 {
191 printf("## SM message:%s\n", event.getFrom().c_str());
192 Pedro::Element *root = event.getDOM();
194 if (root)
195 {
196 if (root->getTagAttribute("inkboard", "xmlns") ==
197 INKBOARD_XMLNS)
198 {
199 _processInkboardEvent(event);
200 }
201 }
202 break;
203 }
204 case Pedro::XmppEvent::EVENT_PRESENCE:
205 {
206 break;
207 }
208 case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
209 {
210 printf("## SM MUC message:%s\n", event.getFrom().c_str());
211 Pedro::Element *root = event.getDOM();
212 if (root)
213 {
214 if (root->getTagAttribute("inkboard", "xmlns") ==
215 INKBOARD_XMLNS)
216 {
217 _processInkboardEvent(event);
218 }
219 }
220 break;
221 }
222 case Pedro::XmppEvent::EVENT_MUC_JOIN:
223 {
224 break;
225 }
226 case Pedro::XmppEvent::EVENT_MUC_LEAVE:
227 {
228 break;
229 }
230 case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
231 {
232 break;
233 }
234 default:
235 {
236 break;
237 }
238 }
239 }
241 /**
242 * Initiates a shared session with a user or conference room.
243 *
244 * \param to The recipient to which this desktop will be linked, specified as a JID.
245 * \param type Type of the session; i.e. private message or group chat.
246 */
247 void
248 SessionManager::doShare(Glib::ustring const& to, SessionType type)
249 {
250 InkboardDocument* doc;
251 SPDesktop* dt;
253 switch (type)
254 {
255 // Just create a new blank canvas for MUC sessions
256 case INKBOARD_MUC:
258 dt = createInkboardDesktop(to, type);
260 if (dt != NULL)
261 {
262 doc = dynamic_cast< InkboardDocument* >(sp_desktop_document(dt)->rdoc);
264 if (doc != NULL)
265 {
266 doc->startSessionNegotiation();
267 }
268 }
269 break;
271 // Let the user pick the document which to start a peer ro peer session
272 // with, or a blank one, then create a blank document, copy over the contents
273 // and initialise session
274 case INKBOARD_PRIVATE:
275 default:
277 ChooseDesktop dialog;
278 int result = dialog.run();
280 if(result == Gtk::RESPONSE_OK)
281 {
282 SPDesktop *desktop = dialog.getDesktop();
283 dt = createInkboardDesktop(to, type);
285 if (dt != NULL)
286 {
287 doc = dynamic_cast< InkboardDocument* >(sp_desktop_document(dt)->rdoc);
289 if (doc != NULL)
290 {
291 if(desktop != NULL)
292 {
293 Inkscape::XML::Document *old_doc = sp_desktop_document(desktop)->rdoc;
294 doc->root()->mergeFrom(old_doc->root(),"id");
295 }
297 doc->startSessionNegotiation();
298 }
299 }
300 }
301 break;
302 }
303 }
305 /**
306 * Clone of sp_file_new and all related subroutines and functions,
307 * with appropriate modifications to use the Inkboard document class.
308 *
309 * \param to The JID to which this Inkboard document will be connected.
310 * \return A pointer to the created desktop, or NULL if a new desktop could not be created.
311 */
312 SPDesktop*
313 SessionManager::createInkboardDesktop(Glib::ustring const& to, SessionType type)
314 {
315 // Create document (sp_repr_document_new clone)
316 SPDocument* doc = makeInkboardDocument(g_quark_from_static_string("xml"), "svg:svg", type, to);
317 g_return_val_if_fail(doc != NULL, NULL);
319 InkboardDocument* inkdoc = dynamic_cast< InkboardDocument* >(doc->rdoc);
320 if (inkdoc == NULL) { // this shouldn't ever happen...
321 return NULL;
322 }
324 // Create desktop and attach document
325 SPDesktop *dt = makeInkboardDesktop(doc);
326 _inkboards.push_back(Inkboard_record_type(to, inkdoc));
327 return dt;
328 }
330 void
331 SessionManager::terminateInkboardSession(Glib::ustring const& to)
332 {
333 std::cout << "Terminating Inkboard session to " << to << std::endl;
334 Inkboards_type::iterator i = _inkboards.begin();
335 for(; i != _inkboards.end(); ++i) {
336 if ((*i).first == to) {
337 break;
338 }
339 }
341 if (i != _inkboards.end()) {
342 std::cout << "Erasing Inkboard session to " << to << std::endl;
343 (*i).second->terminateSession();
344 _inkboards.erase(i);
345 }
346 }
348 InkboardDocument*
349 SessionManager::getInkboardSession(Glib::ustring const& to)
350 {
351 Inkboards_type::iterator i = _inkboards.begin();
352 for(; i != _inkboards.end(); ++i) {
353 if ((*i).first == to) {
354 return (*i).second;
355 }
356 }
357 return NULL;
358 }
360 void
361 SessionManager::_processInkboardEvent(Pedro::XmppEvent const& event)
362 {
363 Pedro::Element* root = event.getDOM();
365 if (root == NULL) {
366 g_warning("Received null DOM; ignoring message.");
367 return;
368 }
370 Pedro::DOMString type = root->getTagAttribute("inkboard", "type");
371 Pedro::DOMString seq = root->getTagAttribute("inkboard", "seq");
372 Pedro::DOMString protover = root->getTagAttribute("inkboard", "protocol");
374 if (type.empty() || seq.empty() || protover.empty()) {
375 g_warning("Received incomplete Inkboard message (missing type, protocol, or sequence number); ignoring message.");
376 return;
377 }
379 MessageType mtype = static_cast< MessageType >(atoi(type.c_str()));
381 // Messages that deal with the creation and destruction of sessions should be handled
382 // here in the SessionManager.
383 //
384 // These events are listed below, along with rationale.
385 //
386 // - CONNECT_REQUEST_USER: when we begin to process this message, we will not have an
387 // Inkboard session available to send the message to. Therefore, this message needs
388 // to be handled by the SessionManager.
389 //
390 // - CONNECT_REQUEST_REFUSED_BY_PEER: this message means that the recipient of a
391 // private invitation refused the invitation. In this case, we need to destroy the
392 // Inkboard desktop, document, and session associated with that invitation.
393 // Destruction of these components seems to be more naturally done in the SessionManager
394 // than in the Inkboard document itself (especially since the document may be associated
395 // with multiple desktops).
396 //
397 // - UNSUPPORTED_PROTOCOL_VERSION: this message means that the recipient of an invitation
398 // does not support the version of the Inkboard protocol we are using. In this case,
399 // we have to destroy the Inkboard desktop, document, and session associated with that
400 // invitation. The rationale for doing it in the SessionManager is the same as that
401 // given above.
402 //
403 // - ALREADY_IN_SESSION: similar rationale to above.
404 //
405 // - DISCONNECTED_FROM_USER_SIGNAL: similar rationale to above.
406 //
407 //
408 // All other events can be handled inside an Inkboard session.
410 // The message we are handling will have come from some Jabber ID. We need to verify
411 // that the Inkboard session associated with that JID is in the correct state for the
412 // incoming message (or, in some cases, that the session correctly exists / does not
413 // exist).
414 InkboardDocument* doc = getInkboardSession(event.getFrom());
416 // NOTE: This line refers to a class that hasn't been written yet
417 // MessageValidityTestResult res = MessageVerifier::verifyMessageValidity(event, mtype, doc);
419 MessageValidityTestResult res = RESULT_INVALID;
421 switch (res) {
422 case RESULT_VALID:
423 {
424 switch (mtype) {
425 case CONNECT_REQUEST_USER:
426 case CONNECT_REQUEST_REFUSED_BY_PEER:
427 case UNSUPPORTED_PROTOCOL_VERSION:
428 case ALREADY_IN_SESSION:
429 _handleSessionEvent(mtype, event);
430 break;
431 case DISCONNECTED_FROM_USER_SIGNAL:
432 break;
433 default:
434 if (doc != NULL) {
435 unsigned int seqnum = atoi(seq.c_str());
436 doc->processInkboardEvent(mtype, seqnum, event.getData());
437 }
438 break;
439 }
440 break;
441 }
442 case RESULT_INVALID:
443 default:
444 // FIXME: better warning message
445 g_warning("Received message in invalid context.");
446 break;
447 }
448 }
450 void
451 SessionManager::_handleSessionEvent(MessageType mtype, Pedro::XmppEvent const& event)
452 {
453 switch (mtype) {
454 case CONNECT_REQUEST_USER:
455 _handleIncomingInvitation(event.getFrom());
456 break;
457 case CONNECT_REQUEST_REFUSED_BY_PEER:
458 _handleInvitationResponse(event.getFrom(), DECLINE_INVITATION);
459 break;
460 case ALREADY_IN_SESSION:
461 _handleInvitationResponse(event.getFrom(), PEER_ALREADY_IN_SESSION);
462 break;
463 case UNSUPPORTED_PROTOCOL_VERSION:
464 _handleInvitationResponse(event.getFrom(), UNSUPPORTED_PROTOCOL);
465 break;
466 default:
467 break;
468 }
469 }
471 void
472 SessionManager::_handleIncomingInvitation(Glib::ustring const& from)
473 {
474 // don't insert duplicate invitations
475 if (std::find(_pending_invitations.begin(), _pending_invitations.end(), from) != _pending_invitations.end()) {
476 return;
477 }
479 // We need to do the invitation confirm/deny dialog elsewhere --
480 // when this method is called, we're still executing in Pedro's context,
481 // which causes issues when we run a dialog main loop.
482 //
483 // The invitation handling is done in a poller timeout that executes approximately
484 // every 50ms. It calls _checkInvitationQueue.
485 _pending_invitations.push_back(from);
487 }
489 void
490 SessionManager::_handleInvitationResponse(Glib::ustring const& from, InvitationResponses resp)
491 {
492 // only handle one response per invitation sender
493 //
494 // TODO: this could have one huge bug: say that Alice sends an invite to Bob, but
495 // Bob is doing something else at the moment and doesn't want to get in an Inkboard
496 // session. Eve intercepts Bob's "reject invitation" message and passes a
497 // "accept invitation" message to Alice that comes before Bob's "reject invitation"
498 // message.
499 //
500 // Does XMPP prevent this sort of attack? Need to investigate that.
501 if (std::find_if(_invitation_responses.begin(), _invitation_responses.end(), CheckInvitationSender(from)) != _invitation_responses.end()) {
502 return;
503 }
505 // We need to do the invitation confirm/deny dialog elsewhere --
506 // when this method is called, we're still executing in Pedro's context,
507 // which causes issues when we run a dialog main loop.
508 //
509 // The invitation handling is done in a poller timeout that executes approximately
510 // every 50ms. It calls _checkInvitationResponseQueue.
511 _invitation_responses.push_back(Invitation_response_type(from, resp));
513 }
515 } // namespace Whiteboard
517 } // namespace Inkscape
520 /*
521 Local Variables:
522 mode:c++
523 c-file-style:"stroustrup"
524 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
525 indent-tabs-mode:nil
526 fill-column:99
527 End:
528 */
529 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :