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