Code

whiteboard now works on jep compliant servers only, aka wildfire
[inkscape.git] / src / jabber_whiteboard / session-manager.cpp
1 /**
2  * Whiteboard session manager
3  *
4  * Authors:
5  * David Yip <yipdw@rose-hulman.edu>
6  *
7  * Copyright (c) 2005 Authors
8  *
9  * Released under GNU GPL, read the file 'COPYING' for more information
10  */
12 /*
13 #include "inkscape.h"
14 */
16 #include <cstring>
18 #include <glibmm/i18n.h>
19 #include <gtkmm/dialog.h>
20 #include <gtkmm/messagedialog.h>
21 #include <gtkmm/filechooserdialog.h>
22 #include <gtkmm/stock.h>
24 #include "gc-anchored.h"
26 #include "prefs-utils.h"
28 #include "xml/repr.h"
29 #include "xml/node-observer.h"
31 #include "util/ucompose.hpp"
33 #include "message-context.h"
34 #include "message-stack.h"
35 #include "desktop-handles.h"
36 #include "document.h"
37 #include "document-private.h"
38 #include "verbs.h"
40 #include "jabber_whiteboard/defines.h"
41 #include "jabber_whiteboard/typedefs.h"
42 #include "jabber_whiteboard/deserializer.h"
43 #include "jabber_whiteboard/message-utilities.h"
44 #include "jabber_whiteboard/message-handler.h"
45 #include "jabber_whiteboard/node-tracker.h"
46 #include "jabber_whiteboard/jabber-handlers.h"
47 #include "jabber_whiteboard/callbacks.h"
48 #include "jabber_whiteboard/chat-handler.h"
49 #include "jabber_whiteboard/session-file.h"
50 #include "jabber_whiteboard/session-file-player.h"
51 #include "jabber_whiteboard/session-manager.h"
52 #include "jabber_whiteboard/message-aggregator.h"
53 #include "jabber_whiteboard/undo-stack-observer.h"
54 #include "jabber_whiteboard/serializer.h"
56 //#include "jabber_whiteboard/pedro/pedroxmpp.h"
58 #include "jabber_whiteboard/message-node.h"
59 #include "jabber_whiteboard/message-queue.h"
61 namespace Inkscape {
63 namespace Whiteboard {
65 SessionData::SessionData(SessionManager *sm)
66 {
67         this->_sm = sm;
68         this->recipient = NULL;
69         this->connection = NULL;
70         this->ssl = NULL;
71         this->ignoreFurtherSSLErrors = false;
72         this->send_queue = new SendMessageQueue(sm);
73         this->sequence_number = 1;
74 }
76 SessionData::~SessionData()
77 {
78         this->receive_queues.clear();
80         if (this->send_queue) {
81                 delete this->send_queue;
82         }
83 }
85 SessionManager::SessionManager(::SPDesktop *desktop) 
86 {
88         // Initialize private members to NULL to facilitate deletion in destructor
89         this->_myDoc = NULL;
90         this->session_data = NULL;
91         this->_myCallbacks = NULL;
92         this->_myTracker = NULL;
93         this->_myChatHandler = NULL;
94         this->_mySessionFile = NULL;
95         this->_mySessionPlayer = NULL;
96         this->_myMessageHandler = NULL;
97         this->_myUndoObserver = NULL;
98         this->_mySerializer = NULL;
99         this->_myDeserializer = NULL;
101         this->setDesktop(desktop);
103         if (this->_myDoc == NULL) {
104                 g_error("Initializing SessionManager on null document object!");
105         }
108 #ifdef WIN32
109     //# lm_initialize() must be called before any network code
110 /*
111     if (!lm_initialize_called) {
112         lm_initialize();
113         lm_initialize_called = true;
114         }
115 */
116 #endif
118         this->_setVerbSensitivity(INITIAL);
121 SessionManager::~SessionManager()
124         if (this->session_data) {
125                 if (this->session_data->status[IN_WHITEBOARD]) {
126                         // also calls closeSession
127                         this->disconnectFromDocument();
128                 }
129                 this->disconnectFromServer();
131                 if (this->session_data->status[LOGGED_IN]) {
132                         // TODO: unref message handlers
133                 }
134         }
136         if (this->_mySessionFile) {
137                 delete this->_mySessionPlayer;
138                 delete this->_mySessionFile;
139         }
141         delete this->_myChatHandler;
144         // Deletion of _myTracker is done in closeSession;
145         // no need to do it here.
147         // Deletion is handled separately from session teardown and server disconnection
148         // because some teardown methods (e.g. closeSession) require access to members that we will
149         // be deleting. Separating deletion from teardown means that we do not have
150         // to worry (as much) about proper ordering of the teardown sequence.  (We still need
151         // to ensure that destructors in each object being deleted have access to all the
152         // members they need, though.)
154         // Stop dispatchers
155         if (this->_myCallbacks) {
156                 this->stopSendQueueDispatch();
157                 this->stopReceiveQueueDispatch();
158                 delete this->_myCallbacks;
159         }
161         delete this->_myMessageHandler;
163         delete this->session_data;
165         Inkscape::GC::release(this->_myDoc);
169 void
170 SessionManager::setDesktop(::SPDesktop* desktop)
172         this->_myDesktop = desktop;
173         if (this->_myDoc != NULL) {
174                 Inkscape::GC::release(this->_myDoc);
175         }
176         if (sp_desktop_document(desktop) != NULL) {
177                 this->_myDoc = sp_desktop_document(desktop);
178                 Inkscape::GC::anchor(this->_myDoc);
179         }
182 int
183 SessionManager::initializeConnection(Glib::ustring const& server, Glib::ustring const& port, bool usessl)
185         GError* error = NULL;
187         if (!this->session_data) {
188                 this->session_data = new SessionData(this);
189         }
191         if (!this->_myMessageHandler) {
192                 this->_myMessageHandler = new MessageHandler(this);
193         }
195         // Connect to server
196         // We need to check to see if this object already exists, because
197         // the user may be reusing an old connection that failed due to e.g.
198         // authentication failure.
199         if (this->session_data->connection) {
200                 lm_connection_close(this->session_data->connection, &error);
201                 lm_connection_unref(this->session_data->connection);
202         }
204         this->session_data->connection = lm_connection_new(server.c_str());
205         this->session_data->chat_server = server;
207         lm_connection_set_port(this->session_data->connection, atoi(port.c_str()));
209         g_log(NULL, G_LOG_LEVEL_DEBUG, "Opened connection to %s at port %s.  Connecting...",
210                     server.c_str(), port.c_str());
212         if (usessl) {
213                 if (lm_ssl_is_supported()) {
214                         this->session_data->ssl = lm_ssl_new(NULL, ssl_error_handler, reinterpret_cast< gpointer >(this), NULL);
216                         lm_ssl_ref(this->session_data->ssl);
217                 } else {
218                         return SSL_INITIALIZATION_ERROR;
219                 }
220                 lm_connection_set_ssl(this->session_data->connection, this->session_data->ssl);
221         }
223         // Send authorization
224         //lm_connection_set_jid(this->session_data->connection, jid.c_str());
226         //      TODO:
227         //      Asynchronous connection and authentication would be nice,
228         //      but it's a huge mess of mixing C callbacks and C++ method calls.
229         //      I've tried to do it and only managed to severely destabilize the Jabber
230         //      server connection routines.
231         //
232         //      This, of course, is an invitation to anyone more capable than me
233         //      to convert this from synchronous to asynchronous Loudmouth calls.
234         if (!lm_connection_open_and_block(this->session_data->connection, &error)) {
235                 if (error != NULL) {
236                         std::cout << "Failed to open: " <<  error->message << std::endl;
237                 }
238                 return FAILED_TO_CONNECT;
239         }
241         //On successful connect, remember info
242         prefs_set_string_attribute("whiteboard.server", "name", server.c_str());
243         prefs_set_string_attribute("whiteboard.server", "port", port.c_str());
244         prefs_set_int_attribute("whiteboard.server", "ssl", (usessl) ? 1 : 0);
246         return CONNECT_SUCCESS;
249 std::vector<Glib::ustring>
250 SessionManager::getRegistrationInfo()
252         GError* error = NULL;
253         xmlDoc *doc = NULL;
254         xmlNode *root_element = NULL;
255         xmlNode *cur_node = NULL;
257         LmMessage *reply,*request;
258         LmMessageNode  *n;
260         std::vector<Glib::ustring> registerelements; 
262         request = lm_message_new_with_sub_type(NULL,LM_MESSAGE_TYPE_IQ,LM_MESSAGE_SUB_TYPE_GET);
263         n = lm_message_node_add_child (request->node, "query", NULL);
264         lm_message_node_set_attributes (n, "xmlns", "jabber:iq:register", NULL);
266         reply = lm_connection_send_with_reply_and_block(this->session_data->connection, request, &error);
267         if (error != NULL) {
268                 return registerelements;
269         }
271         n = lm_message_get_node(reply);
273         Glib::ustring content = static_cast< Glib::ustring >(lm_message_node_to_string(lm_message_node_get_child(n,"query")));
274         doc = xmlReadMemory(content.c_str(),content.size(), "noname.xml", NULL, 0);
276         if (doc == NULL) {
277                 g_warning("Failed to parse document\n");
278                 return registerelements;
279         }
281         root_element = xmlDocGetRootElement(doc);
283         for (cur_node = root_element->children; cur_node; cur_node = cur_node->next) {
284                 Glib::ustring name = static_cast< Glib::ustring >((char const *)cur_node->name);
285                 if (cur_node->type == XML_ELEMENT_NODE && name != "instructions" 
286                         && name != "username" && name != "password" ) {
287                         registerelements.push_back(name);
288                 }
289         }
291         xmlFreeDoc(doc);
292         xmlCleanupParser();
294         return registerelements;
297 int
298 SessionManager::registerWithServer(Glib::ustring const& username, Glib::ustring const& pw, 
299                                 std::vector<Glib::ustring> key, std::vector<Glib::ustring> val)
302         GError* error = NULL;
303         
304         LmMessage *request,*reply;
305         LmMessageNode  *n;
307         request = lm_message_new_with_sub_type(NULL,LM_MESSAGE_TYPE_IQ,LM_MESSAGE_SUB_TYPE_SET);
308         n = lm_message_node_add_child (request->node, "query", NULL);
309         lm_message_node_set_attributes (n, "xmlns", "jabber:iq:register", NULL);
311         lm_message_node_add_child(n,"username",username.c_str());
312         lm_message_node_add_child(n,"password",pw.c_str());
314         for(unsigned i=0;i<key.size();i++)
315         {       
316                 lm_message_node_add_child(n, (key[i]).c_str(),(val[i]).c_str());
317         }
318         
319         
320         reply = lm_connection_send_with_reply_and_block(this->session_data->connection, request, &error);
321         if (error != NULL || lm_message_get_type(reply) != LM_MESSAGE_SUB_TYPE_RESULT) {
322                 return INVALID_AUTH;
323         }
325         this->session_data->jid = username + "@" + (this->session_data->chat_server.c_str()) + "/" + RESOURCE_NAME;
327         prefs_set_string_attribute("whiteboard.server", "username", username.c_str());
329         return this->finaliseConnection();
332 int
333 SessionManager::connectToServer(Glib::ustring const& server, Glib::ustring const& port, 
334                                 Glib::ustring const& username, Glib::ustring const& pw, bool usessl)
336         GError* error = NULL;
338         initializeConnection(server,port,usessl);
340         this->session_data->jid = username + "@" + server + "/" + RESOURCE_NAME;
342         if (!lm_connection_authenticate_and_block(this->session_data->connection, username.c_str(), pw.c_str(), RESOURCE_NAME, &error)) {
343                 if (error != NULL) {
344                         g_warning("Failed to authenticate: %s", error->message);
345                 }
346                 lm_connection_close(this->session_data->connection, NULL);
347                 lm_connection_unref(this->session_data->connection);
348                 this->session_data->connection = NULL;
349                 return INVALID_AUTH;
350         }
352         g_log(NULL, G_LOG_LEVEL_DEBUG, "Successfully authenticated.");
354         return this->finaliseConnection();
357 int 
358 SessionManager::finaliseConnection()
361         GError* error = NULL;
362         LmMessage* m;
363         LmMessageHandler* mh;
365         // Register message handler for presence messages
366         mh = lm_message_handler_new((LmHandleMessageFunction)presence_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
367         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_PRESENCE, LM_HANDLER_PRIORITY_NORMAL);
369         // Register message handler for stream error messages
370         mh = lm_message_handler_new((LmHandleMessageFunction)stream_error_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
371         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_STREAM_ERROR, LM_HANDLER_PRIORITY_NORMAL);
373         // Register message handler for chat messages
374         mh = lm_message_handler_new((LmHandleMessageFunction)default_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
375         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_MESSAGE, LM_HANDLER_PRIORITY_NORMAL);
377         // Send presence message to server
378         m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_NOT_SET);
379         if (!lm_connection_send(this->session_data->connection, m, &error)) {
380                 if (error != NULL) {
381                         g_warning("Presence message could not be sent: %s", error->message);
382                 }
383                 lm_connection_close(this->session_data->connection, NULL);
384                 lm_connection_unref(this->session_data->connection);
385                 this->session_data->connection = NULL;
386                 return FAILED_TO_CONNECT;
387         }
389         this->session_data->status.set(LOGGED_IN, 1);
391         this->_myCallbacks = new Callbacks(this);
393         lm_message_unref(m);
395         this->_setVerbSensitivity(ESTABLISHED_CONNECTION);
397         return CONNECT_SUCCESS;
400 LmSSLResponse
401 SessionManager::handleSSLError(LmSSL* ssl, LmSSLStatus status)
403         if (this->session_data->ignoreFurtherSSLErrors) {
404                 return LM_SSL_RESPONSE_CONTINUE;
405         }
407         Glib::ustring msg;
409         // TODO: It'd be nice to provide the user with additional information in some cases,
410         // like fingerprints, hostname, etc.
411         switch(status) {
412                 case LM_SSL_STATUS_NO_CERT_FOUND:
413                         msg = _("No SSL certificate was found.");
414                         break;
415                 case LM_SSL_STATUS_UNTRUSTED_CERT:
416                         msg = _("The SSL certificate provided by the Jabber server is untrusted.");
417                         break;
418                 case LM_SSL_STATUS_CERT_EXPIRED:
419                         msg = _("The SSL certificate provided by the Jabber server is expired.");
420                         break;
421                 case LM_SSL_STATUS_CERT_NOT_ACTIVATED:
422                         msg = _("The SSL certificate provided by the Jabber server has not been activated.");
423                         break;
424                 case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:
425                         msg = _("The SSL certificate provided by the Jabber server contains a hostname that does not match the Jabber server's hostname.");
426                         break;
427                 case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH:
428                         msg = _("The SSL certificate provided by the Jabber server contains an invalid fingerprint.");
429                         break;
430                 case LM_SSL_STATUS_GENERIC_ERROR:
431                         msg = _("An unknown error occurred while setting up the SSL connection.");
432                         break;
433         }
435         // TRANSLATORS: %1 is the message that describes the specific error that occurred when
436         // establishing the SSL connection.
437         Glib::ustring mainmsg = String::ucompose(_("<span weight=\"bold\" size=\"larger\">%1</span>\n\nDo you wish to continue connecting to the Jabber server?"), msg);
439         Gtk::MessageDialog dlg(mainmsg, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, false);
440         dlg.add_button(_("Continue connecting and ignore further errors"), 0);
441         dlg.add_button(_("Continue connecting, but warn me of further errors"), 1);
442         dlg.add_button(_("Cancel connection"), 2);
444         switch(dlg.run()) {
445                 case 0:
446                         this->session_data->ignoreFurtherSSLErrors = true;
447                         /* FALL-THROUGH */
448                 case 1:
449                         return LM_SSL_RESPONSE_CONTINUE;
451                 default:
452                         return LM_SSL_RESPONSE_STOP;
453         }
456 void
457 SessionManager::disconnectFromServer()
459         if (this->session_data->connection) 
460         {
461                 GError* error = NULL;
463                 LmMessage *m;
464                 this->disconnectFromDocument();
465                 m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_UNAVAILABLE);
466                 if (!lm_connection_send(this->session_data->connection, m, &error)) {
467                         g_warning("Could not send unavailable presence message: %s", error->message);
468                 }
470                 lm_message_unref(m);
471                 lm_connection_close(this->session_data->connection, NULL);
472                 lm_connection_unref(this->session_data->connection);
473                 if (this->session_data->ssl) {
474                         lm_ssl_unref(this->session_data->ssl);
475                 }
477                 this->session_data->connection = NULL;
478                 this->session_data->ssl = NULL;
479                 this->_setVerbSensitivity(INITIAL);
480         }
483 void
484 SessionManager::disconnectFromDocument()
486         if (this->session_data->status[IN_WHITEBOARD] || !this->session_data->status[IN_CHATROOM]) {
487                 this->sendMessage(DISCONNECTED_FROM_USER_SIGNAL, 0, "", this->session_data->recipient, false);
488         }
489         this->closeSession();
490         this->_setVerbSensitivity(DISCONNECTED_FROM_SESSION);
493 void
494 SessionManager::closeSession()
497         if (this->session_data->status[IN_WHITEBOARD]) {
498                 this->session_data->status.set(IN_WHITEBOARD, 0);
499                 this->session_data->receive_queues.clear();
500                 this->session_data->send_queue->clear();
501                 this->stopSendQueueDispatch();
502                 this->stopReceiveQueueDispatch();
503         }
505         if (this->_myUndoObserver) {
506                 this->_myDoc->removeUndoObserver(*this->_myUndoObserver);
507         }
509         delete this->_myUndoObserver;
510         delete this->_mySerializer;
511         delete this->_myDeserializer;
513         this->_myUndoObserver = NULL;
514         this->_mySerializer = NULL;
515         this->_myDeserializer = NULL;
517         if (this->_myTracker) {
518                 delete this->_myTracker;
519                 this->_myTracker = NULL;
520         }
523         this->setRecipient(NULL);
526 void
527 SessionManager::setRecipient(char const* recipientJID)
529         if (this->session_data->recipient) {
530                 free(const_cast< gchar* >(this->session_data->recipient));
531         }
533         if (recipientJID == NULL) {
534                 this->session_data->recipient = NULL;
535         } else {
536                 this->session_data->recipient = g_strdup(recipientJID);
537         }
540 void
541 SessionManager::sendChange(Glib::ustring const& msg, MessageType type, std::string const& recipientJID, bool chatroom)
543         if (!this->session_data->status[IN_WHITEBOARD]) {
544                 return;
545         }
547         std::string& recipient = const_cast< std::string& >(recipientJID);
548         if (recipient.empty()) {
549                 recipient = this->session_data->recipient;
550         }
551                 
553         switch (type) {
554                 case DOCUMENT_BEGIN:
555                 case DOCUMENT_END:
556                 case CHANGE_NOT_REPEATABLE:
557                 case CHANGE_REPEATABLE:
558                 case CHANGE_COMMIT:
559                 {
560                         MessageNode *newNode = new MessageNode(this->session_data->sequence_number++, this->session_data->jid, recipient, msg, type, false, chatroom);
561                         this->session_data->send_queue->insert(newNode);
562                         Inkscape::GC::release(newNode);
563                         break;
564                 }
565                 default:
566                         g_warning("Cannot insert MessageNode with unknown change type into send queue; discarding message.  This may lead to desynchronization!");
567                         break;
568         }
572 // FIXME:
573 // This method needs a massive, massive, massive overhaul.
574 int
575 SessionManager::sendMessage(MessageType msgtype, unsigned int sequence, Glib::ustring const& msg, char const* recipientJID, bool chatroom)
577         LmMessage* m;
578         GError* error = NULL;
579         char* type, * seq;
581         if (recipientJID == NULL || recipientJID == "") {
582                 g_warning("Null recipient JID specified; not sending message.");
583                 return NO_RECIPIENT_JID;
584         } else {
585         }
587         // create message
588         m = lm_message_new(recipientJID, LM_MESSAGE_TYPE_MESSAGE);
590         // add sender
591         lm_message_node_set_attribute(m->node, "from", this->session_data->jid.c_str());
593         // set message subtype according to whether or not this is
594         // destined for a chatroom
595         if (chatroom) {
596                 lm_message_node_set_attribute(m->node, "type", "groupchat");
597         } else {
598                 lm_message_node_set_attribute(m->node, "type", "chat");
599         }
601         // set protocol version;
602         // we are currently fixed at version 1
603         lm_message_node_add_child(m->node, MESSAGE_PROTOCOL_VER, MESSAGE_PROTOCOL_V1);
605         // add message type
606         type = (char *)calloc(TYPE_FIELD_SIZE, sizeof(char));
607         snprintf(type, TYPE_FIELD_SIZE, "%i", msgtype);
608         lm_message_node_add_child(m->node, MESSAGE_TYPE, type);
609         free(type);
611         // add message body
612         if (!msg.empty()) {
613                 lm_message_node_add_child(m->node, MESSAGE_BODY, msg.c_str());
614         } else {
615         }
617         // add sequence number
618         switch(msgtype) {
619                 case CHANGE_REPEATABLE:
620                 case CHANGE_NOT_REPEATABLE:
621                 case DUMMY_CHANGE:
622                 case CHANGE_COMMIT:
623                 case DOCUMENT_BEGIN:
624                 case DOCUMENT_END:
625                 case CONNECT_REQUEST_RESPONSE_CHAT:
626                 case CONNECT_REQUEST_RESPONSE_USER:
627                 case CHATROOM_SYNCHRONIZE_RESPONSE:
628                         seq = (char* )calloc(SEQNUM_FIELD_SIZE, sizeof(char));
629                         sprintf(seq, "%u", sequence);
630                         lm_message_node_add_child(m->node, MESSAGE_SEQNUM, seq);
631                         free(seq);
632                         break;
634                 case CONNECT_REQUEST_USER:
635                 case CONNECTED_SIGNAL:
636                 case DISCONNECTED_FROM_USER_SIGNAL:
637                         break;
639                 // Error messages and synchronization requests do not need a sequence number
640                 case CHATROOM_SYNCHRONIZE_REQUEST:
641                 case CONNECT_REQUEST_REFUSED_BY_PEER:
642                 case UNSUPPORTED_PROTOCOL_VERSION:
643                 case ALREADY_IN_SESSION:
644                         break;
646                 default:
647                         g_warning("Outgoing message type not recognized; not sending.");
648                         lm_message_unref(m);
649                         return UNKNOWN_OUTGOING_TYPE;
650         }
652         // We want to log messages even if they were not successfully sent,
653         // since the user may opt to re-synchronize a session using the session
654         // file record.
655         if (!msg.empty()) {
656                 this->_log(msg);
657                 this->_commitLog();
658         }
660         // send message
662         if (!lm_connection_send(this->session_data->connection, m, &error)) {
663                 g_warning("Send failed: %s", error->message);
664                 lm_message_unref(m);
665                 return CONNECTION_ERROR;
666         }
668         lm_message_unref(m);
669         return SEND_SUCCESS;
672 void
673 SessionManager::connectionError(Glib::ustring const& errmsg)
675         Gtk::MessageDialog dlg(errmsg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
676         dlg.run();
677 //      sp_whiteboard_connect_dialog(const_cast< gchar* >(errmsg));
680 void
681 SessionManager::resendDocument(char const* recipientJID, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf)
683         Glib::ustring docbegin = MessageUtilities::makeTagWithContent(MESSAGE_DOCBEGIN, "");
684         this->sendChange(docbegin, DOCUMENT_BEGIN, recipientJID, false);
686         Inkscape::XML::Node* root = sp_document_repr_root(this->_myDoc);
688         if(root == NULL) {
689                 return;
690     }
692         NewChildObjectMessageList newchildren;
693         MessageAggregator& agg = MessageAggregator::instance();
694         Glib::ustring buf;
696     for ( Inkscape::XML::Node *child = root->firstChild() ; child != NULL ; child = child->next() ) {
697                 // TODO: replace with Serializer methods
698                 MessageUtilities::newObjectMessage(&buf, newidsbuf, newnodesbuf, newchildren, this->_myTracker, child);
700                 NewChildObjectMessageList::iterator j = newchildren.begin();
701                 Glib::ustring aggbuf;
703                 for(; j != newchildren.end(); j++) {
704                         if (!agg.addOne(*j, aggbuf)) {
705                                 this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
706                                 aggbuf.clear();
707                                 agg.addOne(*j, aggbuf);
708                         }
709                 }
711                 // send remaining changes
712                 if (!aggbuf.empty()) {
713                         this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
714                         aggbuf.clear();
715                 }
717                 newchildren.clear();
718                 buf.clear();
719     }
721         Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_COMMIT, "");
722         this->sendChange(commit, CHANGE_COMMIT, recipientJID, false);
723         Glib::ustring docend = MessageUtilities::makeTagWithContent(MESSAGE_DOCEND, "");
724         this->sendChange(docend, DOCUMENT_END, recipientJID, false);
727 void
728 SessionManager::receiveChange(Glib::ustring const& changemsg)
731         struct Node part;
733         Glib::ustring msgcopy = changemsg.c_str();
736         while(MessageUtilities::getFirstMessageTag(part, msgcopy) != false) {
737                 // TODO:
738                 // Yikes.  This is ugly.
739                 if (part.tag == MESSAGE_CHANGE) {
740                         this->_myDeserializer->deserializeEventChgAttr(part.data);
741                         msgcopy.erase(0, part.next_pos);
743                 } else if (part.tag == MESSAGE_NEWOBJ) {
744                         this->_myDeserializer->deserializeEventAdd(part.data);
745                         msgcopy.erase(0, part.next_pos);
747                 } else if (part.tag == MESSAGE_DELETE) {
748                         this->_myDeserializer->deserializeEventDel(part.data);
749                         msgcopy.erase(0, part.next_pos);
751                 } else if (part.tag == MESSAGE_DOCUMENT) {
752                         // no special handler, just keep going with the rest of the message
753                         msgcopy.erase(0, part.next_pos);
755                 } else if (part.tag == MESSAGE_NODECONTENT) {
756                         this->_myDeserializer->deserializeEventChgContent(part.data);
757                         msgcopy.erase(0, part.next_pos);
759                 } else if (part.tag == MESSAGE_ORDERCHANGE) {
760                         this->_myDeserializer->deserializeEventChgOrder(part.data);
761                         msgcopy.erase(0, part.next_pos);
763                 } else if (part.tag == MESSAGE_COMMIT) {
764                         // Retrieve the deserialized event log, node actions, and nodes with updated attributes
765                         XML::Event* log = this->_myDeserializer->getEventLog();
766                         KeyToNodeActionList& node_changes = this->_myDeserializer->getNodeTrackerActions();
767                         AttributesUpdatedSet& updated = this->_myDeserializer->getUpdatedAttributeNodeSet();
769                         // Make document insensitive to undo
770                         gboolean saved = sp_document_get_undo_sensitive(this->_myDoc);
771                         sp_document_set_undo_sensitive(this->_myDoc, FALSE);
773                         // Replay the log and push it onto the undo stack
774                         sp_repr_replay_log(log);
776                         // Call updateRepr on changed nodes
777                         // This is required for some tools to function properly, i.e. text tool
778                         // (TODO: we don't need to update _all_ changed nodes, just their parents)
779                         AttributesUpdatedSet::iterator i = updated.begin();
780                         for(; i != updated.end(); i++) {
781                                 SPObject* updated = this->_myDoc->getObjectByRepr(*i);
782                                 if (updated) {
783                                         updated->updateRepr();
784                                 }
785                         }
787                         // merge the events generated by updateRepr
788                         sp_repr_coalesce_log(this->_myDoc->priv->partial, log);
789                         this->_myDoc->priv->partial = NULL;
791                         this->_myDoc->priv->undo = g_slist_prepend(this->_myDoc->priv->undo, log);
793                         // Restore undo sensitivity
794                         sp_document_set_undo_sensitive(this->_myDoc, saved);
796                         // Add or delete nodes to/from the tracker
797                         this->_myTracker->process(node_changes);
799                         // Reset deserializer state
800                         this->_myDeserializer->reset();
801                         break;
803                 } else if (part.tag == MESSAGE_UNDO) {
804                         this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::UNDO_EVENT);
805                         sp_document_undo(this->_myDoc);
806                         this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::UNDO_EVENT);
807                         msgcopy.erase(0, part.next_pos);
809                 } else if (part.tag == MESSAGE_REDO) {
810                         this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::REDO_EVENT);
811                         sp_document_redo(this->_myDoc);
812                         this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::REDO_EVENT);
813                         msgcopy.erase(0, part.next_pos);
815                 } else if (part.tag == MESSAGE_DOCBEGIN) {
816                         msgcopy.erase(0, part.next_pos);
818                 } else if (part.tag == MESSAGE_DOCEND) {
819                         // Set this to be the new original state of the document
820                         sp_document_done(this->document());
821                         sp_document_clear_redo(this->document());
822                         sp_document_clear_undo(this->document());
823                         this->setupCommitListener();
824                         msgcopy.erase(0, part.next_pos);
826                 } else {
827                         msgcopy.erase(0, part.next_pos);
829                 }
830         }
832         this->_log(changemsg);
834         this->_commitLog();
837 bool
838 SessionManager::isPlayingSessionFile()
840         return this->session_data->status[PLAYING_SESSION_FILE];
843 void
844 SessionManager::loadSessionFile(Glib::ustring filename)
846         if (!this->session_data || !this->session_data->status[IN_WHITEBOARD]) {
847                 try {
848                         if (this->_mySessionFile) {
849                                 delete this->_mySessionFile;
850                         }
851                         this->_mySessionFile = new SessionFile(filename, true, false);
853                         // Initialize objects needed for session playback
854                         if (this->_mySessionPlayer == NULL) {
855                                 this->_mySessionPlayer = new SessionFilePlayer(16, this);
856                         } else {
857                                 this->_mySessionPlayer->load(this->_mySessionFile);
858                         }
860                         if (this->_myTracker == NULL) {
861                                 this->_myTracker = new XMLNodeTracker(this);
862                         } else {
863                                 this->_myTracker->reset();
864                         }
866                         if (!this->session_data) {
867                                 this->session_data = new SessionData(this);
868                         }
870                         if (!this->_myDeserializer) {
871                                 this->_myDeserializer = new Deserializer(this->node_tracker());
872                         }
874                         if (!this->_myUndoObserver) {
875                                 this->setupCommitListener();
876                         }
878                         this->session_data->status.set(PLAYING_SESSION_FILE, 1);
881                 } catch (Glib::FileError e) {
882                         g_warning("Could not load session file: %s", e.what().data());
883                 }
884         }
887 void
888 SessionManager::userConnectedToWhiteboard(gchar const* JID)
890         sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("Established whiteboard session with <b>%s</b>."), JID);
894 void
895 SessionManager::userDisconnectedFromWhiteboard(std::string const& JID)
898         sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("<b>%s</b> has <b>left</b> the whiteboard session."), JID.c_str());
900         // Inform the user
901         // TRANSLATORS: %1 is the name of the user that disconnected, %2 is the name of the user whom the disconnected user disconnected from.
902         // This message is not used in a chatroom context.
903         Glib::ustring primary = String::ucompose(_("<span weight=\"bold\" size=\"larger\">The user <b>%1</b> has left the whiteboard session.</span>\n\n"), JID);
904         // TRANSLATORS: %1 and %2 are userids
905         Glib::ustring secondary = String::ucompose(_("You are still connected to a Jabber server as <b>%2</b>, and may establish a new session to <b>%1</b> or a different user."), JID, this->session_data->jid);
907         // TODO: parent this dialog to the active desktop
908         Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false);
909         /*
910         dialog.set_message(primary, true);
911         dialog.set_secondary_text(secondary, true);
912         */
913         dialog.run();
916 void
917 SessionManager::startSendQueueDispatch()
919         this->_send_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchSendQueue), SEND_TIMEOUT);
922 void
923 SessionManager::stopSendQueueDispatch()
925         if (this->_send_queue_dispatcher) {
926                 this->_send_queue_dispatcher.disconnect();
927         }
930 void
931 SessionManager::startReceiveQueueDispatch()
933         this->_receive_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchReceiveQueue), SEND_TIMEOUT);
936 void
937 SessionManager::stopReceiveQueueDispatch()
939         if (this->_receive_queue_dispatcher) {
940                 this->_receive_queue_dispatcher.disconnect();
941         }
944 void
945 SessionManager::clearDocument()
947         // clear all layers, definitions, and metadata
948         XML::Node* rroot = this->_myDoc->rroot;
950         // clear definitions
951         XML::Node* defs = SP_OBJECT_REPR((SPDefs*)SP_DOCUMENT_DEFS(this->_myDoc));
952         g_assert(SP_ROOT(this->_myDoc->root)->defs);
954         for(XML::Node* child = defs->firstChild(); child; child = child->next()) {
955                 defs->removeChild(child);
956         }
958         // clear layers
959         for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
960                 if (strcmp(child->name(), "svg:g") == 0) {
961                         rroot->removeChild(child);
962                 }
963         }
965         // clear metadata
966         for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
967                 if (strcmp(child->name(), "svg:metadata") == 0) {
968                         rroot->removeChild(child);
969                 }
970         }
972 //      sp_document_done(this->_myDoc);
975 void
976 SessionManager::setupInkscapeInterface()
978         this->session_data->status.set(IN_WHITEBOARD, 1);
979         this->startSendQueueDispatch();
980         this->startReceiveQueueDispatch();
981         if (!this->_myTracker) {
982                 this->_myTracker = new XMLNodeTracker(this);
983         }
985         this->_mySerializer = new Serializer(this->node_tracker());
986         this->_myDeserializer = new Deserializer(this->node_tracker());
988         // We're in a whiteboard session now, so set verb sensitivity accordingly
989         this->_setVerbSensitivity(ESTABLISHED_SESSION);
992 void
993 SessionManager::setupCommitListener()
995         this->_myUndoObserver = new Whiteboard::UndoStackObserver(this);
996         this->_myDoc->addUndoObserver(*this->_myUndoObserver);
999 ::SPDesktop*
1000 SessionManager::desktop()
1002         return this->_myDesktop;
1005 ::SPDocument*
1006 SessionManager::document()
1008         return this->_myDoc;
1011 Callbacks*
1012 SessionManager::callbacks()
1014         return this->_myCallbacks;
1017 Whiteboard::UndoStackObserver*
1018 SessionManager::undo_stack_observer()
1020         return this->_myUndoObserver;
1023 Serializer*
1024 SessionManager::serializer()
1026         return this->_mySerializer;
1029 XMLNodeTracker*
1030 SessionManager::node_tracker()
1032         return this->_myTracker;
1036 ChatMessageHandler*
1037 SessionManager::chat_handler()
1039         return this->_myChatHandler;
1042 SessionFilePlayer*
1043 SessionManager::session_player()
1045         return this->_mySessionPlayer;
1048 SessionFile*
1049 SessionManager::session_file()
1051         return this->_mySessionFile;
1054 void
1055 SessionManager::_log(Glib::ustring const& message)
1057         if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
1058                 this->_mySessionFile->addMessage(message);
1059         }
1062 void
1063 SessionManager::_commitLog()
1065         if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
1066                 this->_mySessionFile->commit();
1067         }
1070 void
1071 SessionManager::_closeLog()
1073         if (this->_mySessionFile) {
1074                 this->_mySessionFile->close();
1075         }
1078 void
1079 SessionManager::startLog(Glib::ustring filename)
1081         try {
1082                 this->_mySessionFile = new SessionFile(filename, false, false);
1083         } catch (Glib::FileError e) {
1084                 g_warning("Caught I/O error %s while attemping to open file %s for session recording.", e.what().c_str(), filename.c_str());
1085                 throw;
1086         }
1089 void
1090 SessionManager::_tryToStartLog()
1092         if (this->session_data) {
1093                 if (!this->session_data->sessionFile.empty()) {
1094                         bool undecided = true;
1095                         while(undecided) {
1096                                 try {
1097                                         this->startLog(this->session_data->sessionFile);
1098                                         undecided = false;
1099                                 } catch (Glib::FileError e) {
1100                                         undecided = true;
1101                                         Glib::ustring msg = String::ucompose(_("Could not open file %1 for session recording.\nThe error encountered was: %2.\n\nYou may select a different location to record the session, or you may opt to not record this session."), this->session_data->sessionFile, e.what());
1102                                         Gtk::MessageDialog dlg(msg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE, false);
1103                                         dlg.add_button(_("Choose a different location"), 0);
1104                                         dlg.add_button(_("Skip session recording"), 1);
1105                                         switch (dlg.run()) {
1106                                                 case 0:
1107                                                 {
1108                                                         Gtk::FileChooserDialog sessionfiledlg(_("Select a location and filename"), Gtk::FILE_CHOOSER_ACTION_SAVE);
1109                                                         sessionfiledlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
1110                                                         sessionfiledlg.add_button(_("Set filename"), Gtk::RESPONSE_OK);
1111                                                         int result = sessionfiledlg.run();
1112                                                         switch (result) {
1113                                                                 case Gtk::RESPONSE_OK:
1114                                                                 {
1115                                                                         this->session_data->sessionFile = sessionfiledlg.get_filename();
1116                                                                         break;
1117                                                                 }
1118                                                                 case Gtk::RESPONSE_CANCEL:
1119                                                                 default:
1120                                                                         undecided = false;
1121                                                                         break;
1122                                                         }
1123                                                         break;
1124                                                 }
1125                                                 case 1:
1126                                                 default:
1127                                                         undecided = false;
1128                                                         break;
1129                                         }
1130                                 }
1131                         }
1132                 }
1133         }
1136 void
1137 SessionManager::_setVerbSensitivity(SensitivityMode mode)
1139         return;
1141         switch (mode) {
1142                 case ESTABLISHED_CONNECTION:
1143                         // Upon successful connection, we can disconnect from the server.
1144                         // We can also start sharing a document with a user or chatroom.
1145                         // We cannot, however, connect to a new server without first disconnecting.
1146                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, false);
1147                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, true);
1148                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1149                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1150                         break;
1152                 case ESTABLISHED_SESSION:
1153                         // When we have established a session, we should not permit the user to go and
1154                         // establish another session from the same document without first disconnecting.
1155                         //
1156                         // TODO: Well, actually, we probably _should_, but there's no real reconnection logic just yet.
1157                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1158                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1159                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, true);
1160                         break;
1161                 case DISCONNECTED_FROM_SESSION:
1162                         // Upon disconnecting from a session, we can establish a new session and disconnect
1163                         // from the server, but we cannot disconnect from a session (since we just left it.)
1164                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1165                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1166                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1168                 case INITIAL:
1169                 default:
1170                         // Upon construction, there is no active connection, so we cannot do the following:
1171                         // (1) disconnect from a session
1172                         // (2) disconnect from a server
1173                         // (3) share with a user 
1174                         // (4) share with a chatroom
1175                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, true);
1176                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1177                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1178                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1179                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, false);
1180                         break;
1181         };
1184 /*
1185 void
1186 SessionManager::Listener::processXmppEvent(Pedro::XmppEvent const& event)
1188         int type = event.getType();
1190         switch (type) {
1191                 case Pedro::XmppEvent::EVENT_STATUS:
1192                         break;
1193                 case Pedro::XmppEvent::EVENT_ERROR:
1194                         break;
1195                 case Pedro::XmppEvent::EVENT_CONNECTED:
1196                         break;
1197                 case Pedro::XmppEvent::EVENT_DISCONNECTED:
1198                         break;
1199                 case Pedro::XmppEvent::EVENT_MESSAGE:
1200                         break;
1201                 case Pedro::XmppEvent::EVENT_PRESENCE:
1202                         break;
1203                 case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
1204                         break;
1205                 case Pedro::XmppEvent::EVENT_MUC_JOIN:
1206                         break;
1207                 case Pedro::XmppEvent::EVENT_MUC_LEAVE:
1208                         break;
1209                 case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
1210                         break;
1211         }
1213 */
1220 /*
1221   Local Variables:
1222   mode:c++
1223   c-file-style:"stroustrup"
1224   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1225   indent-tabs-mode:nil
1226   fill-column:99
1227   End:
1228 */
1229 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :