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