Code

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