Code

added sendProtocol
[inkscape.git] / src / jabber_whiteboard / session-manager.cpp
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         }
105         
106     return true;
109 bool
110 SessionManager::sendProtocol(const Glib::ustring &destJid, 
111                                          const MessageType type,
112                      const Glib::ustring &data)
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         }
134         
135     return true;
138 bool
139 SessionManager::sendGroup(const Glib::ustring &groupJid,
140                                                   const MessageType type,
141                           const Glib::ustring &data)
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         }
163         
164     return true;
167 void
168 SessionManager::processXmppEvent(const Pedro::XmppEvent &event)
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     }
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)
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     }
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)
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;
330 void
331 SessionManager::terminateInkboardSession(Glib::ustring const& to)
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     }
348 InkboardDocument*
349 SessionManager::getInkboardSession(Glib::ustring const& to)
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;
360 void
361 SessionManager::_processInkboardEvent(Pedro::XmppEvent const& event)
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.
409         
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         }
450 void
451 SessionManager::_handleSessionEvent(MessageType mtype, Pedro::XmppEvent const& event)
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         }
471 void
472 SessionManager::_handleIncomingInvitation(Glib::ustring const& from)
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);
489 void
490 SessionManager::_handleInvitationResponse(Glib::ustring const& from, InvitationResponses resp)
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));
515 }  // namespace Whiteboard
516  
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 :