summaryrefslogtreecommitdiffstats
path: root/src/jabber_whiteboard/session-manager.cpp
diff options
context:
space:
mode:
authorMenTaLguY <mental@rydia.net>2006-01-16 02:36:01 +0000
committermental <mental@users.sourceforge.net>2006-01-16 02:36:01 +0000
commit179fa413b047bede6e32109e2ce82437c5fb8d34 (patch)
treea5a6ac2c1708bd02288fbd8edb2ff500ff2e0916 /src/jabber_whiteboard/session-manager.cpp
downloadinkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.tar.gz
inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.zip
moving trunk for module inkscape
(bzr r1)
Diffstat (limited to 'src/jabber_whiteboard/session-manager.cpp')
-rw-r--r--src/jabber_whiteboard/session-manager.cpp1118
1 files changed, 1118 insertions, 0 deletions
diff --git a/src/jabber_whiteboard/session-manager.cpp b/src/jabber_whiteboard/session-manager.cpp
new file mode 100644
index 000000000..a52a7a878
--- /dev/null
+++ b/src/jabber_whiteboard/session-manager.cpp
@@ -0,0 +1,1118 @@
+/**
+ * Whiteboard session manager
+ *
+ * Authors:
+ * David Yip <yipdw@rose-hulman.edu>
+ *
+ * Copyright (c) 2005 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+/*
+#include "inkscape.h"
+*/
+
+#include <cstring>
+
+#include <glibmm/i18n.h>
+#include <gtkmm/dialog.h>
+#include <gtkmm/messagedialog.h>
+#include <gtkmm/filechooserdialog.h>
+#include <gtkmm/stock.h>
+
+#include "gc-anchored.h"
+
+#include "xml/repr.h"
+#include "xml/node-observer.h"
+
+#include "util/ucompose.hpp"
+
+#include "message-context.h"
+#include "message-stack.h"
+#include "desktop-handles.h"
+#include "document.h"
+#include "document-private.h"
+#include "verbs.h"
+
+#include "jabber_whiteboard/defines.h"
+#include "jabber_whiteboard/typedefs.h"
+#include "jabber_whiteboard/deserializer.h"
+#include "jabber_whiteboard/message-utilities.h"
+#include "jabber_whiteboard/message-handler.h"
+#include "jabber_whiteboard/node-tracker.h"
+#include "jabber_whiteboard/jabber-handlers.h"
+#include "jabber_whiteboard/callbacks.h"
+#include "jabber_whiteboard/chat-handler.h"
+#include "jabber_whiteboard/session-file.h"
+#include "jabber_whiteboard/session-file-player.h"
+#include "jabber_whiteboard/session-manager.h"
+#include "jabber_whiteboard/message-aggregator.h"
+#include "jabber_whiteboard/undo-stack-observer.h"
+#include "jabber_whiteboard/serializer.h"
+
+//#include "jabber_whiteboard/pedro/pedroxmpp.h"
+
+#include "jabber_whiteboard/message-node.h"
+#include "jabber_whiteboard/message-queue.h"
+
+namespace Inkscape {
+
+namespace Whiteboard {
+
+#ifdef WIN32
+static bool lm_initialize_called = false;
+#endif
+
+SessionData::SessionData(SessionManager *sm)
+{
+ this->_sm = sm;
+ this->recipient = NULL;
+ this->connection = NULL;
+ this->ssl = NULL;
+ this->ignoreFurtherSSLErrors = false;
+ this->send_queue = new SendMessageQueue(sm);
+ this->sequence_number = 1;
+}
+
+SessionData::~SessionData()
+{
+ this->receive_queues.clear();
+
+ if (this->send_queue) {
+ delete this->send_queue;
+ }
+}
+
+SessionManager::SessionManager(::SPDesktop *desktop)
+{
+
+ // Initialize private members to NULL to facilitate deletion in destructor
+ this->_myDoc = NULL;
+ this->session_data = NULL;
+ this->_myCallbacks = NULL;
+ this->_myTracker = NULL;
+ this->_myChatHandler = NULL;
+ this->_mySessionFile = NULL;
+ this->_mySessionPlayer = NULL;
+ this->_myMessageHandler = NULL;
+ this->_myUndoObserver = NULL;
+ this->_mySerializer = NULL;
+ this->_myDeserializer = NULL;
+
+ this->setDesktop(desktop);
+
+ if (this->_myDoc == NULL) {
+ g_error("Initializing SessionManager on null document object!");
+ }
+
+
+#ifdef WIN32
+ //# lm_initialize() must be called before any network code
+ if (!lm_initialize_called) {
+ lm_initialize();
+ lm_initialize_called = true;
+ }
+#endif
+
+ this->_setVerbSensitivity(INITIAL);
+}
+
+SessionManager::~SessionManager()
+{
+
+ if (this->session_data) {
+ if (this->session_data->status[IN_WHITEBOARD]) {
+ // also calls closeSession
+ this->disconnectFromDocument();
+ }
+ this->disconnectFromServer();
+
+ if (this->session_data->status[LOGGED_IN]) {
+ // TODO: unref message handlers
+ }
+ }
+
+ if (this->_mySessionFile) {
+ delete this->_mySessionPlayer;
+ delete this->_mySessionFile;
+ }
+
+ delete this->_myChatHandler;
+
+
+ // Deletion of _myTracker is done in closeSession;
+ // no need to do it here.
+
+ // Deletion is handled separately from session teardown and server disconnection
+ // because some teardown methods (e.g. closeSession) require access to members that we will
+ // be deleting. Separating deletion from teardown means that we do not have
+ // to worry (as much) about proper ordering of the teardown sequence. (We still need
+ // to ensure that destructors in each object being deleted have access to all the
+ // members they need, though.)
+
+ // Stop dispatchers
+ if (this->_myCallbacks) {
+ this->stopSendQueueDispatch();
+ this->stopReceiveQueueDispatch();
+ delete this->_myCallbacks;
+ }
+
+ delete this->_myMessageHandler;
+
+ delete this->session_data;
+
+ Inkscape::GC::release(this->_myDoc);
+
+}
+
+void
+SessionManager::setDesktop(::SPDesktop* desktop)
+{
+ this->_myDesktop = desktop;
+ if (this->_myDoc != NULL) {
+ Inkscape::GC::release(this->_myDoc);
+ }
+ if (SP_DT_DOCUMENT(desktop) != NULL) {
+ this->_myDoc = SP_DT_DOCUMENT(desktop);
+ Inkscape::GC::anchor(this->_myDoc);
+ }
+}
+
+int
+SessionManager::connectToServer(Glib::ustring const& server, Glib::ustring const& port, Glib::ustring const& username, Glib::ustring const& pw, bool usessl)
+{
+ GError* error = NULL;
+ Glib::ustring jid;
+
+ // JID format is username@server/resource
+ jid += username + "@" + server + "/" + RESOURCE_NAME;
+
+ LmMessage* m;
+ LmMessageHandler* mh;
+
+ if (!this->session_data) {
+ this->session_data = new SessionData(this);
+ }
+
+ if (!this->_myMessageHandler) {
+ this->_myMessageHandler = new MessageHandler(this);
+ }
+
+ // Connect to server
+ // We need to check to see if this object already exists, because
+ // the user may be reusing an old connection that failed due to e.g.
+ // authentication failure.
+ if (this->session_data->connection) {
+ lm_connection_close(this->session_data->connection, &error);
+ lm_connection_unref(this->session_data->connection);
+ }
+
+ this->session_data->connection = lm_connection_new(server.c_str());
+
+ lm_connection_set_port(this->session_data->connection, atoi(port.c_str()));
+
+ if (usessl) {
+ if (lm_ssl_is_supported()) {
+ this->session_data->ssl = lm_ssl_new(NULL, ssl_error_handler, reinterpret_cast< gpointer >(this), NULL);
+
+ lm_ssl_ref(this->session_data->ssl);
+ } else {
+ return SSL_INITIALIZATION_ERROR;
+ }
+ lm_connection_set_ssl(this->session_data->connection, this->session_data->ssl);
+ }
+
+ // Send authorization
+ lm_connection_set_jid(this->session_data->connection, jid.c_str());
+
+ // TODO:
+ // Asynchronous connection and authentication would be nice,
+ // but it's a huge mess of mixing C callbacks and C++ method calls.
+ // I've tried to do it and only managed to severely destabilize the Jabber
+ // server connection routines.
+ //
+ // This, of course, is an invitation to anyone more capable than me
+ // to convert this from synchronous to asynchronous Loudmouth calls.
+ if (!lm_connection_open_and_block(this->session_data->connection, &error)) {
+ if (error != NULL) {
+ g_warning("Failed to open: %s", error->message);
+ }
+ return FAILED_TO_CONNECT;
+ }
+
+ // Authenticate
+ if (!lm_connection_authenticate_and_block(this->session_data->connection, username.c_str(), pw.c_str(), RESOURCE_NAME, &error)) {
+ if (error != NULL) {
+ g_warning("Failed to authenticate: %s", error->message);
+ }
+ lm_connection_close(this->session_data->connection, NULL);
+ lm_connection_unref(this->session_data->connection);
+ this->session_data->connection = NULL;
+ return INVALID_AUTH;
+ }
+
+ // Register message handler for presence messages
+ mh = lm_message_handler_new((LmHandleMessageFunction)presence_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
+ lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_PRESENCE, LM_HANDLER_PRIORITY_NORMAL);
+
+ // Register message handler for stream error messages
+ mh = lm_message_handler_new((LmHandleMessageFunction)stream_error_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
+ lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_STREAM_ERROR, LM_HANDLER_PRIORITY_NORMAL);
+
+ // Register message handler for chat messages
+ mh = lm_message_handler_new((LmHandleMessageFunction)default_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
+ lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_MESSAGE, LM_HANDLER_PRIORITY_NORMAL);
+
+ // Send presence message to server
+ m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_NOT_SET);
+ if (!lm_connection_send(this->session_data->connection, m, &error)) {
+ if (error != NULL) {
+ g_warning("Presence message could not be sent: %s", error->message);
+ }
+ lm_connection_close(this->session_data->connection, NULL);
+ lm_connection_unref(this->session_data->connection);
+ this->session_data->connection = NULL;
+ return FAILED_TO_CONNECT;
+ }
+
+ this->session_data->status.set(LOGGED_IN, 1);
+
+ this->_myCallbacks = new Callbacks(this);
+
+ lm_message_unref(m);
+
+ this->_setVerbSensitivity(ESTABLISHED_CONNECTION);
+
+ return CONNECT_SUCCESS;
+}
+
+LmSSLResponse
+SessionManager::handleSSLError(LmSSL* ssl, LmSSLStatus status)
+{
+ if (this->session_data->ignoreFurtherSSLErrors) {
+ return LM_SSL_RESPONSE_CONTINUE;
+ }
+
+ Glib::ustring msg;
+
+ // TODO: It'd be nice to provide the user with additional information in some cases,
+ // like fingerprints, hostname, etc.
+ switch(status) {
+ case LM_SSL_STATUS_NO_CERT_FOUND:
+ msg = _("No SSL certificate was found.");
+ break;
+ case LM_SSL_STATUS_UNTRUSTED_CERT:
+ msg = _("The SSL certificate provided by the Jabber server is untrusted.");
+ break;
+ case LM_SSL_STATUS_CERT_EXPIRED:
+ msg = _("The SSL certificate provided by the Jabber server is expired.");
+ break;
+ case LM_SSL_STATUS_CERT_NOT_ACTIVATED:
+ msg = _("The SSL certificate provided by the Jabber server has not been activated.");
+ break;
+ case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:
+ msg = _("The SSL certificate provided by the Jabber server contains a hostname that does not match the Jabber server's hostname.");
+ break;
+ case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH:
+ msg = _("The SSL certificate provided by the Jabber server contains an invalid fingerprint.");
+ break;
+ case LM_SSL_STATUS_GENERIC_ERROR:
+ msg = _("An unknown error occurred while setting up the SSL connection.");
+ break;
+ }
+
+ // TRANSLATORS: %1 is the message that describes the specific error that occurred when
+ // establishing the SSL connection.
+ Glib::ustring mainmsg = String::ucompose(_("<span weight=\"bold\" size=\"larger\">%1</span>\n\nDo you wish to continue connecting to the Jabber server?"), msg);
+
+ Gtk::MessageDialog dlg(mainmsg, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, false);
+ dlg.add_button(_("Continue connecting and ignore further errors"), 0);
+ dlg.add_button(_("Continue connecting, but warn me of further errors"), 1);
+ dlg.add_button(_("Cancel connection"), 2);
+
+ switch(dlg.run()) {
+ case 0:
+ this->session_data->ignoreFurtherSSLErrors = true;
+ /* FALL-THROUGH */
+ case 1:
+ return LM_SSL_RESPONSE_CONTINUE;
+
+ default:
+ return LM_SSL_RESPONSE_STOP;
+ }
+}
+
+void
+SessionManager::disconnectFromServer()
+{
+ if (this->session_data->connection) {
+ GError* error = NULL;
+
+ LmMessage *m;
+ this->disconnectFromDocument();
+ m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_UNAVAILABLE);
+ if (!lm_connection_send(this->session_data->connection, m, &error)) {
+ g_warning("Could not send unavailable presence message: %s", error->message);
+ }
+
+ lm_message_unref(m);
+ lm_connection_close(this->session_data->connection, NULL);
+ lm_connection_unref(this->session_data->connection);
+ if (this->session_data->ssl) {
+ lm_ssl_unref(this->session_data->ssl);
+ }
+
+ this->session_data->connection = NULL;
+ this->session_data->ssl = NULL;
+ this->_setVerbSensitivity(INITIAL);
+ }
+}
+
+void
+SessionManager::disconnectFromDocument()
+{
+ if (this->session_data->status[IN_WHITEBOARD] || !this->session_data->status[IN_CHATROOM]) {
+ this->sendMessage(DISCONNECTED_FROM_USER_SIGNAL, 0, "", this->session_data->recipient, false);
+ }
+ this->closeSession();
+ this->_setVerbSensitivity(DISCONNECTED_FROM_SESSION);
+}
+
+void
+SessionManager::closeSession()
+{
+
+ if (this->session_data->status[IN_WHITEBOARD]) {
+ this->session_data->status.set(IN_WHITEBOARD, 0);
+ this->session_data->receive_queues.clear();
+ this->session_data->send_queue->clear();
+ this->stopSendQueueDispatch();
+ this->stopReceiveQueueDispatch();
+ }
+
+ if (this->_myUndoObserver) {
+ this->_myDoc->removeUndoObserver(*this->_myUndoObserver);
+ }
+
+ delete this->_myUndoObserver;
+ delete this->_mySerializer;
+ delete this->_myDeserializer;
+
+ this->_myUndoObserver = NULL;
+ this->_mySerializer = NULL;
+ this->_myDeserializer = NULL;
+
+ if (this->_myTracker) {
+ delete this->_myTracker;
+ this->_myTracker = NULL;
+ }
+
+
+ this->setRecipient(NULL);
+}
+
+void
+SessionManager::setRecipient(char const* recipientJID)
+{
+ if (this->session_data->recipient) {
+ free(const_cast< gchar* >(this->session_data->recipient));
+ }
+
+ if (recipientJID == NULL) {
+ this->session_data->recipient = NULL;
+ } else {
+ this->session_data->recipient = g_strdup(recipientJID);
+ }
+}
+
+void
+SessionManager::sendChange(Glib::ustring const& msg, MessageType type, std::string const& recipientJID, bool chatroom)
+{
+ if (!this->session_data->status[IN_WHITEBOARD]) {
+ return;
+ }
+
+ std::string& recipient = const_cast< std::string& >(recipientJID);
+ if (recipient.empty()) {
+ recipient = this->session_data->recipient;
+ }
+
+
+ switch (type) {
+ case DOCUMENT_BEGIN:
+ case DOCUMENT_END:
+ case CHANGE_NOT_REPEATABLE:
+ case CHANGE_REPEATABLE:
+ case CHANGE_COMMIT:
+ {
+ MessageNode *newNode = new MessageNode(this->session_data->sequence_number++, lm_connection_get_jid(this->session_data->connection), recipient, msg, type, false, chatroom);
+ this->session_data->send_queue->insert(newNode);
+ Inkscape::GC::release(newNode);
+ break;
+ }
+ default:
+ g_warning("Cannot insert MessageNode with unknown change type into send queue; discarding message. This may lead to desynchronization!");
+ break;
+ }
+}
+
+
+// FIXME:
+// This method needs a massive, massive, massive overhaul.
+int
+SessionManager::sendMessage(MessageType msgtype, unsigned int sequence, Glib::ustring const& msg, char const* recipientJID, bool chatroom)
+{
+ LmMessage* m;
+ GError* error = NULL;
+ char* type, * seq;
+
+ if (recipientJID == NULL || recipientJID == "") {
+ g_warning("Null recipient JID specified; not sending message.");
+ return NO_RECIPIENT_JID;
+ } else {
+ }
+
+ // create message
+ m = lm_message_new(recipientJID, LM_MESSAGE_TYPE_MESSAGE);
+
+ // add sender
+ lm_message_node_set_attribute(m->node, "from", lm_connection_get_jid(this->session_data->connection));
+
+ // set message subtype according to whether or not this is
+ // destined for a chatroom
+ if (chatroom) {
+ lm_message_node_set_attribute(m->node, "type", "groupchat");
+ } else {
+ lm_message_node_set_attribute(m->node, "type", "chat");
+ }
+
+ // set protocol version;
+ // we are currently fixed at version 1
+ lm_message_node_add_child(m->node, MESSAGE_PROTOCOL_VER, MESSAGE_PROTOCOL_V1);
+
+ // add message type
+ type = (char *)calloc(TYPE_FIELD_SIZE, sizeof(char));
+ snprintf(type, TYPE_FIELD_SIZE, "%i", msgtype);
+ lm_message_node_add_child(m->node, MESSAGE_TYPE, type);
+ free(type);
+
+ // add message body
+ if (!msg.empty()) {
+ lm_message_node_add_child(m->node, MESSAGE_BODY, msg.c_str());
+ } else {
+ }
+
+ // add sequence number
+ switch(msgtype) {
+ case CHANGE_REPEATABLE:
+ case CHANGE_NOT_REPEATABLE:
+ case DUMMY_CHANGE:
+ case CHANGE_COMMIT:
+ case DOCUMENT_BEGIN:
+ case DOCUMENT_END:
+ case CONNECT_REQUEST_RESPONSE_CHAT:
+ case CONNECT_REQUEST_RESPONSE_USER:
+ case CHATROOM_SYNCHRONIZE_RESPONSE:
+ seq = (char* )calloc(SEQNUM_FIELD_SIZE, sizeof(char));
+ sprintf(seq, "%u", sequence);
+ lm_message_node_add_child(m->node, MESSAGE_SEQNUM, seq);
+ free(seq);
+ break;
+
+ case CONNECT_REQUEST_USER:
+ case CONNECTED_SIGNAL:
+ case DISCONNECTED_FROM_USER_SIGNAL:
+ break;
+
+ // Error messages and synchronization requests do not need a sequence number
+ case CHATROOM_SYNCHRONIZE_REQUEST:
+ case CONNECT_REQUEST_REFUSED_BY_PEER:
+ case UNSUPPORTED_PROTOCOL_VERSION:
+ case ALREADY_IN_SESSION:
+ break;
+
+ default:
+ g_warning("Outgoing message type not recognized; not sending.");
+ lm_message_unref(m);
+ return UNKNOWN_OUTGOING_TYPE;
+ }
+
+ // We want to log messages even if they were not successfully sent,
+ // since the user may opt to re-synchronize a session using the session
+ // file record.
+ if (!msg.empty()) {
+ this->_log(msg);
+ this->_commitLog();
+ }
+
+ // send message
+
+ if (!lm_connection_send(this->session_data->connection, m, &error)) {
+ g_warning("Send failed: %s", error->message);
+ lm_message_unref(m);
+ return CONNECTION_ERROR;
+ }
+
+ lm_message_unref(m);
+ return SEND_SUCCESS;
+}
+
+void
+SessionManager::connectionError(Glib::ustring const& errmsg)
+{
+ Gtk::MessageDialog dlg(errmsg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
+ dlg.run();
+// sp_whiteboard_connect_dialog(const_cast< gchar* >(errmsg));
+}
+
+void
+SessionManager::resendDocument(char const* recipientJID, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf)
+{
+ Glib::ustring docbegin = MessageUtilities::makeTagWithContent(MESSAGE_DOCBEGIN, "");
+ this->sendChange(docbegin, DOCUMENT_BEGIN, recipientJID, false);
+
+ Inkscape::XML::Node* root = sp_document_repr_root(this->_myDoc);
+
+ if(root == NULL) {
+ return;
+ }
+
+ NewChildObjectMessageList newchildren;
+ MessageAggregator& agg = MessageAggregator::instance();
+ Glib::ustring buf;
+
+ for ( Inkscape::XML::Node *child = root->firstChild() ; child != NULL ; child = child->next() ) {
+ // TODO: replace with Serializer methods
+ MessageUtilities::newObjectMessage(&buf, newidsbuf, newnodesbuf, newchildren, this->_myTracker, child);
+
+ NewChildObjectMessageList::iterator j = newchildren.begin();
+ Glib::ustring aggbuf;
+
+ for(; j != newchildren.end(); j++) {
+ if (!agg.addOne(*j, aggbuf)) {
+ this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
+ aggbuf.clear();
+ agg.addOne(*j, aggbuf);
+ }
+ }
+
+ // send remaining changes
+ if (!aggbuf.empty()) {
+ this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
+ aggbuf.clear();
+ }
+
+ newchildren.clear();
+ buf.clear();
+ }
+
+ Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_COMMIT, "");
+ this->sendChange(commit, CHANGE_COMMIT, recipientJID, false);
+ Glib::ustring docend = MessageUtilities::makeTagWithContent(MESSAGE_DOCEND, "");
+ this->sendChange(docend, DOCUMENT_END, recipientJID, false);
+}
+
+void
+SessionManager::receiveChange(Glib::ustring const& changemsg)
+{
+
+ struct Node part;
+
+ Glib::ustring msgcopy = changemsg.c_str();
+
+
+ while(MessageUtilities::getFirstMessageTag(part, msgcopy) != false) {
+ // TODO:
+ // Yikes. This is ugly.
+ if (part.tag == MESSAGE_CHANGE) {
+ this->_myDeserializer->deserializeEventChgAttr(part.data);
+ msgcopy.erase(0, part.next_pos);
+
+ } else if (part.tag == MESSAGE_NEWOBJ) {
+ this->_myDeserializer->deserializeEventAdd(part.data);
+ msgcopy.erase(0, part.next_pos);
+
+ } else if (part.tag == MESSAGE_DELETE) {
+ this->_myDeserializer->deserializeEventDel(part.data);
+ msgcopy.erase(0, part.next_pos);
+
+ } else if (part.tag == MESSAGE_DOCUMENT) {
+ // no special handler, just keep going with the rest of the message
+ msgcopy.erase(0, part.next_pos);
+
+ } else if (part.tag == MESSAGE_NODECONTENT) {
+ this->_myDeserializer->deserializeEventChgContent(part.data);
+ msgcopy.erase(0, part.next_pos);
+
+ } else if (part.tag == MESSAGE_ORDERCHANGE) {
+ this->_myDeserializer->deserializeEventChgOrder(part.data);
+ msgcopy.erase(0, part.next_pos);
+
+ } else if (part.tag == MESSAGE_COMMIT) {
+ // Retrieve the deserialized event log, node actions, and nodes with updated attributes
+ XML::Event* log = this->_myDeserializer->getEventLog();
+ KeyToNodeActionList& node_changes = this->_myDeserializer->getNodeTrackerActions();
+ AttributesUpdatedSet& updated = this->_myDeserializer->getUpdatedAttributeNodeSet();
+
+ // Make document insensitive to undo
+ gboolean saved = sp_document_get_undo_sensitive(this->_myDoc);
+ sp_document_set_undo_sensitive(this->_myDoc, FALSE);
+
+ // Replay the log and push it onto the undo stack
+ sp_repr_replay_log(log);
+
+ // Call updateRepr on changed nodes
+ // This is required for some tools to function properly, i.e. text tool
+ // (TODO: we don't need to update _all_ changed nodes, just their parents)
+ AttributesUpdatedSet::iterator i = updated.begin();
+ for(; i != updated.end(); i++) {
+ SPObject* updated = this->_myDoc->getObjectByRepr(*i);
+ if (updated) {
+ updated->updateRepr();
+ }
+ }
+
+ // merge the events generated by updateRepr
+ sp_repr_coalesce_log(this->_myDoc->priv->partial, log);
+ this->_myDoc->priv->partial = NULL;
+
+ this->_myDoc->priv->undo = g_slist_prepend(this->_myDoc->priv->undo, log);
+
+ // Restore undo sensitivity
+ sp_document_set_undo_sensitive(this->_myDoc, saved);
+
+ // Add or delete nodes to/from the tracker
+ this->_myTracker->process(node_changes);
+
+ // Reset deserializer state
+ this->_myDeserializer->reset();
+ break;
+
+ } else if (part.tag == MESSAGE_UNDO) {
+ this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::UNDO_EVENT);
+ sp_document_undo(this->_myDoc);
+ this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::UNDO_EVENT);
+ msgcopy.erase(0, part.next_pos);
+
+ } else if (part.tag == MESSAGE_REDO) {
+ this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::REDO_EVENT);
+ sp_document_redo(this->_myDoc);
+ this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::REDO_EVENT);
+ msgcopy.erase(0, part.next_pos);
+
+ } else if (part.tag == MESSAGE_DOCBEGIN) {
+ msgcopy.erase(0, part.next_pos);
+
+ } else if (part.tag == MESSAGE_DOCEND) {
+ // Set this to be the new original state of the document
+ sp_document_done(this->document());
+ sp_document_clear_redo(this->document());
+ sp_document_clear_undo(this->document());
+ this->setupCommitListener();
+ msgcopy.erase(0, part.next_pos);
+
+ } else {
+ msgcopy.erase(0, part.next_pos);
+
+ }
+ }
+
+ this->_log(changemsg);
+
+ this->_commitLog();
+}
+
+bool
+SessionManager::isPlayingSessionFile()
+{
+ return this->session_data->status[PLAYING_SESSION_FILE];
+}
+
+void
+SessionManager::loadSessionFile(Glib::ustring filename)
+{
+ if (!this->session_data || !this->session_data->status[IN_WHITEBOARD]) {
+ try {
+ if (this->_mySessionFile) {
+ delete this->_mySessionFile;
+ }
+ this->_mySessionFile = new SessionFile(filename, true, false);
+
+ // Initialize objects needed for session playback
+ if (this->_mySessionPlayer == NULL) {
+ this->_mySessionPlayer = new SessionFilePlayer(16, this);
+ } else {
+ this->_mySessionPlayer->load(this->_mySessionFile);
+ }
+
+ if (this->_myTracker == NULL) {
+ this->_myTracker = new XMLNodeTracker(this);
+ } else {
+ this->_myTracker->reset();
+ }
+
+ if (!this->session_data) {
+ this->session_data = new SessionData(this);
+ }
+
+ if (!this->_myDeserializer) {
+ this->_myDeserializer = new Deserializer(this->node_tracker());
+ }
+
+ if (!this->_myUndoObserver) {
+ this->setupCommitListener();
+ }
+
+ this->session_data->status.set(PLAYING_SESSION_FILE, 1);
+
+
+ } catch (Glib::FileError e) {
+ g_warning("Could not load session file: %s", e.what().data());
+ }
+ }
+}
+
+void
+SessionManager::userConnectedToWhiteboard(gchar const* JID)
+{
+ SP_DT_MSGSTACK(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("Established whiteboard session with <b>%s</b>."), JID);
+}
+
+
+void
+SessionManager::userDisconnectedFromWhiteboard(std::string const& JID)
+{
+
+ SP_DT_MSGSTACK(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("<b>%s</b> has <b>left</b> the whiteboard session."), JID.c_str());
+
+ // Inform the user
+ // TRANSLATORS: %1 is the name of the user that disconnected, %2 is the name of the user whom the disconnected user disconnected from.
+ // This message is not used in a chatroom context.
+ Glib::ustring primary = String::ucompose(_("<span weight=\"bold\" size=\"larger\">The user <b>%1</b> has left the whiteboard session.</span>\n\n"), JID);
+ // TRANSLATORS: %1 and %2 are userids
+ 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));
+
+ // TODO: parent this dialog to the active desktop
+ Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false);
+ /*
+ dialog.set_message(primary, true);
+ dialog.set_secondary_text(secondary, true);
+ */
+ dialog.run();
+}
+
+void
+SessionManager::startSendQueueDispatch()
+{
+ this->_send_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchSendQueue), SEND_TIMEOUT);
+}
+
+void
+SessionManager::stopSendQueueDispatch()
+{
+ if (this->_send_queue_dispatcher) {
+ this->_send_queue_dispatcher.disconnect();
+ }
+}
+
+void
+SessionManager::startReceiveQueueDispatch()
+{
+ this->_receive_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchReceiveQueue), SEND_TIMEOUT);
+}
+
+void
+SessionManager::stopReceiveQueueDispatch()
+{
+ if (this->_receive_queue_dispatcher) {
+ this->_receive_queue_dispatcher.disconnect();
+ }
+}
+
+void
+SessionManager::clearDocument()
+{
+ // clear all layers, definitions, and metadata
+ XML::Node* rroot = this->_myDoc->rroot;
+
+ // clear definitions
+ XML::Node* defs = SP_OBJECT_REPR((SPDefs*)SP_DOCUMENT_DEFS(this->_myDoc));
+ g_assert(SP_ROOT(this->_myDoc->root)->defs);
+
+ for(XML::Node* child = defs->firstChild(); child; child = child->next()) {
+ defs->removeChild(child);
+ }
+
+ // clear layers
+ for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
+ if (strcmp(child->name(), "svg:g") == 0) {
+ rroot->removeChild(child);
+ }
+ }
+
+ // clear metadata
+ for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
+ if (strcmp(child->name(), "svg:metadata") == 0) {
+ rroot->removeChild(child);
+ }
+ }
+
+// sp_document_done(this->_myDoc);
+}
+
+void
+SessionManager::setupInkscapeInterface()
+{
+ this->session_data->status.set(IN_WHITEBOARD, 1);
+ this->startSendQueueDispatch();
+ this->startReceiveQueueDispatch();
+ if (!this->_myTracker) {
+ this->_myTracker = new XMLNodeTracker(this);
+ }
+
+ this->_mySerializer = new Serializer(this->node_tracker());
+ this->_myDeserializer = new Deserializer(this->node_tracker());
+
+ // We're in a whiteboard session now, so set verb sensitivity accordingly
+ this->_setVerbSensitivity(ESTABLISHED_SESSION);
+}
+
+void
+SessionManager::setupCommitListener()
+{
+ this->_myUndoObserver = new Whiteboard::UndoStackObserver(this);
+ this->_myDoc->addUndoObserver(*this->_myUndoObserver);
+}
+
+::SPDesktop*
+SessionManager::desktop()
+{
+ return this->_myDesktop;
+}
+
+::SPDocument*
+SessionManager::document()
+{
+ return this->_myDoc;
+}
+
+Callbacks*
+SessionManager::callbacks()
+{
+ return this->_myCallbacks;
+}
+
+Whiteboard::UndoStackObserver*
+SessionManager::undo_stack_observer()
+{
+ return this->_myUndoObserver;
+}
+
+Serializer*
+SessionManager::serializer()
+{
+ return this->_mySerializer;
+}
+
+XMLNodeTracker*
+SessionManager::node_tracker()
+{
+ return this->_myTracker;
+}
+
+
+ChatMessageHandler*
+SessionManager::chat_handler()
+{
+ return this->_myChatHandler;
+}
+
+SessionFilePlayer*
+SessionManager::session_player()
+{
+ return this->_mySessionPlayer;
+}
+
+SessionFile*
+SessionManager::session_file()
+{
+ return this->_mySessionFile;
+}
+
+void
+SessionManager::_log(Glib::ustring const& message)
+{
+ if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
+ this->_mySessionFile->addMessage(message);
+ }
+}
+
+void
+SessionManager::_commitLog()
+{
+ if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
+ this->_mySessionFile->commit();
+ }
+}
+
+void
+SessionManager::_closeLog()
+{
+ if (this->_mySessionFile) {
+ this->_mySessionFile->close();
+ }
+}
+
+void
+SessionManager::startLog(Glib::ustring filename)
+{
+ try {
+ this->_mySessionFile = new SessionFile(filename, false, false);
+ } catch (Glib::FileError e) {
+ g_warning("Caught I/O error %s while attemping to open file %s for session recording.", e.what().c_str(), filename.c_str());
+ throw;
+ }
+}
+
+void
+SessionManager::_tryToStartLog()
+{
+ if (this->session_data) {
+ if (!this->session_data->sessionFile.empty()) {
+ bool undecided = true;
+ while(undecided) {
+ try {
+ this->startLog(this->session_data->sessionFile);
+ undecided = false;
+ } catch (Glib::FileError e) {
+ undecided = true;
+ 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());
+ Gtk::MessageDialog dlg(msg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE, false);
+ dlg.add_button(_("Choose a different location"), 0);
+ dlg.add_button(_("Skip session recording"), 1);
+ switch (dlg.run()) {
+ case 0:
+ {
+ Gtk::FileChooserDialog sessionfiledlg(_("Select a location and filename"), Gtk::FILE_CHOOSER_ACTION_SAVE);
+ sessionfiledlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ sessionfiledlg.add_button(_("Set filename"), Gtk::RESPONSE_OK);
+ int result = sessionfiledlg.run();
+ switch (result) {
+ case Gtk::RESPONSE_OK:
+ {
+ this->session_data->sessionFile = sessionfiledlg.get_filename();
+ break;
+ }
+ case Gtk::RESPONSE_CANCEL:
+ default:
+ undecided = false;
+ break;
+ }
+ break;
+ }
+ case 1:
+ default:
+ undecided = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+SessionManager::_setVerbSensitivity(SensitivityMode mode)
+{
+ return;
+
+ switch (mode) {
+ case ESTABLISHED_CONNECTION:
+ // Upon successful connection, we can disconnect from the server.
+ // We can also start sharing a document with a user or chatroom.
+ // We cannot, however, connect to a new server without first disconnecting.
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, false);
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, true);
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
+ break;
+
+ case ESTABLISHED_SESSION:
+ // When we have established a session, we should not permit the user to go and
+ // establish another session from the same document without first disconnecting.
+ //
+ // TODO: Well, actually, we probably _should_, but there's no real reconnection logic just yet.
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, true);
+ break;
+ case DISCONNECTED_FROM_SESSION:
+ // Upon disconnecting from a session, we can establish a new session and disconnect
+ // from the server, but we cannot disconnect from a session (since we just left it.)
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
+
+ case INITIAL:
+ default:
+ // Upon construction, there is no active connection, so we cannot do the following:
+ // (1) disconnect from a session
+ // (2) disconnect from a server
+ // (3) share with a user
+ // (4) share with a chatroom
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, true);
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
+ Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, false);
+ break;
+ };
+}
+
+/*
+void
+SessionManager::Listener::processXmppEvent(Pedro::XmppEvent const& event)
+{
+ int type = event.getType();
+
+ switch (type) {
+ case Pedro::XmppEvent::EVENT_STATUS:
+ break;
+ case Pedro::XmppEvent::EVENT_ERROR:
+ break;
+ case Pedro::XmppEvent::EVENT_CONNECTED:
+ break;
+ case Pedro::XmppEvent::EVENT_DISCONNECTED:
+ break;
+ case Pedro::XmppEvent::EVENT_MESSAGE:
+ break;
+ case Pedro::XmppEvent::EVENT_PRESENCE:
+ break;
+ case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
+ break;
+ case Pedro::XmppEvent::EVENT_MUC_JOIN:
+ break;
+ case Pedro::XmppEvent::EVENT_MUC_LEAVE:
+ break;
+ case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
+ break;
+ }
+}
+*/
+
+}
+
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :