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