Code

acb661cafd13d36afe0e2b5b0850cc3d003e44d4
[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 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         }
106         
107     return true;
110 bool
111 SessionManager::sendGroup(const Glib::ustring &groupJid,
112                           const Message::Wrapper type,
113                           const Glib::ustring &data)
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         }
135         
136     return true;
139 void
140 SessionManager::processXmppEvent(const Pedro::XmppEvent &event)
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     }
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)
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     }
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)
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;
302 void
303 SessionManager::terminateInkboardSession(Glib::ustring const& to)
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     }
320 InkboardDocument*
321 SessionManager::getInkboardSession(Glib::ustring const& to)
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;
332 void
333 SessionManager::_processInkboardEvent(Pedro::XmppEvent const& event)
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.
381         
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         */
416 void
417 SessionManager::_handleSessionEvent(Message::Wrapper mtype, Pedro::XmppEvent const& event)
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         */
433 void
434 SessionManager::_handleIncomingInvitation(Glib::ustring const& from)
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);
451 void
452 SessionManager::_handleInvitationResponse(Glib::ustring const& from, InvitationResponses resp)
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));
477 }  // namespace Whiteboard
478  
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 :