Code

more session establishment
[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                            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, State::SessionType type)
222     InkboardDocument* doc;
223     SPDesktop* dt;
225     // Just create a new blank canvas for MUC sessions
226     if(type == State::WHITEBOARD_MUC) 
227     {
228         dt = createInkboardDesktop(to, type);
230         if (dt != NULL) 
231         {
232             doc = dynamic_cast< InkboardDocument* >(sp_desktop_document(dt)->rdoc);
234             if (doc != NULL) 
235             {
236                 doc->startSessionNegotiation();
237             }
238         }
239            
241         // Let the user pick the document which to start a peer ro peer session
242         // with, or a blank one, then create a blank document, copy over the contents
243         // and initialise session
244     } else if (type== State::WHITEBOARD_PEER) {
246         ChooseDesktop dialog;
247         int result = dialog.run();
249         if(result == Gtk::RESPONSE_OK)
250         {
251             SPDesktop *desktop = dialog.getDesktop();
252             dt = createInkboardDesktop(to, type);
254             if (dt != NULL) 
255             {
256                 doc = dynamic_cast< InkboardDocument* >(sp_desktop_document(dt)->rdoc);
258                 if (doc != NULL) 
259                 {
260                     if(desktop != NULL)
261                     {
262                         Inkscape::XML::Document *old_doc =
263                             sp_desktop_document(desktop)->rdoc;
264                         doc->root()->mergeFrom(old_doc->root(),"id");
265                     }
267                     doc->startSessionNegotiation();
268                 }
269             }
270         }
271     }
274 /**
275  * Clone of sp_file_new and all related subroutines and functions,
276  * with appropriate modifications to use the Inkboard document class.
277  *
278  * \param to The JID to which this Inkboard document will be connected.
279  * \return A pointer to the created desktop, or NULL if a new desktop could not be created.
280  */
281 SPDesktop*
282 SessionManager::createInkboardDesktop(Glib::ustring const& to, State::SessionType type)
284 // Create document (sp_repr_document_new clone)
285     SPDocument* doc = makeInkboardDocument(g_quark_from_static_string("xml"), "svg:svg", type, to);
286     g_return_val_if_fail(doc != NULL, NULL);
288     InkboardDocument* inkdoc = dynamic_cast< InkboardDocument* >(doc->rdoc);
289     if (inkdoc == NULL) { // this shouldn't ever happen...
290         return NULL;
291     }
293 // Create desktop and attach document
294     SPDesktop *dt = makeInkboardDesktop(doc);
295     _inkboards.push_back(Inkboard_record_type(to, inkdoc));
296     return dt;
299 void
300 SessionManager::terminateInkboardSession(Glib::ustring const& to)
302         std::cout << "Terminating Inkboard session to " << to << std::endl;
303     Inkboards_type::iterator i = _inkboards.begin();
304     for(; i != _inkboards.end(); ++i) {
305         if ((*i).first == to) {
306             break;
307         }
308     }
310     if (i != _inkboards.end()) {
311                 std::cout << "Erasing Inkboard session to " << to << std::endl;
312         (*i).second->terminateSession();
313         _inkboards.erase(i);
314     }
317 InkboardDocument*
318 SessionManager::getInkboardSession(Glib::ustring const& to)
320     Inkboards_type::iterator i = _inkboards.begin();
321     for(; i != _inkboards.end(); ++i) {
322         if ((*i).first == to) {
323             return (*i).second;
324         }
325     }
326     return NULL;
329 void
330 SessionManager::_processInkboardEvent(Pedro::XmppEvent const& event)
332     Pedro::Element* root = event.getDOM();
334         if (root == NULL) {
335                 g_warning("Received null DOM; ignoring message.");
336                 return;
337         }
339     Pedro::DOMString type = root->getTagAttribute("inkboard", "type");
340     Pedro::DOMString seq = root->getTagAttribute("inkboard", "seq");
341     Pedro::DOMString protover = root->getTagAttribute("inkboard", "protocol");
343     if (type.empty() || seq.empty() || protover.empty()) {
344         g_warning("Received incomplete Inkboard message (missing type, protocol, or sequence number); ignoring message.");
345         return;
346     }
348     MessageType mtype = static_cast< MessageType >(atoi(type.c_str()));
350         // Messages that deal with the creation and destruction of sessions should be handled
351         // here in the SessionManager.  
352         //
353         // These events are listed below, along with rationale.
354         //
355         // - CONNECT_REQUEST_USER: when we begin to process this message, we will not have an 
356         //   Inkboard session available to send the message to.  Therefore, this message needs
357         //   to be handled by the SessionManager.
358         // 
359         // - CONNECT_REQUEST_REFUSED_BY_PEER: this message means that the recipient of a 
360         //   private invitation refused the invitation.  In this case, we need to destroy the
361         //   Inkboard desktop, document, and session associated with that invitation.
362         //   Destruction of these components seems to be more naturally done in the SessionManager
363         //   than in the Inkboard document itself (especially since the document may be associated
364         //   with multiple desktops).
365         //
366         // - UNSUPPORTED_PROTOCOL_VERSION: this message means that the recipient of an invitation
367         //   does not support the version of the Inkboard protocol we are using.  In this case,
368         //   we have to destroy the Inkboard desktop, document, and session associated with that
369         //   invitation.  The rationale for doing it in the SessionManager is the same as that
370         //   given above.
371         //
372         // - ALREADY_IN_SESSION: similar rationale to above.
373         //
374         // - DISCONNECTED_FROM_USER_SIGNAL: similar rationale to above.
375         //
376         //
377         // All other events can be handled inside an Inkboard session.
378         
379         // The message we are handling will have come from some Jabber ID.  We need to verify
380         // that the Inkboard session associated with that JID is in the correct state for the
381         // incoming message (or, in some cases, that the session correctly exists / does not
382         // exist).
383         InkboardDocument* doc = getInkboardSession(event.getFrom());
385 //      NOTE: This line refers to a class that hasn't been written yet
386 //      MessageValidityTestResult res = MessageVerifier::verifyMessageValidity(event, mtype, doc);
388         MessageValidityTestResult res = RESULT_INVALID;
389         /*
390         switch (res) {
391                 case RESULT_VALID:
392                 {
393                         switch (mtype) {
394                                 case CONNECT_REQUEST:
395                                 default:
396                                         if (doc != NULL) {
397                                                 unsigned int seqnum = atoi(seq.c_str());
398                                                 doc->processInkboardEvent(mtype, seqnum, event.getData());
399                                         }
400                                         break;
401                         }
402                         break;
403                 }
404                 case RESULT_INVALID:
405                 default:
406                         // FIXME: better warning message
407                         g_warning("Received message in invalid context.");
408                         break;
409         }
410         */
413 void
414 SessionManager::_handleSessionEvent(Message::Wrapper mtype, Pedro::XmppEvent const& event)
416         /*
417         switch (mtype) {
418                 case CONNECT_REQUEST:
419                         _handleIncomingInvitation(event.getFrom());
420                         break;
421                 case INVITATION_DECLINE:
422                         _handleInvitationResponse(event.getFrom(), DECLINE_INVITATION);
423                         break;
424                 default:
425                         break;
426         }
427         */
430 void
431 SessionManager::_handleIncomingInvitation(Glib::ustring const& from)
433         // don't insert duplicate invitations
434         if (std::find(_pending_invitations.begin(), _pending_invitations.end(), from) != _pending_invitations.end()) {
435                 return;
436         }
438         // We need to do the invitation confirm/deny dialog elsewhere --
439         // when this method is called, we're still executing in Pedro's context,
440         // which causes issues when we run a dialog main loop.
441         //
442         // The invitation handling is done in a poller timeout that executes approximately
443         // every 50ms.  It calls _checkInvitationQueue.
444         _pending_invitations.push_back(from);
448 void
449 SessionManager::_handleInvitationResponse(Glib::ustring const& from, InvitationResponses resp)
451         // only handle one response per invitation sender
452         //
453         // TODO: this could have one huge bug: say that Alice sends an invite to Bob, but
454         // Bob is doing something else at the moment and doesn't want to get in an Inkboard
455         // session.  Eve intercepts Bob's "reject invitation" message and passes a
456         // "accept invitation" message to Alice that comes before Bob's "reject invitation"
457         // message. 
458         //
459         // Does XMPP prevent this sort of attack?  Need to investigate that.
460         if (std::find_if(_invitation_responses.begin(), _invitation_responses.end(), CheckInvitationSender(from)) != _invitation_responses.end()) {
461                 return;
462         }
464         // We need to do the invitation confirm/deny dialog elsewhere --
465         // when this method is called, we're still executing in Pedro's context,
466         // which causes issues when we run a dialog main loop.
467         //
468         // The invitation handling is done in a poller timeout that executes approximately
469         // every 50ms.  It calls _checkInvitationResponseQueue.
470         _invitation_responses.push_back(Invitation_response_type(from, resp));
474 }  // namespace Whiteboard
475  
476 }  // namespace Inkscape
479 /*
480   Local Variables:
481   mode:c++
482   c-file-style:"stroustrup"
483   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
484   indent-tabs-mode:nil
485   fill-column:99
486   End:
487 */
488 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :