acb661cafd13d36afe0e2b5b0850cc3d003e44d4
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 (MessageType)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, SessionType type)
221 {
222 InkboardDocument* doc;
223 SPDesktop* dt;
225 switch (type)
226 {
227 // Just create a new blank canvas for MUC sessions
228 case INKBOARD_MUC:
230 dt = createInkboardDesktop(to, type);
232 if (dt != NULL)
233 {
234 doc = dynamic_cast< InkboardDocument* >(sp_desktop_document(dt)->rdoc);
236 if (doc != NULL)
237 {
238 doc->startSessionNegotiation();
239 }
240 }
241 break;
243 // Let the user pick the document which to start a peer ro peer session
244 // with, or a blank one, then create a blank document, copy over the contents
245 // and initialise session
246 case INKBOARD_PRIVATE:
247 default:
249 ChooseDesktop dialog;
250 int result = dialog.run();
252 if(result == Gtk::RESPONSE_OK)
253 {
254 SPDesktop *desktop = dialog.getDesktop();
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 if(desktop != NULL)
264 {
265 Inkscape::XML::Document *old_doc = sp_desktop_document(desktop)->rdoc;
266 doc->root()->mergeFrom(old_doc->root(),"id");
267 }
269 doc->startSessionNegotiation();
270 }
271 }
272 }
273 break;
274 }
275 }
277 /**
278 * Clone of sp_file_new and all related subroutines and functions,
279 * with appropriate modifications to use the Inkboard document class.
280 *
281 * \param to The JID to which this Inkboard document will be connected.
282 * \return A pointer to the created desktop, or NULL if a new desktop could not be created.
283 */
284 SPDesktop*
285 SessionManager::createInkboardDesktop(Glib::ustring const& to, SessionType type)
286 {
287 // Create document (sp_repr_document_new clone)
288 SPDocument* doc = makeInkboardDocument(g_quark_from_static_string("xml"), "svg:svg", type, to);
289 g_return_val_if_fail(doc != NULL, NULL);
291 InkboardDocument* inkdoc = dynamic_cast< InkboardDocument* >(doc->rdoc);
292 if (inkdoc == NULL) { // this shouldn't ever happen...
293 return NULL;
294 }
296 // Create desktop and attach document
297 SPDesktop *dt = makeInkboardDesktop(doc);
298 _inkboards.push_back(Inkboard_record_type(to, inkdoc));
299 return dt;
300 }
302 void
303 SessionManager::terminateInkboardSession(Glib::ustring const& to)
304 {
305 std::cout << "Terminating Inkboard session to " << to << std::endl;
306 Inkboards_type::iterator i = _inkboards.begin();
307 for(; i != _inkboards.end(); ++i) {
308 if ((*i).first == to) {
309 break;
310 }
311 }
313 if (i != _inkboards.end()) {
314 std::cout << "Erasing Inkboard session to " << to << std::endl;
315 (*i).second->terminateSession();
316 _inkboards.erase(i);
317 }
318 }
320 InkboardDocument*
321 SessionManager::getInkboardSession(Glib::ustring const& to)
322 {
323 Inkboards_type::iterator i = _inkboards.begin();
324 for(; i != _inkboards.end(); ++i) {
325 if ((*i).first == to) {
326 return (*i).second;
327 }
328 }
329 return NULL;
330 }
332 void
333 SessionManager::_processInkboardEvent(Pedro::XmppEvent const& event)
334 {
335 Pedro::Element* root = event.getDOM();
337 if (root == NULL) {
338 g_warning("Received null DOM; ignoring message.");
339 return;
340 }
342 Pedro::DOMString type = root->getTagAttribute("inkboard", "type");
343 Pedro::DOMString seq = root->getTagAttribute("inkboard", "seq");
344 Pedro::DOMString protover = root->getTagAttribute("inkboard", "protocol");
346 if (type.empty() || seq.empty() || protover.empty()) {
347 g_warning("Received incomplete Inkboard message (missing type, protocol, or sequence number); ignoring message.");
348 return;
349 }
351 MessageType mtype = static_cast< MessageType >(atoi(type.c_str()));
353 // Messages that deal with the creation and destruction of sessions should be handled
354 // here in the SessionManager.
355 //
356 // These events are listed below, along with rationale.
357 //
358 // - CONNECT_REQUEST_USER: when we begin to process this message, we will not have an
359 // Inkboard session available to send the message to. Therefore, this message needs
360 // to be handled by the SessionManager.
361 //
362 // - CONNECT_REQUEST_REFUSED_BY_PEER: this message means that the recipient of a
363 // private invitation refused the invitation. In this case, we need to destroy the
364 // Inkboard desktop, document, and session associated with that invitation.
365 // Destruction of these components seems to be more naturally done in the SessionManager
366 // than in the Inkboard document itself (especially since the document may be associated
367 // with multiple desktops).
368 //
369 // - UNSUPPORTED_PROTOCOL_VERSION: this message means that the recipient of an invitation
370 // does not support the version of the Inkboard protocol we are using. In this case,
371 // we have to destroy the Inkboard desktop, document, and session associated with that
372 // invitation. The rationale for doing it in the SessionManager is the same as that
373 // given above.
374 //
375 // - ALREADY_IN_SESSION: similar rationale to above.
376 //
377 // - DISCONNECTED_FROM_USER_SIGNAL: similar rationale to above.
378 //
379 //
380 // All other events can be handled inside an Inkboard session.
382 // The message we are handling will have come from some Jabber ID. We need to verify
383 // that the Inkboard session associated with that JID is in the correct state for the
384 // incoming message (or, in some cases, that the session correctly exists / does not
385 // exist).
386 InkboardDocument* doc = getInkboardSession(event.getFrom());
388 // NOTE: This line refers to a class that hasn't been written yet
389 // MessageValidityTestResult res = MessageVerifier::verifyMessageValidity(event, mtype, doc);
391 MessageValidityTestResult res = RESULT_INVALID;
392 /*
393 switch (res) {
394 case RESULT_VALID:
395 {
396 switch (mtype) {
397 case CONNECT_REQUEST:
398 default:
399 if (doc != NULL) {
400 unsigned int seqnum = atoi(seq.c_str());
401 doc->processInkboardEvent(mtype, seqnum, event.getData());
402 }
403 break;
404 }
405 break;
406 }
407 case RESULT_INVALID:
408 default:
409 // FIXME: better warning message
410 g_warning("Received message in invalid context.");
411 break;
412 }
413 */
414 }
416 void
417 SessionManager::_handleSessionEvent(Message::Wrapper mtype, Pedro::XmppEvent const& event)
418 {
419 /*
420 switch (mtype) {
421 case CONNECT_REQUEST:
422 _handleIncomingInvitation(event.getFrom());
423 break;
424 case INVITATION_DECLINE:
425 _handleInvitationResponse(event.getFrom(), DECLINE_INVITATION);
426 break;
427 default:
428 break;
429 }
430 */
431 }
433 void
434 SessionManager::_handleIncomingInvitation(Glib::ustring const& from)
435 {
436 // don't insert duplicate invitations
437 if (std::find(_pending_invitations.begin(), _pending_invitations.end(), from) != _pending_invitations.end()) {
438 return;
439 }
441 // We need to do the invitation confirm/deny dialog elsewhere --
442 // when this method is called, we're still executing in Pedro's context,
443 // which causes issues when we run a dialog main loop.
444 //
445 // The invitation handling is done in a poller timeout that executes approximately
446 // every 50ms. It calls _checkInvitationQueue.
447 _pending_invitations.push_back(from);
449 }
451 void
452 SessionManager::_handleInvitationResponse(Glib::ustring const& from, InvitationResponses resp)
453 {
454 // only handle one response per invitation sender
455 //
456 // TODO: this could have one huge bug: say that Alice sends an invite to Bob, but
457 // Bob is doing something else at the moment and doesn't want to get in an Inkboard
458 // session. Eve intercepts Bob's "reject invitation" message and passes a
459 // "accept invitation" message to Alice that comes before Bob's "reject invitation"
460 // message.
461 //
462 // Does XMPP prevent this sort of attack? Need to investigate that.
463 if (std::find_if(_invitation_responses.begin(), _invitation_responses.end(), CheckInvitationSender(from)) != _invitation_responses.end()) {
464 return;
465 }
467 // We need to do the invitation confirm/deny dialog elsewhere --
468 // when this method is called, we're still executing in Pedro's context,
469 // which causes issues when we run a dialog main loop.
470 //
471 // The invitation handling is done in a poller timeout that executes approximately
472 // every 50ms. It calls _checkInvitationResponseQueue.
473 _invitation_responses.push_back(Invitation_response_type(from, resp));
475 }
477 } // namespace Whiteboard
479 } // namespace Inkscape
482 /*
483 Local Variables:
484 mode:c++
485 c-file-style:"stroustrup"
486 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
487 indent-tabs-mode:nil
488 fill-column:99
489 End:
490 */
491 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :