Code

removed resource name from self's jid, allowing authentication against jabber.org
[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::connectToServer(Glib::ustring const& server, Glib::ustring const& port, Glib::ustring const& username, Glib::ustring const& pw, bool usessl)
185         GError* error = NULL;
186         Glib::ustring jid;
188         // JID format is username@server/resource
189         jid += username + "@" + server;
191         LmMessage* m;
192         LmMessageHandler* mh;
194         if (!this->session_data) {
195                 this->session_data = new SessionData(this);
196         }
198         if (!this->_myMessageHandler) {
199                 this->_myMessageHandler = new MessageHandler(this);
200         }
202         // Connect to server
203         // We need to check to see if this object already exists, because
204         // the user may be reusing an old connection that failed due to e.g.
205         // authentication failure.
206         if (this->session_data->connection) {
207                 lm_connection_close(this->session_data->connection, &error);
208                 lm_connection_unref(this->session_data->connection);
209         }
211         this->session_data->connection = lm_connection_new(server.c_str());
213         lm_connection_set_port(this->session_data->connection, atoi(port.c_str()));
215         g_log(NULL, G_LOG_LEVEL_DEBUG, "Opened connection to %s at port %s.  Connecting...",
216                     server.c_str(), port.c_str());
218         if (usessl) {
219                 if (lm_ssl_is_supported()) {
220                         this->session_data->ssl = lm_ssl_new(NULL, ssl_error_handler, reinterpret_cast< gpointer >(this), NULL);
222                         lm_ssl_ref(this->session_data->ssl);
223                 } else {
224                         return SSL_INITIALIZATION_ERROR;
225                 }
226                 lm_connection_set_ssl(this->session_data->connection, this->session_data->ssl);
227         }
229         // Send authorization
230         lm_connection_set_jid(this->session_data->connection, jid.c_str());
232         //      TODO:
233         //      Asynchronous connection and authentication would be nice,
234         //      but it's a huge mess of mixing C callbacks and C++ method calls.
235         //      I've tried to do it and only managed to severely destabilize the Jabber
236         //      server connection routines.
237         //
238         //      This, of course, is an invitation to anyone more capable than me
239         //      to convert this from synchronous to asynchronous Loudmouth calls.
240         if (!lm_connection_open_and_block(this->session_data->connection, &error)) {
241                 if (error != NULL) {
242                         std::cout << "Failed to open: " <<  error->message << std::endl;
243                 }
244                 return FAILED_TO_CONNECT;
245         }
247         g_log(NULL, G_LOG_LEVEL_DEBUG, "Opened Loudmouth connection in blocking mode.");
249         // Authenticate
250         if (!lm_connection_authenticate_and_block(this->session_data->connection, username.c_str(), pw.c_str(), RESOURCE_NAME, &error)) {
251                 if (error != NULL) {
252                         g_warning("Failed to authenticate: %s", error->message);
253                 }
254                 lm_connection_close(this->session_data->connection, NULL);
255                 lm_connection_unref(this->session_data->connection);
256                 this->session_data->connection = NULL;
257                 return INVALID_AUTH;
258         }
260         g_log(NULL, G_LOG_LEVEL_DEBUG, "Successfully authenticated.");
262         // Register message handler for presence messages
263         mh = lm_message_handler_new((LmHandleMessageFunction)presence_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
264         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_PRESENCE, LM_HANDLER_PRIORITY_NORMAL);
266         // Register message handler for stream error messages
267         mh = lm_message_handler_new((LmHandleMessageFunction)stream_error_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
268         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_STREAM_ERROR, LM_HANDLER_PRIORITY_NORMAL);
270         // Register message handler for chat messages
271         mh = lm_message_handler_new((LmHandleMessageFunction)default_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
272         lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_MESSAGE, LM_HANDLER_PRIORITY_NORMAL);
274         // Send presence message to server
275         m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_NOT_SET);
276         if (!lm_connection_send(this->session_data->connection, m, &error)) {
277                 if (error != NULL) {
278                         g_warning("Presence message could not be sent: %s", error->message);
279                 }
280                 lm_connection_close(this->session_data->connection, NULL);
281                 lm_connection_unref(this->session_data->connection);
282                 this->session_data->connection = NULL;
283                 return FAILED_TO_CONNECT;
284         }
286         this->session_data->status.set(LOGGED_IN, 1);
288         this->_myCallbacks = new Callbacks(this);
290         lm_message_unref(m);
292         this->_setVerbSensitivity(ESTABLISHED_CONNECTION);
294         //On successful connect, remember info
295         prefs_set_string_attribute("whiteboard.server", "name", server.c_str());
296         prefs_set_string_attribute("whiteboard.server", "port", port.c_str());
297         prefs_set_string_attribute("whiteboard.server", "username", username.c_str());
298         prefs_set_int_attribute("whiteboard.server", "ssl", (usessl) ? 1 : 0);
299         //Option to store password here?
302         return CONNECT_SUCCESS;
305 LmSSLResponse
306 SessionManager::handleSSLError(LmSSL* ssl, LmSSLStatus status)
308         if (this->session_data->ignoreFurtherSSLErrors) {
309                 return LM_SSL_RESPONSE_CONTINUE;
310         }
312         Glib::ustring msg;
314         // TODO: It'd be nice to provide the user with additional information in some cases,
315         // like fingerprints, hostname, etc.
316         switch(status) {
317                 case LM_SSL_STATUS_NO_CERT_FOUND:
318                         msg = _("No SSL certificate was found.");
319                         break;
320                 case LM_SSL_STATUS_UNTRUSTED_CERT:
321                         msg = _("The SSL certificate provided by the Jabber server is untrusted.");
322                         break;
323                 case LM_SSL_STATUS_CERT_EXPIRED:
324                         msg = _("The SSL certificate provided by the Jabber server is expired.");
325                         break;
326                 case LM_SSL_STATUS_CERT_NOT_ACTIVATED:
327                         msg = _("The SSL certificate provided by the Jabber server has not been activated.");
328                         break;
329                 case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:
330                         msg = _("The SSL certificate provided by the Jabber server contains a hostname that does not match the Jabber server's hostname.");
331                         break;
332                 case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH:
333                         msg = _("The SSL certificate provided by the Jabber server contains an invalid fingerprint.");
334                         break;
335                 case LM_SSL_STATUS_GENERIC_ERROR:
336                         msg = _("An unknown error occurred while setting up the SSL connection.");
337                         break;
338         }
340         // TRANSLATORS: %1 is the message that describes the specific error that occurred when
341         // establishing the SSL connection.
342         Glib::ustring mainmsg = String::ucompose(_("<span weight=\"bold\" size=\"larger\">%1</span>\n\nDo you wish to continue connecting to the Jabber server?"), msg);
344         Gtk::MessageDialog dlg(mainmsg, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, false);
345         dlg.add_button(_("Continue connecting and ignore further errors"), 0);
346         dlg.add_button(_("Continue connecting, but warn me of further errors"), 1);
347         dlg.add_button(_("Cancel connection"), 2);
349         switch(dlg.run()) {
350                 case 0:
351                         this->session_data->ignoreFurtherSSLErrors = true;
352                         /* FALL-THROUGH */
353                 case 1:
354                         return LM_SSL_RESPONSE_CONTINUE;
356                 default:
357                         return LM_SSL_RESPONSE_STOP;
358         }
361 void
362 SessionManager::disconnectFromServer()
364         if (this->session_data->connection) {
365                 GError* error = NULL;
367                 LmMessage *m;
368                 this->disconnectFromDocument();
369                 m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_UNAVAILABLE);
370                 if (!lm_connection_send(this->session_data->connection, m, &error)) {
371                         g_warning("Could not send unavailable presence message: %s", error->message);
372                 }
374                 lm_message_unref(m);
375                 lm_connection_close(this->session_data->connection, NULL);
376                 lm_connection_unref(this->session_data->connection);
377                 if (this->session_data->ssl) {
378                         lm_ssl_unref(this->session_data->ssl);
379                 }
381                 this->session_data->connection = NULL;
382                 this->session_data->ssl = NULL;
383                 this->_setVerbSensitivity(INITIAL);
384         }
387 void
388 SessionManager::disconnectFromDocument()
390         if (this->session_data->status[IN_WHITEBOARD] || !this->session_data->status[IN_CHATROOM]) {
391                 this->sendMessage(DISCONNECTED_FROM_USER_SIGNAL, 0, "", this->session_data->recipient, false);
392         }
393         this->closeSession();
394         this->_setVerbSensitivity(DISCONNECTED_FROM_SESSION);
397 void
398 SessionManager::closeSession()
401         if (this->session_data->status[IN_WHITEBOARD]) {
402                 this->session_data->status.set(IN_WHITEBOARD, 0);
403                 this->session_data->receive_queues.clear();
404                 this->session_data->send_queue->clear();
405                 this->stopSendQueueDispatch();
406                 this->stopReceiveQueueDispatch();
407         }
409         if (this->_myUndoObserver) {
410                 this->_myDoc->removeUndoObserver(*this->_myUndoObserver);
411         }
413         delete this->_myUndoObserver;
414         delete this->_mySerializer;
415         delete this->_myDeserializer;
417         this->_myUndoObserver = NULL;
418         this->_mySerializer = NULL;
419         this->_myDeserializer = NULL;
421         if (this->_myTracker) {
422                 delete this->_myTracker;
423                 this->_myTracker = NULL;
424         }
427         this->setRecipient(NULL);
430 void
431 SessionManager::setRecipient(char const* recipientJID)
433         if (this->session_data->recipient) {
434                 free(const_cast< gchar* >(this->session_data->recipient));
435         }
437         if (recipientJID == NULL) {
438                 this->session_data->recipient = NULL;
439         } else {
440                 this->session_data->recipient = g_strdup(recipientJID);
441         }
444 void
445 SessionManager::sendChange(Glib::ustring const& msg, MessageType type, std::string const& recipientJID, bool chatroom)
447         if (!this->session_data->status[IN_WHITEBOARD]) {
448                 return;
449         }
451         std::string& recipient = const_cast< std::string& >(recipientJID);
452         if (recipient.empty()) {
453                 recipient = this->session_data->recipient;
454         }
455                 
457         switch (type) {
458                 case DOCUMENT_BEGIN:
459                 case DOCUMENT_END:
460                 case CHANGE_NOT_REPEATABLE:
461                 case CHANGE_REPEATABLE:
462                 case CHANGE_COMMIT:
463                 {
464                         MessageNode *newNode = new MessageNode(this->session_data->sequence_number++, lm_connection_get_jid(this->session_data->connection), recipient, msg, type, false, chatroom);
465                         this->session_data->send_queue->insert(newNode);
466                         Inkscape::GC::release(newNode);
467                         break;
468                 }
469                 default:
470                         g_warning("Cannot insert MessageNode with unknown change type into send queue; discarding message.  This may lead to desynchronization!");
471                         break;
472         }
476 // FIXME:
477 // This method needs a massive, massive, massive overhaul.
478 int
479 SessionManager::sendMessage(MessageType msgtype, unsigned int sequence, Glib::ustring const& msg, char const* recipientJID, bool chatroom)
481         LmMessage* m;
482         GError* error = NULL;
483         char* type, * seq;
485         if (recipientJID == NULL || recipientJID == "") {
486                 g_warning("Null recipient JID specified; not sending message.");
487                 return NO_RECIPIENT_JID;
488         } else {
489         }
491         // create message
492         m = lm_message_new(recipientJID, LM_MESSAGE_TYPE_MESSAGE);
494         // add sender
495         lm_message_node_set_attribute(m->node, "from", lm_connection_get_jid(this->session_data->connection));
497         // set message subtype according to whether or not this is
498         // destined for a chatroom
499         if (chatroom) {
500                 lm_message_node_set_attribute(m->node, "type", "groupchat");
501         } else {
502                 lm_message_node_set_attribute(m->node, "type", "chat");
503         }
505         // set protocol version;
506         // we are currently fixed at version 1
507         lm_message_node_add_child(m->node, MESSAGE_PROTOCOL_VER, MESSAGE_PROTOCOL_V1);
509         // add message type
510         type = (char *)calloc(TYPE_FIELD_SIZE, sizeof(char));
511         snprintf(type, TYPE_FIELD_SIZE, "%i", msgtype);
512         lm_message_node_add_child(m->node, MESSAGE_TYPE, type);
513         free(type);
515         // add message body
516         if (!msg.empty()) {
517                 lm_message_node_add_child(m->node, MESSAGE_BODY, msg.c_str());
518         } else {
519         }
521         // add sequence number
522         switch(msgtype) {
523                 case CHANGE_REPEATABLE:
524                 case CHANGE_NOT_REPEATABLE:
525                 case DUMMY_CHANGE:
526                 case CHANGE_COMMIT:
527                 case DOCUMENT_BEGIN:
528                 case DOCUMENT_END:
529                 case CONNECT_REQUEST_RESPONSE_CHAT:
530                 case CONNECT_REQUEST_RESPONSE_USER:
531                 case CHATROOM_SYNCHRONIZE_RESPONSE:
532                         seq = (char* )calloc(SEQNUM_FIELD_SIZE, sizeof(char));
533                         sprintf(seq, "%u", sequence);
534                         lm_message_node_add_child(m->node, MESSAGE_SEQNUM, seq);
535                         free(seq);
536                         break;
538                 case CONNECT_REQUEST_USER:
539                 case CONNECTED_SIGNAL:
540                 case DISCONNECTED_FROM_USER_SIGNAL:
541                         break;
543                 // Error messages and synchronization requests do not need a sequence number
544                 case CHATROOM_SYNCHRONIZE_REQUEST:
545                 case CONNECT_REQUEST_REFUSED_BY_PEER:
546                 case UNSUPPORTED_PROTOCOL_VERSION:
547                 case ALREADY_IN_SESSION:
548                         break;
550                 default:
551                         g_warning("Outgoing message type not recognized; not sending.");
552                         lm_message_unref(m);
553                         return UNKNOWN_OUTGOING_TYPE;
554         }
556         // We want to log messages even if they were not successfully sent,
557         // since the user may opt to re-synchronize a session using the session
558         // file record.
559         if (!msg.empty()) {
560                 this->_log(msg);
561                 this->_commitLog();
562         }
564         // send message
566         if (!lm_connection_send(this->session_data->connection, m, &error)) {
567                 g_warning("Send failed: %s", error->message);
568                 lm_message_unref(m);
569                 return CONNECTION_ERROR;
570         }
572         lm_message_unref(m);
573         return SEND_SUCCESS;
576 void
577 SessionManager::connectionError(Glib::ustring const& errmsg)
579         Gtk::MessageDialog dlg(errmsg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
580         dlg.run();
581 //      sp_whiteboard_connect_dialog(const_cast< gchar* >(errmsg));
584 void
585 SessionManager::resendDocument(char const* recipientJID, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf)
587         Glib::ustring docbegin = MessageUtilities::makeTagWithContent(MESSAGE_DOCBEGIN, "");
588         this->sendChange(docbegin, DOCUMENT_BEGIN, recipientJID, false);
590         Inkscape::XML::Node* root = sp_document_repr_root(this->_myDoc);
592         if(root == NULL) {
593                 return;
594     }
596         NewChildObjectMessageList newchildren;
597         MessageAggregator& agg = MessageAggregator::instance();
598         Glib::ustring buf;
600     for ( Inkscape::XML::Node *child = root->firstChild() ; child != NULL ; child = child->next() ) {
601                 // TODO: replace with Serializer methods
602                 MessageUtilities::newObjectMessage(&buf, newidsbuf, newnodesbuf, newchildren, this->_myTracker, child);
604                 NewChildObjectMessageList::iterator j = newchildren.begin();
605                 Glib::ustring aggbuf;
607                 for(; j != newchildren.end(); j++) {
608                         if (!agg.addOne(*j, aggbuf)) {
609                                 this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
610                                 aggbuf.clear();
611                                 agg.addOne(*j, aggbuf);
612                         }
613                 }
615                 // send remaining changes
616                 if (!aggbuf.empty()) {
617                         this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
618                         aggbuf.clear();
619                 }
621                 newchildren.clear();
622                 buf.clear();
623     }
625         Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_COMMIT, "");
626         this->sendChange(commit, CHANGE_COMMIT, recipientJID, false);
627         Glib::ustring docend = MessageUtilities::makeTagWithContent(MESSAGE_DOCEND, "");
628         this->sendChange(docend, DOCUMENT_END, recipientJID, false);
631 void
632 SessionManager::receiveChange(Glib::ustring const& changemsg)
635         struct Node part;
637         Glib::ustring msgcopy = changemsg.c_str();
640         while(MessageUtilities::getFirstMessageTag(part, msgcopy) != false) {
641                 // TODO:
642                 // Yikes.  This is ugly.
643                 if (part.tag == MESSAGE_CHANGE) {
644                         this->_myDeserializer->deserializeEventChgAttr(part.data);
645                         msgcopy.erase(0, part.next_pos);
647                 } else if (part.tag == MESSAGE_NEWOBJ) {
648                         this->_myDeserializer->deserializeEventAdd(part.data);
649                         msgcopy.erase(0, part.next_pos);
651                 } else if (part.tag == MESSAGE_DELETE) {
652                         this->_myDeserializer->deserializeEventDel(part.data);
653                         msgcopy.erase(0, part.next_pos);
655                 } else if (part.tag == MESSAGE_DOCUMENT) {
656                         // no special handler, just keep going with the rest of the message
657                         msgcopy.erase(0, part.next_pos);
659                 } else if (part.tag == MESSAGE_NODECONTENT) {
660                         this->_myDeserializer->deserializeEventChgContent(part.data);
661                         msgcopy.erase(0, part.next_pos);
663                 } else if (part.tag == MESSAGE_ORDERCHANGE) {
664                         this->_myDeserializer->deserializeEventChgOrder(part.data);
665                         msgcopy.erase(0, part.next_pos);
667                 } else if (part.tag == MESSAGE_COMMIT) {
668                         // Retrieve the deserialized event log, node actions, and nodes with updated attributes
669                         XML::Event* log = this->_myDeserializer->getEventLog();
670                         KeyToNodeActionList& node_changes = this->_myDeserializer->getNodeTrackerActions();
671                         AttributesUpdatedSet& updated = this->_myDeserializer->getUpdatedAttributeNodeSet();
673                         // Make document insensitive to undo
674                         gboolean saved = sp_document_get_undo_sensitive(this->_myDoc);
675                         sp_document_set_undo_sensitive(this->_myDoc, FALSE);
677                         // Replay the log and push it onto the undo stack
678                         sp_repr_replay_log(log);
680                         // Call updateRepr on changed nodes
681                         // This is required for some tools to function properly, i.e. text tool
682                         // (TODO: we don't need to update _all_ changed nodes, just their parents)
683                         AttributesUpdatedSet::iterator i = updated.begin();
684                         for(; i != updated.end(); i++) {
685                                 SPObject* updated = this->_myDoc->getObjectByRepr(*i);
686                                 if (updated) {
687                                         updated->updateRepr();
688                                 }
689                         }
691                         // merge the events generated by updateRepr
692                         sp_repr_coalesce_log(this->_myDoc->priv->partial, log);
693                         this->_myDoc->priv->partial = NULL;
695                         this->_myDoc->priv->undo = g_slist_prepend(this->_myDoc->priv->undo, log);
697                         // Restore undo sensitivity
698                         sp_document_set_undo_sensitive(this->_myDoc, saved);
700                         // Add or delete nodes to/from the tracker
701                         this->_myTracker->process(node_changes);
703                         // Reset deserializer state
704                         this->_myDeserializer->reset();
705                         break;
707                 } else if (part.tag == MESSAGE_UNDO) {
708                         this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::UNDO_EVENT);
709                         sp_document_undo(this->_myDoc);
710                         this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::UNDO_EVENT);
711                         msgcopy.erase(0, part.next_pos);
713                 } else if (part.tag == MESSAGE_REDO) {
714                         this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::REDO_EVENT);
715                         sp_document_redo(this->_myDoc);
716                         this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::REDO_EVENT);
717                         msgcopy.erase(0, part.next_pos);
719                 } else if (part.tag == MESSAGE_DOCBEGIN) {
720                         msgcopy.erase(0, part.next_pos);
722                 } else if (part.tag == MESSAGE_DOCEND) {
723                         // Set this to be the new original state of the document
724                         sp_document_done(this->document());
725                         sp_document_clear_redo(this->document());
726                         sp_document_clear_undo(this->document());
727                         this->setupCommitListener();
728                         msgcopy.erase(0, part.next_pos);
730                 } else {
731                         msgcopy.erase(0, part.next_pos);
733                 }
734         }
736         this->_log(changemsg);
738         this->_commitLog();
741 bool
742 SessionManager::isPlayingSessionFile()
744         return this->session_data->status[PLAYING_SESSION_FILE];
747 void
748 SessionManager::loadSessionFile(Glib::ustring filename)
750         if (!this->session_data || !this->session_data->status[IN_WHITEBOARD]) {
751                 try {
752                         if (this->_mySessionFile) {
753                                 delete this->_mySessionFile;
754                         }
755                         this->_mySessionFile = new SessionFile(filename, true, false);
757                         // Initialize objects needed for session playback
758                         if (this->_mySessionPlayer == NULL) {
759                                 this->_mySessionPlayer = new SessionFilePlayer(16, this);
760                         } else {
761                                 this->_mySessionPlayer->load(this->_mySessionFile);
762                         }
764                         if (this->_myTracker == NULL) {
765                                 this->_myTracker = new XMLNodeTracker(this);
766                         } else {
767                                 this->_myTracker->reset();
768                         }
770                         if (!this->session_data) {
771                                 this->session_data = new SessionData(this);
772                         }
774                         if (!this->_myDeserializer) {
775                                 this->_myDeserializer = new Deserializer(this->node_tracker());
776                         }
778                         if (!this->_myUndoObserver) {
779                                 this->setupCommitListener();
780                         }
782                         this->session_data->status.set(PLAYING_SESSION_FILE, 1);
785                 } catch (Glib::FileError e) {
786                         g_warning("Could not load session file: %s", e.what().data());
787                 }
788         }
791 void
792 SessionManager::userConnectedToWhiteboard(gchar const* JID)
794         sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("Established whiteboard session with <b>%s</b>."), JID);
798 void
799 SessionManager::userDisconnectedFromWhiteboard(std::string const& JID)
802         sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("<b>%s</b> has <b>left</b> the whiteboard session."), JID.c_str());
804         // Inform the user
805         // TRANSLATORS: %1 is the name of the user that disconnected, %2 is the name of the user whom the disconnected user disconnected from.
806         // This message is not used in a chatroom context.
807         Glib::ustring primary = String::ucompose(_("<span weight=\"bold\" size=\"larger\">The user <b>%1</b> has left the whiteboard session.</span>\n\n"), JID);
808         // TRANSLATORS: %1 and %2 are userids
809         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, lm_connection_get_jid(this->session_data->connection));
811         // TODO: parent this dialog to the active desktop
812         Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false);
813         /*
814         dialog.set_message(primary, true);
815         dialog.set_secondary_text(secondary, true);
816         */
817         dialog.run();
820 void
821 SessionManager::startSendQueueDispatch()
823         this->_send_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchSendQueue), SEND_TIMEOUT);
826 void
827 SessionManager::stopSendQueueDispatch()
829         if (this->_send_queue_dispatcher) {
830                 this->_send_queue_dispatcher.disconnect();
831         }
834 void
835 SessionManager::startReceiveQueueDispatch()
837         this->_receive_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchReceiveQueue), SEND_TIMEOUT);
840 void
841 SessionManager::stopReceiveQueueDispatch()
843         if (this->_receive_queue_dispatcher) {
844                 this->_receive_queue_dispatcher.disconnect();
845         }
848 void
849 SessionManager::clearDocument()
851         // clear all layers, definitions, and metadata
852         XML::Node* rroot = this->_myDoc->rroot;
854         // clear definitions
855         XML::Node* defs = SP_OBJECT_REPR((SPDefs*)SP_DOCUMENT_DEFS(this->_myDoc));
856         g_assert(SP_ROOT(this->_myDoc->root)->defs);
858         for(XML::Node* child = defs->firstChild(); child; child = child->next()) {
859                 defs->removeChild(child);
860         }
862         // clear layers
863         for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
864                 if (strcmp(child->name(), "svg:g") == 0) {
865                         rroot->removeChild(child);
866                 }
867         }
869         // clear metadata
870         for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
871                 if (strcmp(child->name(), "svg:metadata") == 0) {
872                         rroot->removeChild(child);
873                 }
874         }
876 //      sp_document_done(this->_myDoc);
879 void
880 SessionManager::setupInkscapeInterface()
882         this->session_data->status.set(IN_WHITEBOARD, 1);
883         this->startSendQueueDispatch();
884         this->startReceiveQueueDispatch();
885         if (!this->_myTracker) {
886                 this->_myTracker = new XMLNodeTracker(this);
887         }
889         this->_mySerializer = new Serializer(this->node_tracker());
890         this->_myDeserializer = new Deserializer(this->node_tracker());
892         // We're in a whiteboard session now, so set verb sensitivity accordingly
893         this->_setVerbSensitivity(ESTABLISHED_SESSION);
896 void
897 SessionManager::setupCommitListener()
899         this->_myUndoObserver = new Whiteboard::UndoStackObserver(this);
900         this->_myDoc->addUndoObserver(*this->_myUndoObserver);
903 ::SPDesktop*
904 SessionManager::desktop()
906         return this->_myDesktop;
909 ::SPDocument*
910 SessionManager::document()
912         return this->_myDoc;
915 Callbacks*
916 SessionManager::callbacks()
918         return this->_myCallbacks;
921 Whiteboard::UndoStackObserver*
922 SessionManager::undo_stack_observer()
924         return this->_myUndoObserver;
927 Serializer*
928 SessionManager::serializer()
930         return this->_mySerializer;
933 XMLNodeTracker*
934 SessionManager::node_tracker()
936         return this->_myTracker;
940 ChatMessageHandler*
941 SessionManager::chat_handler()
943         return this->_myChatHandler;
946 SessionFilePlayer*
947 SessionManager::session_player()
949         return this->_mySessionPlayer;
952 SessionFile*
953 SessionManager::session_file()
955         return this->_mySessionFile;
958 void
959 SessionManager::_log(Glib::ustring const& message)
961         if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
962                 this->_mySessionFile->addMessage(message);
963         }
966 void
967 SessionManager::_commitLog()
969         if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
970                 this->_mySessionFile->commit();
971         }
974 void
975 SessionManager::_closeLog()
977         if (this->_mySessionFile) {
978                 this->_mySessionFile->close();
979         }
982 void
983 SessionManager::startLog(Glib::ustring filename)
985         try {
986                 this->_mySessionFile = new SessionFile(filename, false, false);
987         } catch (Glib::FileError e) {
988                 g_warning("Caught I/O error %s while attemping to open file %s for session recording.", e.what().c_str(), filename.c_str());
989                 throw;
990         }
993 void
994 SessionManager::_tryToStartLog()
996         if (this->session_data) {
997                 if (!this->session_data->sessionFile.empty()) {
998                         bool undecided = true;
999                         while(undecided) {
1000                                 try {
1001                                         this->startLog(this->session_data->sessionFile);
1002                                         undecided = false;
1003                                 } catch (Glib::FileError e) {
1004                                         undecided = true;
1005                                         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());
1006                                         Gtk::MessageDialog dlg(msg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE, false);
1007                                         dlg.add_button(_("Choose a different location"), 0);
1008                                         dlg.add_button(_("Skip session recording"), 1);
1009                                         switch (dlg.run()) {
1010                                                 case 0:
1011                                                 {
1012                                                         Gtk::FileChooserDialog sessionfiledlg(_("Select a location and filename"), Gtk::FILE_CHOOSER_ACTION_SAVE);
1013                                                         sessionfiledlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
1014                                                         sessionfiledlg.add_button(_("Set filename"), Gtk::RESPONSE_OK);
1015                                                         int result = sessionfiledlg.run();
1016                                                         switch (result) {
1017                                                                 case Gtk::RESPONSE_OK:
1018                                                                 {
1019                                                                         this->session_data->sessionFile = sessionfiledlg.get_filename();
1020                                                                         break;
1021                                                                 }
1022                                                                 case Gtk::RESPONSE_CANCEL:
1023                                                                 default:
1024                                                                         undecided = false;
1025                                                                         break;
1026                                                         }
1027                                                         break;
1028                                                 }
1029                                                 case 1:
1030                                                 default:
1031                                                         undecided = false;
1032                                                         break;
1033                                         }
1034                                 }
1035                         }
1036                 }
1037         }
1040 void
1041 SessionManager::_setVerbSensitivity(SensitivityMode mode)
1043         return;
1045         switch (mode) {
1046                 case ESTABLISHED_CONNECTION:
1047                         // Upon successful connection, we can disconnect from the server.
1048                         // We can also start sharing a document with a user or chatroom.
1049                         // We cannot, however, connect to a new server without first disconnecting.
1050                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, false);
1051                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, true);
1052                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1053                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1054                         break;
1056                 case ESTABLISHED_SESSION:
1057                         // When we have established a session, we should not permit the user to go and
1058                         // establish another session from the same document without first disconnecting.
1059                         //
1060                         // TODO: Well, actually, we probably _should_, but there's no real reconnection logic just yet.
1061                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1062                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1063                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, true);
1064                         break;
1065                 case DISCONNECTED_FROM_SESSION:
1066                         // Upon disconnecting from a session, we can establish a new session and disconnect
1067                         // from the server, but we cannot disconnect from a session (since we just left it.)
1068                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1069                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1070                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1072                 case INITIAL:
1073                 default:
1074                         // Upon construction, there is no active connection, so we cannot do the following:
1075                         // (1) disconnect from a session
1076                         // (2) disconnect from a server
1077                         // (3) share with a user 
1078                         // (4) share with a chatroom
1079                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, true);
1080                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1081                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1082                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1083                         Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, false);
1084                         break;
1085         };
1088 /*
1089 void
1090 SessionManager::Listener::processXmppEvent(Pedro::XmppEvent const& event)
1092         int type = event.getType();
1094         switch (type) {
1095                 case Pedro::XmppEvent::EVENT_STATUS:
1096                         break;
1097                 case Pedro::XmppEvent::EVENT_ERROR:
1098                         break;
1099                 case Pedro::XmppEvent::EVENT_CONNECTED:
1100                         break;
1101                 case Pedro::XmppEvent::EVENT_DISCONNECTED:
1102                         break;
1103                 case Pedro::XmppEvent::EVENT_MESSAGE:
1104                         break;
1105                 case Pedro::XmppEvent::EVENT_PRESENCE:
1106                         break;
1107                 case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
1108                         break;
1109                 case Pedro::XmppEvent::EVENT_MUC_JOIN:
1110                         break;
1111                 case Pedro::XmppEvent::EVENT_MUC_LEAVE:
1112                         break;
1113                 case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
1114                         break;
1115         }
1117 */
1124 /*
1125   Local Variables:
1126   mode:c++
1127   c-file-style:"stroustrup"
1128   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1129   indent-tabs-mode:nil
1130   fill-column:99
1131   End:
1132 */
1133 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :