Code

9ead6b60b70857f30049447faa9430a4363cd800
[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& entered_username, Glib::ustring const& pw, bool usessl)
336         GError* error = NULL;
337         Glib::ustring username;
338         Glib::ustring jid;
340         initializeConnection(server,port,usessl);
342         Glib::ustring::size_type atPos = entered_username.find('@');
344      if (atPos != Glib::ustring::npos) {
345                 jid += entered_username;
346                 username = entered_username.substr(0, atPos);
347         } else {
348                 jid += entered_username + "@" + server + "/" + RESOURCE_NAME;
349                 username = entered_username;
350         }
351  
352         this->session_data->jid = jid;
354         if (!lm_connection_authenticate_and_block(this->session_data->connection, username.c_str(), pw.c_str(), RESOURCE_NAME, &error)) {
355                 if (error != NULL) {
356                         g_warning("Failed to authenticate: %s", error->message);
357                 }
358                 lm_connection_close(this->session_data->connection, NULL);
359                 lm_connection_unref(this->session_data->connection);
360                 this->session_data->connection = NULL;
361                 return INVALID_AUTH;
362         }
364         g_log(NULL, G_LOG_LEVEL_DEBUG, "Successfully authenticated.");
366         return this->finaliseConnection();
369 int 
370 SessionManager::finaliseConnection()
373         GError* error = NULL;
374         LmMessage* m;
375         LmMessageHandler* mh;
377         // Register message handler for presence messages
378         mh = lm_message_handler_new((LmHandleMessageFunction)presence_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
379         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_PRESENCE, LM_HANDLER_PRIORITY_NORMAL);
381         // Register message handler for stream error messages
382         mh = lm_message_handler_new((LmHandleMessageFunction)stream_error_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
383         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_STREAM_ERROR, LM_HANDLER_PRIORITY_NORMAL);
385         // Register message handler for chat messages
386         mh = lm_message_handler_new((LmHandleMessageFunction)default_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
387         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_MESSAGE, LM_HANDLER_PRIORITY_NORMAL);
389         // Send presence message to server
390         m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_NOT_SET);
391         if (!lm_connection_send(this->session_data->connection, m, &error)) {
392                 if (error != NULL) {
393                         g_warning("Presence message could not be sent: %s", error->message);
394                 }
395                 lm_connection_close(this->session_data->connection, NULL);
396                 lm_connection_unref(this->session_data->connection);
397                 this->session_data->connection = NULL;
398                 return FAILED_TO_CONNECT;
399         }
401         this->session_data->status.set(LOGGED_IN, 1);
403         this->_myCallbacks = new Callbacks(this);
405         lm_message_unref(m);
407         this->_setVerbSensitivity(ESTABLISHED_CONNECTION);
409         return CONNECT_SUCCESS;
412 LmSSLResponse
413 SessionManager::handleSSLError(LmSSL* ssl, LmSSLStatus status)
415         if (this->session_data->ignoreFurtherSSLErrors) {
416                 return LM_SSL_RESPONSE_CONTINUE;
417         }
419         Glib::ustring msg;
421         // TODO: It'd be nice to provide the user with additional information in some cases,
422         // like fingerprints, hostname, etc.
423         switch(status) {
424                 case LM_SSL_STATUS_NO_CERT_FOUND:
425                         msg = _("No SSL certificate was found.");
426                         break;
427                 case LM_SSL_STATUS_UNTRUSTED_CERT:
428                         msg = _("The SSL certificate provided by the Jabber server is untrusted.");
429                         break;
430                 case LM_SSL_STATUS_CERT_EXPIRED:
431                         msg = _("The SSL certificate provided by the Jabber server is expired.");
432                         break;
433                 case LM_SSL_STATUS_CERT_NOT_ACTIVATED:
434                         msg = _("The SSL certificate provided by the Jabber server has not been activated.");
435                         break;
436                 case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:
437                         msg = _("The SSL certificate provided by the Jabber server contains a hostname that does not match the Jabber server's hostname.");
438                         break;
439                 case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH:
440                         msg = _("The SSL certificate provided by the Jabber server contains an invalid fingerprint.");
441                         break;
442                 case LM_SSL_STATUS_GENERIC_ERROR:
443                         msg = _("An unknown error occurred while setting up the SSL connection.");
444                         break;
445         }
447         // TRANSLATORS: %1 is the message that describes the specific error that occurred when
448         // establishing the SSL connection.
449         Glib::ustring mainmsg = String::ucompose(_("<span weight=\"bold\" size=\"larger\">%1</span>\n\nDo you wish to continue connecting to the Jabber server?"), msg);
451         Gtk::MessageDialog dlg(mainmsg, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, false);
452         dlg.add_button(_("Continue connecting and ignore further errors"), 0);
453         dlg.add_button(_("Continue connecting, but warn me of further errors"), 1);
454         dlg.add_button(_("Cancel connection"), 2);
456         switch(dlg.run()) {
457                 case 0:
458                         this->session_data->ignoreFurtherSSLErrors = true;
459                         /* FALL-THROUGH */
460                 case 1:
461                         return LM_SSL_RESPONSE_CONTINUE;
463                 default:
464                         return LM_SSL_RESPONSE_STOP;
465         }
468 void
469 SessionManager::disconnectFromServer()
471         if (this->session_data->connection) 
472         {
473                 GError* error = NULL;
475                 LmMessage *m;
476                 this->disconnectFromDocument();
477                 m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_UNAVAILABLE);
478                 if (!lm_connection_send(this->session_data->connection, m, &error)) {
479                         g_warning("Could not send unavailable presence message: %s", error->message);
480                 }
482                 lm_message_unref(m);
483                 lm_connection_close(this->session_data->connection, NULL);
484                 lm_connection_unref(this->session_data->connection);
485                 if (this->session_data->ssl) {
486                         lm_ssl_unref(this->session_data->ssl);
487                 }
489                 this->session_data->connection = NULL;
490                 this->session_data->ssl = NULL;
491                 this->_setVerbSensitivity(INITIAL);
492         }
495 void
496 SessionManager::disconnectFromDocument()
498         if (this->session_data->status[IN_WHITEBOARD] || !this->session_data->status[IN_CHATROOM]) {
499                 this->sendMessage(DISCONNECTED_FROM_USER_SIGNAL, 0, "", this->session_data->recipient, false);
500         }
501         this->closeSession();
502         this->_setVerbSensitivity(DISCONNECTED_FROM_SESSION);
505 void
506 SessionManager::closeSession()
509         if (this->session_data->status[IN_WHITEBOARD]) {
510                 this->session_data->status.set(IN_WHITEBOARD, 0);
511                 this->session_data->receive_queues.clear();
512                 this->session_data->send_queue->clear();
513                 this->stopSendQueueDispatch();
514                 this->stopReceiveQueueDispatch();
515         }
517         if (this->_myUndoObserver) {
518                 this->_myDoc->removeUndoObserver(*this->_myUndoObserver);
519         }
521         delete this->_myUndoObserver;
522         delete this->_mySerializer;
523         delete this->_myDeserializer;
525         this->_myUndoObserver = NULL;
526         this->_mySerializer = NULL;
527         this->_myDeserializer = NULL;
529         if (this->_myTracker) {
530                 delete this->_myTracker;
531                 this->_myTracker = NULL;
532         }
535         this->setRecipient(NULL);
538 void
539 SessionManager::setRecipient(char const* recipientJID)
541         if (this->session_data->recipient) {
542                 free(const_cast< gchar* >(this->session_data->recipient));
543         }
545         if (recipientJID == NULL) {
546                 this->session_data->recipient = NULL;
547         } else {
548                 this->session_data->recipient = g_strdup(recipientJID);
549         }
552 void
553 SessionManager::sendChange(Glib::ustring const& msg, MessageType type, std::string const& recipientJID, bool chatroom)
555         if (!this->session_data->status[IN_WHITEBOARD]) {
556                 return;
557         }
559         std::string& recipient = const_cast< std::string& >(recipientJID);
560         if (recipient.empty()) {
561                 recipient = this->session_data->recipient;
562         }
563                 
565         switch (type) {
566                 case DOCUMENT_BEGIN:
567                 case DOCUMENT_END:
568                 case CHANGE_NOT_REPEATABLE:
569                 case CHANGE_REPEATABLE:
570                 case CHANGE_COMMIT:
571                 {
572                         MessageNode *newNode = new MessageNode(this->session_data->sequence_number++, this->session_data->jid, recipient, msg, type, false, chatroom);
573                         this->session_data->send_queue->insert(newNode);
574                         Inkscape::GC::release(newNode);
575                         break;
576                 }
577                 default:
578                         g_warning("Cannot insert MessageNode with unknown change type into send queue; discarding message.  This may lead to desynchronization!");
579                         break;
580         }
584 // FIXME:
585 // This method needs a massive, massive, massive overhaul.
586 int
587 SessionManager::sendMessage(MessageType msgtype, unsigned int sequence, Glib::ustring const& msg, char const* recipientJID, bool chatroom)
589         LmMessage* m;
590         GError* error = NULL;
591         char* type, * seq;
593         if (recipientJID == NULL || recipientJID == "") {
594                 g_warning("Null recipient JID specified; not sending message.");
595                 return NO_RECIPIENT_JID;
596         } else {
597         }
599         // create message
600         m = lm_message_new(recipientJID, LM_MESSAGE_TYPE_MESSAGE);
602         // add sender
603         lm_message_node_set_attribute(m->node, "from", this->session_data->jid.c_str());
605         // set message subtype according to whether or not this is
606         // destined for a chatroom
607         if (chatroom) {
608                 lm_message_node_set_attribute(m->node, "type", "groupchat");
609         } else {
610                 lm_message_node_set_attribute(m->node, "type", "chat");
611         }
613         // set protocol version;
614         // we are currently fixed at version 1
615         lm_message_node_add_child(m->node, MESSAGE_PROTOCOL_VER, MESSAGE_PROTOCOL_V1);
617         // add message type
618         type = (char *)calloc(TYPE_FIELD_SIZE, sizeof(char));
619         snprintf(type, TYPE_FIELD_SIZE, "%i", msgtype);
620         lm_message_node_add_child(m->node, MESSAGE_TYPE, type);
621         free(type);
623         // add message body
624         if (!msg.empty()) {
625                 lm_message_node_add_child(m->node, MESSAGE_BODY, msg.c_str());
626         } else {
627         }
629         // add sequence number
630         switch(msgtype) {
631                 case CHANGE_REPEATABLE:
632                 case CHANGE_NOT_REPEATABLE:
633                 case DUMMY_CHANGE:
634                 case CHANGE_COMMIT:
635                 case DOCUMENT_BEGIN:
636                 case DOCUMENT_END:
637                 case CONNECT_REQUEST_RESPONSE_CHAT:
638                 case CONNECT_REQUEST_RESPONSE_USER:
639                 case CHATROOM_SYNCHRONIZE_RESPONSE:
640                         seq = (char* )calloc(SEQNUM_FIELD_SIZE, sizeof(char));
641                         sprintf(seq, "%u", sequence);
642                         lm_message_node_add_child(m->node, MESSAGE_SEQNUM, seq);
643                         free(seq);
644                         break;
646                 case CONNECT_REQUEST_USER:
647                 case CONNECTED_SIGNAL:
648                 case DISCONNECTED_FROM_USER_SIGNAL:
649                         break;
651                 // Error messages and synchronization requests do not need a sequence number
652                 case CHATROOM_SYNCHRONIZE_REQUEST:
653                 case CONNECT_REQUEST_REFUSED_BY_PEER:
654                 case UNSUPPORTED_PROTOCOL_VERSION:
655                 case ALREADY_IN_SESSION:
656                         break;
658                 default:
659                         g_warning("Outgoing message type not recognized; not sending.");
660                         lm_message_unref(m);
661                         return UNKNOWN_OUTGOING_TYPE;
662         }
664         // We want to log messages even if they were not successfully sent,
665         // since the user may opt to re-synchronize a session using the session
666         // file record.
667         if (!msg.empty()) {
668                 this->_log(msg);
669                 this->_commitLog();
670         }
672         // send message
674         if (!lm_connection_send(this->session_data->connection, m, &error)) {
675                 g_warning("Send failed: %s", error->message);
676                 lm_message_unref(m);
677                 return CONNECTION_ERROR;
678         }
680         lm_message_unref(m);
681         return SEND_SUCCESS;
684 void
685 SessionManager::connectionError(Glib::ustring const& errmsg)
687         Gtk::MessageDialog dlg(errmsg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
688         dlg.run();
689 //      sp_whiteboard_connect_dialog(const_cast< gchar* >(errmsg));
692 void
693 SessionManager::resendDocument(char const* recipientJID, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf)
695         Glib::ustring docbegin = MessageUtilities::makeTagWithContent(MESSAGE_DOCBEGIN, "");
696         this->sendChange(docbegin, DOCUMENT_BEGIN, recipientJID, false);
698         Inkscape::XML::Node* root = sp_document_repr_root(this->_myDoc);
700         if(root == NULL) {
701                 return;
702     }
704         NewChildObjectMessageList newchildren;
705         MessageAggregator& agg = MessageAggregator::instance();
706         Glib::ustring buf;
708     for ( Inkscape::XML::Node *child = root->firstChild() ; child != NULL ; child = child->next() ) {
709                 // TODO: replace with Serializer methods
710                 MessageUtilities::newObjectMessage(&buf, newidsbuf, newnodesbuf, newchildren, this->_myTracker, child);
712                 NewChildObjectMessageList::iterator j = newchildren.begin();
713                 Glib::ustring aggbuf;
715                 for(; j != newchildren.end(); j++) {
716                         if (!agg.addOne(*j, aggbuf)) {
717                                 this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
718                                 aggbuf.clear();
719                                 agg.addOne(*j, aggbuf);
720                         }
721                 }
723                 // send remaining changes
724                 if (!aggbuf.empty()) {
725                         this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
726                         aggbuf.clear();
727                 }
729                 newchildren.clear();
730                 buf.clear();
731     }
733         Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_COMMIT, "");
734         this->sendChange(commit, CHANGE_COMMIT, recipientJID, false);
735         Glib::ustring docend = MessageUtilities::makeTagWithContent(MESSAGE_DOCEND, "");
736         this->sendChange(docend, DOCUMENT_END, recipientJID, false);
739 void
740 SessionManager::receiveChange(Glib::ustring const& changemsg)
743         struct Node part;
745         Glib::ustring msgcopy = changemsg.c_str();
748         while(MessageUtilities::getFirstMessageTag(part, msgcopy) != false) {
749                 // TODO:
750                 // Yikes.  This is ugly.
751                 if (part.tag == MESSAGE_CHANGE) {
752                         this->_myDeserializer->deserializeEventChgAttr(part.data);
753                         msgcopy.erase(0, part.next_pos);
755                 } else if (part.tag == MESSAGE_NEWOBJ) {
756                         this->_myDeserializer->deserializeEventAdd(part.data);
757                         msgcopy.erase(0, part.next_pos);
759                 } else if (part.tag == MESSAGE_DELETE) {
760                         this->_myDeserializer->deserializeEventDel(part.data);
761                         msgcopy.erase(0, part.next_pos);
763                 } else if (part.tag == MESSAGE_DOCUMENT) {
764                         // no special handler, just keep going with the rest of the message
765                         msgcopy.erase(0, part.next_pos);
767                 } else if (part.tag == MESSAGE_NODECONTENT) {
768                         this->_myDeserializer->deserializeEventChgContent(part.data);
769                         msgcopy.erase(0, part.next_pos);
771                 } else if (part.tag == MESSAGE_ORDERCHANGE) {
772                         this->_myDeserializer->deserializeEventChgOrder(part.data);
773                         msgcopy.erase(0, part.next_pos);
775                 } else if (part.tag == MESSAGE_COMMIT) {
776                         // Retrieve the deserialized event log, node actions, and nodes with updated attributes
777                         XML::Event* log = this->_myDeserializer->getEventLog();
778                         KeyToNodeActionList& node_changes = this->_myDeserializer->getNodeTrackerActions();
779                         AttributesUpdatedSet& updated = this->_myDeserializer->getUpdatedAttributeNodeSet();
781                         // Make document insensitive to undo
782                         gboolean saved = sp_document_get_undo_sensitive(this->_myDoc);
783                         sp_document_set_undo_sensitive(this->_myDoc, FALSE);
785                         // Replay the log and push it onto the undo stack
786                         sp_repr_replay_log(log);
788                         // Call updateRepr on changed nodes
789                         // This is required for some tools to function properly, i.e. text tool
790                         // (TODO: we don't need to update _all_ changed nodes, just their parents)
791                         AttributesUpdatedSet::iterator i = updated.begin();
792                         for(; i != updated.end(); i++) {
793                                 SPObject* updated = this->_myDoc->getObjectByRepr(*i);
794                                 if (updated) {
795                                         updated->updateRepr();
796                                 }
797                         }
799                         // merge the events generated by updateRepr
800                         sp_repr_coalesce_log(this->_myDoc->priv->partial, log);
801                         this->_myDoc->priv->partial = NULL;
803                         this->_myDoc->priv->undo = g_slist_prepend(this->_myDoc->priv->undo, log);
805                         // Restore undo sensitivity
806                         sp_document_set_undo_sensitive(this->_myDoc, saved);
808                         // Add or delete nodes to/from the tracker
809                         this->_myTracker->process(node_changes);
811                         // Reset deserializer state
812                         this->_myDeserializer->reset();
813                         break;
815                 } else if (part.tag == MESSAGE_UNDO) {
816                         this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::UNDO_EVENT);
817                         sp_document_undo(this->_myDoc);
818                         this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::UNDO_EVENT);
819                         msgcopy.erase(0, part.next_pos);
821                 } else if (part.tag == MESSAGE_REDO) {
822                         this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::REDO_EVENT);
823                         sp_document_redo(this->_myDoc);
824                         this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::REDO_EVENT);
825                         msgcopy.erase(0, part.next_pos);
827                 } else if (part.tag == MESSAGE_DOCBEGIN) {
828                         msgcopy.erase(0, part.next_pos);
830                 } else if (part.tag == MESSAGE_DOCEND) {
831                         // Set this to be the new original state of the document
832                         sp_document_done(this->document());
833                         sp_document_clear_redo(this->document());
834                         sp_document_clear_undo(this->document());
835                         this->setupCommitListener();
836                         msgcopy.erase(0, part.next_pos);
838                 } else {
839                         msgcopy.erase(0, part.next_pos);
841                 }
842         }
844         this->_log(changemsg);
846         this->_commitLog();
849 bool
850 SessionManager::isPlayingSessionFile()
852         return this->session_data->status[PLAYING_SESSION_FILE];
855 void
856 SessionManager::loadSessionFile(Glib::ustring filename)
858         if (!this->session_data || !this->session_data->status[IN_WHITEBOARD]) {
859                 try {
860                         if (this->_mySessionFile) {
861                                 delete this->_mySessionFile;
862                         }
863                         this->_mySessionFile = new SessionFile(filename, true, false);
865                         // Initialize objects needed for session playback
866                         if (this->_mySessionPlayer == NULL) {
867                                 this->_mySessionPlayer = new SessionFilePlayer(16, this);
868                         } else {
869                                 this->_mySessionPlayer->load(this->_mySessionFile);
870                         }
872                         if (this->_myTracker == NULL) {
873                                 this->_myTracker = new XMLNodeTracker(this);
874                         } else {
875                                 this->_myTracker->reset();
876                         }
878                         if (!this->session_data) {
879                                 this->session_data = new SessionData(this);
880                         }
882                         if (!this->_myDeserializer) {
883                                 this->_myDeserializer = new Deserializer(this->node_tracker());
884                         }
886                         if (!this->_myUndoObserver) {
887                                 this->setupCommitListener();
888                         }
890                         this->session_data->status.set(PLAYING_SESSION_FILE, 1);
893                 } catch (Glib::FileError e) {
894                         g_warning("Could not load session file: %s", e.what().data());
895                 }
896         }
899 void
900 SessionManager::userConnectedToWhiteboard(gchar const* JID)
902         sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("Established whiteboard session with <b>%s</b>."), JID);
906 void
907 SessionManager::userDisconnectedFromWhiteboard(std::string const& JID)
910         sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("<b>%s</b> has <b>left</b> the whiteboard session."), JID.c_str());
912         // Inform the user
913         // TRANSLATORS: %1 is the name of the user that disconnected, %2 is the name of the user whom the disconnected user disconnected from.
914         // This message is not used in a chatroom context.
915         Glib::ustring primary = String::ucompose(_("<span weight=\"bold\" size=\"larger\">The user <b>%1</b> has left the whiteboard session.</span>\n\n"), JID);
916         // TRANSLATORS: %1 and %2 are userids
917         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);
919         // TODO: parent this dialog to the active desktop
920         Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false);
921         /*
922         dialog.set_message(primary, true);
923         dialog.set_secondary_text(secondary, true);
924         */
925         dialog.run();
928 void
929 SessionManager::startSendQueueDispatch()
931         this->_send_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchSendQueue), SEND_TIMEOUT);
934 void
935 SessionManager::stopSendQueueDispatch()
937         if (this->_send_queue_dispatcher) {
938                 this->_send_queue_dispatcher.disconnect();
939         }
942 void
943 SessionManager::startReceiveQueueDispatch()
945         this->_receive_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchReceiveQueue), SEND_TIMEOUT);
948 void
949 SessionManager::stopReceiveQueueDispatch()
951         if (this->_receive_queue_dispatcher) {
952                 this->_receive_queue_dispatcher.disconnect();
953         }
956 void
957 SessionManager::clearDocument()
959         // clear all layers, definitions, and metadata
960         XML::Node* rroot = this->_myDoc->rroot;
962         // clear definitions
963         XML::Node* defs = SP_OBJECT_REPR((SPDefs*)SP_DOCUMENT_DEFS(this->_myDoc));
964         g_assert(SP_ROOT(this->_myDoc->root)->defs);
966         for(XML::Node* child = defs->firstChild(); child; child = child->next()) {
967                 defs->removeChild(child);
968         }
970         // clear layers
971         for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
972                 if (strcmp(child->name(), "svg:g") == 0) {
973                         rroot->removeChild(child);
974                 }
975         }
977         // clear metadata
978         for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
979                 if (strcmp(child->name(), "svg:metadata") == 0) {
980                         rroot->removeChild(child);
981                 }
982         }
984 //      sp_document_done(this->_myDoc);
987 void
988 SessionManager::setupInkscapeInterface()
990         this->session_data->status.set(IN_WHITEBOARD, 1);
991         this->startSendQueueDispatch();
992         this->startReceiveQueueDispatch();
993         if (!this->_myTracker) {
994                 this->_myTracker = new XMLNodeTracker(this);
995         }
997         this->_mySerializer = new Serializer(this->node_tracker());
998         this->_myDeserializer = new Deserializer(this->node_tracker());
1000         // We're in a whiteboard session now, so set verb sensitivity accordingly
1001         this->_setVerbSensitivity(ESTABLISHED_SESSION);
1004 void
1005 SessionManager::setupCommitListener()
1007         this->_myUndoObserver = new Whiteboard::UndoStackObserver(this);
1008         this->_myDoc->addUndoObserver(*this->_myUndoObserver);
1011 ::SPDesktop*
1012 SessionManager::desktop()
1014         return this->_myDesktop;
1017 ::SPDocument*
1018 SessionManager::document()
1020         return this->_myDoc;
1023 Callbacks*
1024 SessionManager::callbacks()
1026         return this->_myCallbacks;
1029 Whiteboard::UndoStackObserver*
1030 SessionManager::undo_stack_observer()
1032         return this->_myUndoObserver;
1035 Serializer*
1036 SessionManager::serializer()
1038         return this->_mySerializer;
1041 XMLNodeTracker*
1042 SessionManager::node_tracker()
1044         return this->_myTracker;
1048 ChatMessageHandler*
1049 SessionManager::chat_handler()
1051         return this->_myChatHandler;
1054 SessionFilePlayer*
1055 SessionManager::session_player()
1057         return this->_mySessionPlayer;
1060 SessionFile*
1061 SessionManager::session_file()
1063         return this->_mySessionFile;
1066 void
1067 SessionManager::_log(Glib::ustring const& message)
1069         if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
1070                 this->_mySessionFile->addMessage(message);
1071         }
1074 void
1075 SessionManager::_commitLog()
1077         if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
1078                 this->_mySessionFile->commit();
1079         }
1082 void
1083 SessionManager::_closeLog()
1085         if (this->_mySessionFile) {
1086                 this->_mySessionFile->close();
1087         }
1090 void
1091 SessionManager::startLog(Glib::ustring filename)
1093         try {
1094                 this->_mySessionFile = new SessionFile(filename, false, false);
1095         } catch (Glib::FileError e) {
1096                 g_warning("Caught I/O error %s while attemping to open file %s for session recording.", e.what().c_str(), filename.c_str());
1097                 throw;
1098         }
1101 void
1102 SessionManager::_tryToStartLog()
1104         if (this->session_data) {
1105                 if (!this->session_data->sessionFile.empty()) {
1106                         bool undecided = true;
1107                         while(undecided) {
1108                                 try {
1109                                         this->startLog(this->session_data->sessionFile);
1110                                         undecided = false;
1111                                 } catch (Glib::FileError e) {
1112                                         undecided = true;
1113                                         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());
1114                                         Gtk::MessageDialog dlg(msg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE, false);
1115                                         dlg.add_button(_("Choose a different location"), 0);
1116                                         dlg.add_button(_("Skip session recording"), 1);
1117                                         switch (dlg.run()) {
1118                                                 case 0:
1119                                                 {
1120                                                         Gtk::FileChooserDialog sessionfiledlg(_("Select a location and filename"), Gtk::FILE_CHOOSER_ACTION_SAVE);
1121                                                         sessionfiledlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
1122                                                         sessionfiledlg.add_button(_("Set filename"), Gtk::RESPONSE_OK);
1123                                                         int result = sessionfiledlg.run();
1124                                                         switch (result) {
1125                                                                 case Gtk::RESPONSE_OK:
1126                                                                 {
1127                                                                         this->session_data->sessionFile = sessionfiledlg.get_filename();
1128                                                                         break;
1129                                                                 }
1130                                                                 case Gtk::RESPONSE_CANCEL:
1131                                                                 default:
1132                                                                         undecided = false;
1133                                                                         break;
1134                                                         }
1135                                                         break;
1136                                                 }
1137                                                 case 1:
1138                                                 default:
1139                                                         undecided = false;
1140                                                         break;
1141                                         }
1142                                 }
1143                         }
1144                 }
1145         }
1148 void
1149 SessionManager::_setVerbSensitivity(SensitivityMode mode)
1151         return;
1153         switch (mode) {
1154                 case ESTABLISHED_CONNECTION:
1155                         // Upon successful connection, we can disconnect from the server.
1156                         // We can also start sharing a document with a user or chatroom.
1157                         // We cannot, however, connect to a new server without first disconnecting.
1158                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, false);
1159                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, true);
1160                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1161                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1162                         break;
1164                 case ESTABLISHED_SESSION:
1165                         // When we have established a session, we should not permit the user to go and
1166                         // establish another session from the same document without first disconnecting.
1167                         //
1168                         // TODO: Well, actually, we probably _should_, but there's no real reconnection logic just yet.
1169                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1170                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1171                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, true);
1172                         break;
1173                 case DISCONNECTED_FROM_SESSION:
1174                         // Upon disconnecting from a session, we can establish a new session and disconnect
1175                         // from the server, but we cannot disconnect from a session (since we just left it.)
1176                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1177                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1178                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1180                 case INITIAL:
1181                 default:
1182                         // Upon construction, there is no active connection, so we cannot do the following:
1183                         // (1) disconnect from a session
1184                         // (2) disconnect from a server
1185                         // (3) share with a user 
1186                         // (4) share with a chatroom
1187                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, true);
1188                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1189                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1190                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1191                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, false);
1192                         break;
1193         };
1196 /*
1197 void
1198 SessionManager::Listener::processXmppEvent(Pedro::XmppEvent const& event)
1200         int type = event.getType();
1202         switch (type) {
1203                 case Pedro::XmppEvent::EVENT_STATUS:
1204                         break;
1205                 case Pedro::XmppEvent::EVENT_ERROR:
1206                         break;
1207                 case Pedro::XmppEvent::EVENT_CONNECTED:
1208                         break;
1209                 case Pedro::XmppEvent::EVENT_DISCONNECTED:
1210                         break;
1211                 case Pedro::XmppEvent::EVENT_MESSAGE:
1212                         break;
1213                 case Pedro::XmppEvent::EVENT_PRESENCE:
1214                         break;
1215                 case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
1216                         break;
1217                 case Pedro::XmppEvent::EVENT_MUC_JOIN:
1218                         break;
1219                 case Pedro::XmppEvent::EVENT_MUC_LEAVE:
1220                         break;
1221                 case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
1222                         break;
1223         }
1225 */
1232 /*
1233   Local Variables:
1234   mode:c++
1235   c-file-style:"stroustrup"
1236   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1237   indent-tabs-mode:nil
1238   fill-column:99
1239   End:
1240 */
1241 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :