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