Code

manually merging the INKBOARD_PEDRO branch into trunk
[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 "document.h"
23 #include "desktop.h"
24 #include "desktop-handles.h"
26 #include "jabber_whiteboard/message-verifier.h"
27 #include "jabber_whiteboard/session-manager.h"
28 #include "jabber_whiteboard/inkboard-document.h"
29 #include "jabber_whiteboard/new-inkboard-document.h"
31 #define INKBOARD_XMLNS "http://inkscape.org/inkboard"
33 namespace Inkscape {
35 namespace Whiteboard {
37 //#########################################################################
38 //# S E S S I O N    M A N A G E R
39 //#########################################################################
41 SessionManager *sessionManagerInstance = NULL;
43 void SessionManager::showClient()
44 {
45         SessionManager::instance().gui.show();
46 }
48 SessionManager&
49 SessionManager::instance()
50 {
51     if (!sessionManagerInstance)
52         sessionManagerInstance = new SessionManager();
53         return *sessionManagerInstance;
54 }
56 SessionManager::SessionManager() 
57 {
58     sequenceNumber = 0L;
59     getClient().addXmppEventListener(*this);
61         this->_check_pending_invitations = Glib::signal_timeout().connect(sigc::mem_fun(*this, &SessionManager::_checkInvitationQueue), 50);
62         this->_check_invitation_responses = Glib::signal_timeout().connect(sigc::mem_fun(*this, &SessionManager::_checkInvitationResponseQueue), 50);
63 }
65 SessionManager::~SessionManager()
66 {
67     getClient().removeXmppEventListener(*this);
68     getClient().disconnect();
69 }
71 unsigned long SessionManager::getSequenceNumber()
72 {
73     return sequenceNumber++;
74 }
76 bool
77 SessionManager::send(const Glib::ustring &destJid, 
78                                          const MessageType type,
79                      const Glib::ustring &data)
80 {
81     Pedro::DOMString xmlData = Pedro::Parser::encode(data);
82     char *fmt=
83     "<message type='chat' from='%s' to='%s' id='ink_%d'>"
84     "<inkboard xmlns='%s' "
85     "protocol='%d' type='%d' seq='%d'><x:inkboard-data>%s</x:inkboard-data></inkboard>"
86     "<body></body>"
87     "</message>";
88     if (!getClient().write(fmt, 
89                            getClient().getJid().c_str(),
90                            destJid.c_str(),
91                            getClient().getMsgId(),
92                            INKBOARD_XMLNS,
93                            2,
94                            (MessageType)type,
95                            getSequenceNumber(),
96                            xmlData.c_str()
97                            ))
98         {
99         return false;
100         }
101         
102     return true;
105 bool
106 SessionManager::sendGroup(const Glib::ustring &groupJid,
107                                                   const MessageType type,
108                           const Glib::ustring &data)
110     Pedro::DOMString xmlData = Pedro::Parser::encode(data);
111     char *fmt=
112     "<message type='groupchat' from='%s' to='%s' id='ink_%d'>"
113     "<inkboard xmlns='%s' "
114     "protocol='%d' type='%d' seq='%d'><x:inkboard-data>%s</x:inkboard-data></inkboard>"
115     "<body></body>"
116     "</message>";
117     if (!getClient().write(fmt,
118                            getClient().getJid().c_str(),
119                            groupJid.c_str(),
120                            getClient().getMsgId(),
121                            INKBOARD_XMLNS,
122                            2,
123                            type,
124                            getSequenceNumber(),
125                            xmlData.c_str()
126                            ))
127         {
128         return false;
129         }
130         
131     return true;
134 void
135 SessionManager::processXmppEvent(const Pedro::XmppEvent &event)
137     int type = event.getType();
139     switch (type) {
140         case Pedro::XmppEvent::EVENT_STATUS:
141             {
142             break;
143             }
144         case Pedro::XmppEvent::EVENT_ERROR:
145             {
146             break;
147             }
148         case Pedro::XmppEvent::EVENT_CONNECTED:
149             {
150             break;
151             }
152         case Pedro::XmppEvent::EVENT_DISCONNECTED:
153             {
154             break;
155             }
156         case Pedro::XmppEvent::EVENT_MESSAGE:
157             {
158             printf("## SM message:%s\n", event.getFrom().c_str());
159             Pedro::Element *root = event.getDOM();
161             if (root)
162                 {
163                 if (root->getTagAttribute("inkboard", "xmlns") ==
164                                INKBOARD_XMLNS)
165                     {
166                         _processInkboardEvent(event);
167                     }
168                 }
169             break;
170             }
171         case Pedro::XmppEvent::EVENT_PRESENCE:
172             {
173             break;
174             }
175         case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
176             {
177             printf("## SM MUC message:%s\n", event.getFrom().c_str());
178             Pedro::Element *root = event.getDOM();
179             if (root)
180                 {
181                 if (root->getTagAttribute("inkboard", "xmlns") ==
182                                INKBOARD_XMLNS)
183                     {
184                         _processInkboardEvent(event);
185                     }
186                 }
187             break;
188             }
189         case Pedro::XmppEvent::EVENT_MUC_JOIN:
190             {
191             break;
192             }
193         case Pedro::XmppEvent::EVENT_MUC_LEAVE:
194             {
195             break;
196             }
197         case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
198             {
199             break;
200             }
201         default:
202             {
203             break;
204             }
205     }
208 /**
209  * Initiates a shared session with a user or conference room.
210  * 
211  * \param to The recipient to which this desktop will be linked, specified as a JID.
212  * \param type Type of the session; i.e. private message or group chat.
213  */
214 void
215 SessionManager::doShare(Glib::ustring const& to, SessionType type)
217     SPDesktop* dt = createInkboardDesktop(to, type);
218         if (dt != NULL) {
219                 InkboardDocument* doc = dynamic_cast< InkboardDocument* >(sp_desktop_document(dt)->rdoc);
220                 if (doc != NULL) {
221                         doc->startSessionNegotiation();
222                 }
223         }
226 /**
227  * Clone of sp_file_new and all related subroutines and functions,
228  * with appropriate modifications to use the Inkboard document class.
229  *
230  * \param to The JID to which this Inkboard document will be connected.
231  * \return A pointer to the created desktop, or NULL if a new desktop could not be created.
232  */
233 SPDesktop*
234 SessionManager::createInkboardDesktop(Glib::ustring const& to, SessionType type)
236 // Create document (sp_repr_document_new clone)
237     SPDocument* doc = makeInkboardDocument(g_quark_from_static_string("xml"), "svg:svg", type, to);
238     g_return_val_if_fail(doc != NULL, NULL);
240     InkboardDocument* inkdoc = dynamic_cast< InkboardDocument* >(doc->rdoc);
241     if (inkdoc == NULL) { // this shouldn't ever happen...
242         return NULL;
243     }
245 // Create desktop and attach document
246     SPDesktop *dt = makeInkboardDesktop(doc);
247     _inkboards.push_back(Inkboard_record_type(to, inkdoc));
248     return dt;
251 void
252 SessionManager::terminateInkboardSession(Glib::ustring const& to)
254         std::cout << "Terminating Inkboard session to " << to << std::endl;
255     Inkboards_type::iterator i = _inkboards.begin();
256     for(; i != _inkboards.end(); ++i) {
257         if ((*i).first == to) {
258             break;
259         }
260     }
262     if (i != _inkboards.end()) {
263                 std::cout << "Erasing Inkboard session to " << to << std::endl;
264         (*i).second->terminateSession();
265         _inkboards.erase(i);
266     }
269 InkboardDocument*
270 SessionManager::getInkboardSession(Glib::ustring const& to)
272     Inkboards_type::iterator i = _inkboards.begin();
273     for(; i != _inkboards.end(); ++i) {
274         if ((*i).first == to) {
275             return (*i).second;
276         }
277     }
278     return NULL;
281 void
282 SessionManager::_processInkboardEvent(Pedro::XmppEvent const& event)
284     Pedro::Element* root = event.getDOM();
286         if (root == NULL) {
287                 g_warning("Received null DOM; ignoring message.");
288                 return;
289         }
291     Pedro::DOMString type = root->getTagAttribute("inkboard", "type");
292     Pedro::DOMString seq = root->getTagAttribute("inkboard", "seq");
293     Pedro::DOMString protover = root->getTagAttribute("inkboard", "protocol");
295     if (type.empty() || seq.empty() || protover.empty()) {
296         g_warning("Received incomplete Inkboard message (missing type, protocol, or sequence number); ignoring message.");
297         return;
298     }
300     MessageType mtype = static_cast< MessageType >(atoi(type.c_str()));
302         // Messages that deal with the creation and destruction of sessions should be handled
303         // here in the SessionManager.  
304         //
305         // These events are listed below, along with rationale.
306         //
307         // - CONNECT_REQUEST_USER: when we begin to process this message, we will not have an 
308         //   Inkboard session available to send the message to.  Therefore, this message needs
309         //   to be handled by the SessionManager.
310         // 
311         // - CONNECT_REQUEST_REFUSED_BY_PEER: this message means that the recipient of a 
312         //   private invitation refused the invitation.  In this case, we need to destroy the
313         //   Inkboard desktop, document, and session associated with that invitation.
314         //   Destruction of these components seems to be more naturally done in the SessionManager
315         //   than in the Inkboard document itself (especially since the document may be associated
316         //   with multiple desktops).
317         //
318         // - UNSUPPORTED_PROTOCOL_VERSION: this message means that the recipient of an invitation
319         //   does not support the version of the Inkboard protocol we are using.  In this case,
320         //   we have to destroy the Inkboard desktop, document, and session associated with that
321         //   invitation.  The rationale for doing it in the SessionManager is the same as that
322         //   given above.
323         //
324         // - ALREADY_IN_SESSION: similar rationale to above.
325         //
326         // - DISCONNECTED_FROM_USER_SIGNAL: similar rationale to above.
327         //
328         //
329         // All other events can be handled inside an Inkboard session.
330         
331         // The message we are handling will have come from some Jabber ID.  We need to verify
332         // that the Inkboard session associated with that JID is in the correct state for the
333         // incoming message (or, in some cases, that the session correctly exists / does not
334         // exist).
335         InkboardDocument* doc = getInkboardSession(event.getFrom());
337 //      NOTE: This line refers to a class that hasn't been written yet
338 //      MessageValidityTestResult res = MessageVerifier::verifyMessageValidity(event, mtype, doc);
340         MessageValidityTestResult res = RESULT_INVALID;
342         switch (res) {
343                 case RESULT_VALID:
344                 {
345                         switch (mtype) {
346                                 case CONNECT_REQUEST_USER:
347                                 case CONNECT_REQUEST_REFUSED_BY_PEER:
348                                 case UNSUPPORTED_PROTOCOL_VERSION:
349                                 case ALREADY_IN_SESSION:
350                                         _handleSessionEvent(mtype, event);
351                                         break;
352                                 case DISCONNECTED_FROM_USER_SIGNAL:
353                                         break;
354                                 default:
355                                         if (doc != NULL) {
356                                                 unsigned int seqnum = atoi(seq.c_str());
357                                                 doc->processInkboardEvent(mtype, seqnum, event.getData());
358                                         }
359                                         break;
360                         }
361                         break;
362                 }
363                 case RESULT_INVALID:
364                 default:
365                         // FIXME: better warning message
366                         g_warning("Received message in invalid context.");
367                         break;
368         }
371 void
372 SessionManager::_handleSessionEvent(MessageType mtype, Pedro::XmppEvent const& event)
374         switch (mtype) {
375                 case CONNECT_REQUEST_USER:
376                         _handleIncomingInvitation(event.getFrom());
377                         break;
378                 case CONNECT_REQUEST_REFUSED_BY_PEER:
379                         _handleInvitationResponse(event.getFrom(), DECLINE_INVITATION);
380                         break;
381                 case ALREADY_IN_SESSION:
382                         _handleInvitationResponse(event.getFrom(), PEER_ALREADY_IN_SESSION);
383                         break;
384                 case UNSUPPORTED_PROTOCOL_VERSION:
385                         _handleInvitationResponse(event.getFrom(), UNSUPPORTED_PROTOCOL);
386                         break;
387                 default:
388                         break;
389         }
392 void
393 SessionManager::_handleIncomingInvitation(Glib::ustring const& from)
395         // don't insert duplicate invitations
396         if (std::find(_pending_invitations.begin(), _pending_invitations.end(), from) != _pending_invitations.end()) {
397                 return;
398         }
400         // We need to do the invitation confirm/deny dialog elsewhere --
401         // when this method is called, we're still executing in Pedro's context,
402         // which causes issues when we run a dialog main loop.
403         //
404         // The invitation handling is done in a poller timeout that executes approximately
405         // every 50ms.  It calls _checkInvitationQueue.
406         _pending_invitations.push_back(from);
410 void
411 SessionManager::_handleInvitationResponse(Glib::ustring const& from, InvitationResponses resp)
413         // only handle one response per invitation sender
414         //
415         // TODO: this could have one huge bug: say that Alice sends an invite to Bob, but
416         // Bob is doing something else at the moment and doesn't want to get in an Inkboard
417         // session.  Eve intercepts Bob's "reject invitation" message and passes a
418         // "accept invitation" message to Alice that comes before Bob's "reject invitation"
419         // message. 
420         //
421         // Does XMPP prevent this sort of attack?  Need to investigate that.
422         if (std::find_if(_invitation_responses.begin(), _invitation_responses.end(), CheckInvitationSender(from)) != _invitation_responses.end()) {
423                 return;
424         }
426         // We need to do the invitation confirm/deny dialog elsewhere --
427         // when this method is called, we're still executing in Pedro's context,
428         // which causes issues when we run a dialog main loop.
429         //
430         // The invitation handling is done in a poller timeout that executes approximately
431         // every 50ms.  It calls _checkInvitationResponseQueue.
432         _invitation_responses.push_back(Invitation_response_type(from, resp));
436 }  // namespace Whiteboard
437  
438 }  // namespace Inkscape
441 /*
442   Local Variables:
443   mode:c++
444   c-file-style:"stroustrup"
445   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
446   indent-tabs-mode:nil
447   fill-column:99
448   End:
449 */
450 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :