Code

3c982c4df80676738504691dea423d8c931fccc5
[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"
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         }
106         
107     return true;
110 bool
111 SessionManager::sendProtocol(const Glib::ustring &destJid, 
112                                          const MessageType type)
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;
135 bool
136 SessionManager::sendGroup(const Glib::ustring &groupJid,
137                                                   const MessageType type,
138                           const Glib::ustring &data)
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         }
160         
161     return true;
164 void
165 SessionManager::processXmppEvent(const Pedro::XmppEvent &event)
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     }
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)
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     }
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)
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;
327 void
328 SessionManager::terminateInkboardSession(Glib::ustring const& to)
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     }
345 InkboardDocument*
346 SessionManager::getInkboardSession(Glib::ustring const& to)
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;
357 void
358 SessionManager::_processInkboardEvent(Pedro::XmppEvent const& event)
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.
406         
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         }
447 void
448 SessionManager::_handleSessionEvent(MessageType mtype, Pedro::XmppEvent const& event)
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         }
468 void
469 SessionManager::_handleIncomingInvitation(Glib::ustring const& from)
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);
486 void
487 SessionManager::_handleInvitationResponse(Glib::ustring const& from, InvitationResponses resp)
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));
512 }  // namespace Whiteboard
513  
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 :