diff options
| author | MenTaLguY <mental@rydia.net> | 2006-01-16 02:36:01 +0000 |
|---|---|---|
| committer | mental <mental@users.sourceforge.net> | 2006-01-16 02:36:01 +0000 |
| commit | 179fa413b047bede6e32109e2ce82437c5fb8d34 (patch) | |
| tree | a5a6ac2c1708bd02288fbd8edb2ff500ff2e0916 /src/jabber_whiteboard/session-manager.cpp | |
| download | inkscape-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.cpp | 1118 |
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 : |
