5a7dda4266b5e8f260c46c6ccf21cb7cf7c81afe
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 "xml/repr.h"
27 #include "xml/node-observer.h"
29 #include "util/ucompose.hpp"
31 #include "message-context.h"
32 #include "message-stack.h"
33 #include "desktop-handles.h"
34 #include "document.h"
35 #include "document-private.h"
36 #include "verbs.h"
38 #include "jabber_whiteboard/defines.h"
39 #include "jabber_whiteboard/typedefs.h"
40 #include "jabber_whiteboard/deserializer.h"
41 #include "jabber_whiteboard/message-utilities.h"
42 #include "jabber_whiteboard/message-handler.h"
43 #include "jabber_whiteboard/node-tracker.h"
44 #include "jabber_whiteboard/jabber-handlers.h"
45 #include "jabber_whiteboard/callbacks.h"
46 #include "jabber_whiteboard/chat-handler.h"
47 #include "jabber_whiteboard/session-file.h"
48 #include "jabber_whiteboard/session-file-player.h"
49 #include "jabber_whiteboard/session-manager.h"
50 #include "jabber_whiteboard/message-aggregator.h"
51 #include "jabber_whiteboard/undo-stack-observer.h"
52 #include "jabber_whiteboard/serializer.h"
54 //#include "jabber_whiteboard/pedro/pedroxmpp.h"
56 #include "jabber_whiteboard/message-node.h"
57 #include "jabber_whiteboard/message-queue.h"
59 namespace Inkscape {
61 namespace Whiteboard {
63 SessionData::SessionData(SessionManager *sm)
64 {
65 this->_sm = sm;
66 this->recipient = NULL;
67 this->connection = NULL;
68 this->ssl = NULL;
69 this->ignoreFurtherSSLErrors = false;
70 this->send_queue = new SendMessageQueue(sm);
71 this->sequence_number = 1;
72 }
74 SessionData::~SessionData()
75 {
76 this->receive_queues.clear();
78 if (this->send_queue) {
79 delete this->send_queue;
80 }
81 }
83 SessionManager::SessionManager(::SPDesktop *desktop)
84 {
86 // Initialize private members to NULL to facilitate deletion in destructor
87 this->_myDoc = NULL;
88 this->session_data = NULL;
89 this->_myCallbacks = NULL;
90 this->_myTracker = NULL;
91 this->_myChatHandler = NULL;
92 this->_mySessionFile = NULL;
93 this->_mySessionPlayer = NULL;
94 this->_myMessageHandler = NULL;
95 this->_myUndoObserver = NULL;
96 this->_mySerializer = NULL;
97 this->_myDeserializer = NULL;
99 this->setDesktop(desktop);
101 if (this->_myDoc == NULL) {
102 g_error("Initializing SessionManager on null document object!");
103 }
106 #ifdef WIN32
107 //# lm_initialize() must be called before any network code
108 /*
109 if (!lm_initialize_called) {
110 lm_initialize();
111 lm_initialize_called = true;
112 }
113 */
114 #endif
116 this->_setVerbSensitivity(INITIAL);
117 }
119 SessionManager::~SessionManager()
120 {
122 if (this->session_data) {
123 if (this->session_data->status[IN_WHITEBOARD]) {
124 // also calls closeSession
125 this->disconnectFromDocument();
126 }
127 this->disconnectFromServer();
129 if (this->session_data->status[LOGGED_IN]) {
130 // TODO: unref message handlers
131 }
132 }
134 if (this->_mySessionFile) {
135 delete this->_mySessionPlayer;
136 delete this->_mySessionFile;
137 }
139 delete this->_myChatHandler;
142 // Deletion of _myTracker is done in closeSession;
143 // no need to do it here.
145 // Deletion is handled separately from session teardown and server disconnection
146 // because some teardown methods (e.g. closeSession) require access to members that we will
147 // be deleting. Separating deletion from teardown means that we do not have
148 // to worry (as much) about proper ordering of the teardown sequence. (We still need
149 // to ensure that destructors in each object being deleted have access to all the
150 // members they need, though.)
152 // Stop dispatchers
153 if (this->_myCallbacks) {
154 this->stopSendQueueDispatch();
155 this->stopReceiveQueueDispatch();
156 delete this->_myCallbacks;
157 }
159 delete this->_myMessageHandler;
161 delete this->session_data;
163 Inkscape::GC::release(this->_myDoc);
165 }
167 void
168 SessionManager::setDesktop(::SPDesktop* desktop)
169 {
170 this->_myDesktop = desktop;
171 if (this->_myDoc != NULL) {
172 Inkscape::GC::release(this->_myDoc);
173 }
174 if (sp_desktop_document(desktop) != NULL) {
175 this->_myDoc = sp_desktop_document(desktop);
176 Inkscape::GC::anchor(this->_myDoc);
177 }
178 }
180 int
181 SessionManager::connectToServer(Glib::ustring const& server, Glib::ustring const& port, Glib::ustring const& username, Glib::ustring const& pw, bool usessl)
182 {
183 GError* error = NULL;
184 Glib::ustring jid;
186 // JID format is username@server/resource
187 jid += username + "@" + server + "/" + RESOURCE_NAME;
189 LmMessage* m;
190 LmMessageHandler* mh;
192 if (!this->session_data) {
193 this->session_data = new SessionData(this);
194 }
196 if (!this->_myMessageHandler) {
197 this->_myMessageHandler = new MessageHandler(this);
198 }
200 // Connect to server
201 // We need to check to see if this object already exists, because
202 // the user may be reusing an old connection that failed due to e.g.
203 // authentication failure.
204 if (this->session_data->connection) {
205 lm_connection_close(this->session_data->connection, &error);
206 lm_connection_unref(this->session_data->connection);
207 }
209 this->session_data->connection = lm_connection_new(server.c_str());
211 lm_connection_set_port(this->session_data->connection, atoi(port.c_str()));
213 g_log(NULL, G_LOG_LEVEL_DEBUG, "Opened connection to %s at port %s. Connecting...",
214 server.c_str(), port.c_str());
216 if (usessl) {
217 if (lm_ssl_is_supported()) {
218 this->session_data->ssl = lm_ssl_new(NULL, ssl_error_handler, reinterpret_cast< gpointer >(this), NULL);
220 lm_ssl_ref(this->session_data->ssl);
221 } else {
222 return SSL_INITIALIZATION_ERROR;
223 }
224 lm_connection_set_ssl(this->session_data->connection, this->session_data->ssl);
225 }
227 // Send authorization
228 lm_connection_set_jid(this->session_data->connection, jid.c_str());
230 // TODO:
231 // Asynchronous connection and authentication would be nice,
232 // but it's a huge mess of mixing C callbacks and C++ method calls.
233 // I've tried to do it and only managed to severely destabilize the Jabber
234 // server connection routines.
235 //
236 // This, of course, is an invitation to anyone more capable than me
237 // to convert this from synchronous to asynchronous Loudmouth calls.
238 if (!lm_connection_open_and_block(this->session_data->connection, &error)) {
239 if (error != NULL) {
240 std::cout << "Failed to open: " << error->message << std::endl;
241 }
242 return FAILED_TO_CONNECT;
243 }
245 g_log(NULL, G_LOG_LEVEL_DEBUG, "Opened Loudmouth connection in blocking mode.");
247 // Authenticate
248 if (!lm_connection_authenticate_and_block(this->session_data->connection, username.c_str(), pw.c_str(), RESOURCE_NAME, &error)) {
249 if (error != NULL) {
250 g_warning("Failed to authenticate: %s", error->message);
251 }
252 lm_connection_close(this->session_data->connection, NULL);
253 lm_connection_unref(this->session_data->connection);
254 this->session_data->connection = NULL;
255 return INVALID_AUTH;
256 }
258 g_log(NULL, G_LOG_LEVEL_DEBUG, "Successfully authenticated.");
260 // Register message handler for presence messages
261 mh = lm_message_handler_new((LmHandleMessageFunction)presence_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
262 lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_PRESENCE, LM_HANDLER_PRIORITY_NORMAL);
264 // Register message handler for stream error messages
265 mh = lm_message_handler_new((LmHandleMessageFunction)stream_error_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
266 lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_STREAM_ERROR, LM_HANDLER_PRIORITY_NORMAL);
268 // Register message handler for chat messages
269 mh = lm_message_handler_new((LmHandleMessageFunction)default_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
270 lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_MESSAGE, LM_HANDLER_PRIORITY_NORMAL);
272 // Send presence message to server
273 m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_NOT_SET);
274 if (!lm_connection_send(this->session_data->connection, m, &error)) {
275 if (error != NULL) {
276 g_warning("Presence message could not be sent: %s", error->message);
277 }
278 lm_connection_close(this->session_data->connection, NULL);
279 lm_connection_unref(this->session_data->connection);
280 this->session_data->connection = NULL;
281 return FAILED_TO_CONNECT;
282 }
284 this->session_data->status.set(LOGGED_IN, 1);
286 this->_myCallbacks = new Callbacks(this);
288 lm_message_unref(m);
290 this->_setVerbSensitivity(ESTABLISHED_CONNECTION);
292 return CONNECT_SUCCESS;
293 }
295 LmSSLResponse
296 SessionManager::handleSSLError(LmSSL* ssl, LmSSLStatus status)
297 {
298 if (this->session_data->ignoreFurtherSSLErrors) {
299 return LM_SSL_RESPONSE_CONTINUE;
300 }
302 Glib::ustring msg;
304 // TODO: It'd be nice to provide the user with additional information in some cases,
305 // like fingerprints, hostname, etc.
306 switch(status) {
307 case LM_SSL_STATUS_NO_CERT_FOUND:
308 msg = _("No SSL certificate was found.");
309 break;
310 case LM_SSL_STATUS_UNTRUSTED_CERT:
311 msg = _("The SSL certificate provided by the Jabber server is untrusted.");
312 break;
313 case LM_SSL_STATUS_CERT_EXPIRED:
314 msg = _("The SSL certificate provided by the Jabber server is expired.");
315 break;
316 case LM_SSL_STATUS_CERT_NOT_ACTIVATED:
317 msg = _("The SSL certificate provided by the Jabber server has not been activated.");
318 break;
319 case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:
320 msg = _("The SSL certificate provided by the Jabber server contains a hostname that does not match the Jabber server's hostname.");
321 break;
322 case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH:
323 msg = _("The SSL certificate provided by the Jabber server contains an invalid fingerprint.");
324 break;
325 case LM_SSL_STATUS_GENERIC_ERROR:
326 msg = _("An unknown error occurred while setting up the SSL connection.");
327 break;
328 }
330 // TRANSLATORS: %1 is the message that describes the specific error that occurred when
331 // establishing the SSL connection.
332 Glib::ustring mainmsg = String::ucompose(_("<span weight=\"bold\" size=\"larger\">%1</span>\n\nDo you wish to continue connecting to the Jabber server?"), msg);
334 Gtk::MessageDialog dlg(mainmsg, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, false);
335 dlg.add_button(_("Continue connecting and ignore further errors"), 0);
336 dlg.add_button(_("Continue connecting, but warn me of further errors"), 1);
337 dlg.add_button(_("Cancel connection"), 2);
339 switch(dlg.run()) {
340 case 0:
341 this->session_data->ignoreFurtherSSLErrors = true;
342 /* FALL-THROUGH */
343 case 1:
344 return LM_SSL_RESPONSE_CONTINUE;
346 default:
347 return LM_SSL_RESPONSE_STOP;
348 }
349 }
351 void
352 SessionManager::disconnectFromServer()
353 {
354 if (this->session_data->connection) {
355 GError* error = NULL;
357 LmMessage *m;
358 this->disconnectFromDocument();
359 m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_UNAVAILABLE);
360 if (!lm_connection_send(this->session_data->connection, m, &error)) {
361 g_warning("Could not send unavailable presence message: %s", error->message);
362 }
364 lm_message_unref(m);
365 lm_connection_close(this->session_data->connection, NULL);
366 lm_connection_unref(this->session_data->connection);
367 if (this->session_data->ssl) {
368 lm_ssl_unref(this->session_data->ssl);
369 }
371 this->session_data->connection = NULL;
372 this->session_data->ssl = NULL;
373 this->_setVerbSensitivity(INITIAL);
374 }
375 }
377 void
378 SessionManager::disconnectFromDocument()
379 {
380 if (this->session_data->status[IN_WHITEBOARD] || !this->session_data->status[IN_CHATROOM]) {
381 this->sendMessage(DISCONNECTED_FROM_USER_SIGNAL, 0, "", this->session_data->recipient, false);
382 }
383 this->closeSession();
384 this->_setVerbSensitivity(DISCONNECTED_FROM_SESSION);
385 }
387 void
388 SessionManager::closeSession()
389 {
391 if (this->session_data->status[IN_WHITEBOARD]) {
392 this->session_data->status.set(IN_WHITEBOARD, 0);
393 this->session_data->receive_queues.clear();
394 this->session_data->send_queue->clear();
395 this->stopSendQueueDispatch();
396 this->stopReceiveQueueDispatch();
397 }
399 if (this->_myUndoObserver) {
400 this->_myDoc->removeUndoObserver(*this->_myUndoObserver);
401 }
403 delete this->_myUndoObserver;
404 delete this->_mySerializer;
405 delete this->_myDeserializer;
407 this->_myUndoObserver = NULL;
408 this->_mySerializer = NULL;
409 this->_myDeserializer = NULL;
411 if (this->_myTracker) {
412 delete this->_myTracker;
413 this->_myTracker = NULL;
414 }
417 this->setRecipient(NULL);
418 }
420 void
421 SessionManager::setRecipient(char const* recipientJID)
422 {
423 if (this->session_data->recipient) {
424 free(const_cast< gchar* >(this->session_data->recipient));
425 }
427 if (recipientJID == NULL) {
428 this->session_data->recipient = NULL;
429 } else {
430 this->session_data->recipient = g_strdup(recipientJID);
431 }
432 }
434 void
435 SessionManager::sendChange(Glib::ustring const& msg, MessageType type, std::string const& recipientJID, bool chatroom)
436 {
437 if (!this->session_data->status[IN_WHITEBOARD]) {
438 return;
439 }
441 std::string& recipient = const_cast< std::string& >(recipientJID);
442 if (recipient.empty()) {
443 recipient = this->session_data->recipient;
444 }
447 switch (type) {
448 case DOCUMENT_BEGIN:
449 case DOCUMENT_END:
450 case CHANGE_NOT_REPEATABLE:
451 case CHANGE_REPEATABLE:
452 case CHANGE_COMMIT:
453 {
454 MessageNode *newNode = new MessageNode(this->session_data->sequence_number++, lm_connection_get_jid(this->session_data->connection), recipient, msg, type, false, chatroom);
455 this->session_data->send_queue->insert(newNode);
456 Inkscape::GC::release(newNode);
457 break;
458 }
459 default:
460 g_warning("Cannot insert MessageNode with unknown change type into send queue; discarding message. This may lead to desynchronization!");
461 break;
462 }
463 }
466 // FIXME:
467 // This method needs a massive, massive, massive overhaul.
468 int
469 SessionManager::sendMessage(MessageType msgtype, unsigned int sequence, Glib::ustring const& msg, char const* recipientJID, bool chatroom)
470 {
471 LmMessage* m;
472 GError* error = NULL;
473 char* type, * seq;
475 if (recipientJID == NULL || recipientJID == "") {
476 g_warning("Null recipient JID specified; not sending message.");
477 return NO_RECIPIENT_JID;
478 } else {
479 }
481 // create message
482 m = lm_message_new(recipientJID, LM_MESSAGE_TYPE_MESSAGE);
484 // add sender
485 lm_message_node_set_attribute(m->node, "from", lm_connection_get_jid(this->session_data->connection));
487 // set message subtype according to whether or not this is
488 // destined for a chatroom
489 if (chatroom) {
490 lm_message_node_set_attribute(m->node, "type", "groupchat");
491 } else {
492 lm_message_node_set_attribute(m->node, "type", "chat");
493 }
495 // set protocol version;
496 // we are currently fixed at version 1
497 lm_message_node_add_child(m->node, MESSAGE_PROTOCOL_VER, MESSAGE_PROTOCOL_V1);
499 // add message type
500 type = (char *)calloc(TYPE_FIELD_SIZE, sizeof(char));
501 snprintf(type, TYPE_FIELD_SIZE, "%i", msgtype);
502 lm_message_node_add_child(m->node, MESSAGE_TYPE, type);
503 free(type);
505 // add message body
506 if (!msg.empty()) {
507 lm_message_node_add_child(m->node, MESSAGE_BODY, msg.c_str());
508 } else {
509 }
511 // add sequence number
512 switch(msgtype) {
513 case CHANGE_REPEATABLE:
514 case CHANGE_NOT_REPEATABLE:
515 case DUMMY_CHANGE:
516 case CHANGE_COMMIT:
517 case DOCUMENT_BEGIN:
518 case DOCUMENT_END:
519 case CONNECT_REQUEST_RESPONSE_CHAT:
520 case CONNECT_REQUEST_RESPONSE_USER:
521 case CHATROOM_SYNCHRONIZE_RESPONSE:
522 seq = (char* )calloc(SEQNUM_FIELD_SIZE, sizeof(char));
523 sprintf(seq, "%u", sequence);
524 lm_message_node_add_child(m->node, MESSAGE_SEQNUM, seq);
525 free(seq);
526 break;
528 case CONNECT_REQUEST_USER:
529 case CONNECTED_SIGNAL:
530 case DISCONNECTED_FROM_USER_SIGNAL:
531 break;
533 // Error messages and synchronization requests do not need a sequence number
534 case CHATROOM_SYNCHRONIZE_REQUEST:
535 case CONNECT_REQUEST_REFUSED_BY_PEER:
536 case UNSUPPORTED_PROTOCOL_VERSION:
537 case ALREADY_IN_SESSION:
538 break;
540 default:
541 g_warning("Outgoing message type not recognized; not sending.");
542 lm_message_unref(m);
543 return UNKNOWN_OUTGOING_TYPE;
544 }
546 // We want to log messages even if they were not successfully sent,
547 // since the user may opt to re-synchronize a session using the session
548 // file record.
549 if (!msg.empty()) {
550 this->_log(msg);
551 this->_commitLog();
552 }
554 // send message
556 if (!lm_connection_send(this->session_data->connection, m, &error)) {
557 g_warning("Send failed: %s", error->message);
558 lm_message_unref(m);
559 return CONNECTION_ERROR;
560 }
562 lm_message_unref(m);
563 return SEND_SUCCESS;
564 }
566 void
567 SessionManager::connectionError(Glib::ustring const& errmsg)
568 {
569 Gtk::MessageDialog dlg(errmsg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
570 dlg.run();
571 // sp_whiteboard_connect_dialog(const_cast< gchar* >(errmsg));
572 }
574 void
575 SessionManager::resendDocument(char const* recipientJID, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf)
576 {
577 Glib::ustring docbegin = MessageUtilities::makeTagWithContent(MESSAGE_DOCBEGIN, "");
578 this->sendChange(docbegin, DOCUMENT_BEGIN, recipientJID, false);
580 Inkscape::XML::Node* root = sp_document_repr_root(this->_myDoc);
582 if(root == NULL) {
583 return;
584 }
586 NewChildObjectMessageList newchildren;
587 MessageAggregator& agg = MessageAggregator::instance();
588 Glib::ustring buf;
590 for ( Inkscape::XML::Node *child = root->firstChild() ; child != NULL ; child = child->next() ) {
591 // TODO: replace with Serializer methods
592 MessageUtilities::newObjectMessage(&buf, newidsbuf, newnodesbuf, newchildren, this->_myTracker, child);
594 NewChildObjectMessageList::iterator j = newchildren.begin();
595 Glib::ustring aggbuf;
597 for(; j != newchildren.end(); j++) {
598 if (!agg.addOne(*j, aggbuf)) {
599 this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
600 aggbuf.clear();
601 agg.addOne(*j, aggbuf);
602 }
603 }
605 // send remaining changes
606 if (!aggbuf.empty()) {
607 this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
608 aggbuf.clear();
609 }
611 newchildren.clear();
612 buf.clear();
613 }
615 Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_COMMIT, "");
616 this->sendChange(commit, CHANGE_COMMIT, recipientJID, false);
617 Glib::ustring docend = MessageUtilities::makeTagWithContent(MESSAGE_DOCEND, "");
618 this->sendChange(docend, DOCUMENT_END, recipientJID, false);
619 }
621 void
622 SessionManager::receiveChange(Glib::ustring const& changemsg)
623 {
625 struct Node part;
627 Glib::ustring msgcopy = changemsg.c_str();
630 while(MessageUtilities::getFirstMessageTag(part, msgcopy) != false) {
631 // TODO:
632 // Yikes. This is ugly.
633 if (part.tag == MESSAGE_CHANGE) {
634 this->_myDeserializer->deserializeEventChgAttr(part.data);
635 msgcopy.erase(0, part.next_pos);
637 } else if (part.tag == MESSAGE_NEWOBJ) {
638 this->_myDeserializer->deserializeEventAdd(part.data);
639 msgcopy.erase(0, part.next_pos);
641 } else if (part.tag == MESSAGE_DELETE) {
642 this->_myDeserializer->deserializeEventDel(part.data);
643 msgcopy.erase(0, part.next_pos);
645 } else if (part.tag == MESSAGE_DOCUMENT) {
646 // no special handler, just keep going with the rest of the message
647 msgcopy.erase(0, part.next_pos);
649 } else if (part.tag == MESSAGE_NODECONTENT) {
650 this->_myDeserializer->deserializeEventChgContent(part.data);
651 msgcopy.erase(0, part.next_pos);
653 } else if (part.tag == MESSAGE_ORDERCHANGE) {
654 this->_myDeserializer->deserializeEventChgOrder(part.data);
655 msgcopy.erase(0, part.next_pos);
657 } else if (part.tag == MESSAGE_COMMIT) {
658 // Retrieve the deserialized event log, node actions, and nodes with updated attributes
659 XML::Event* log = this->_myDeserializer->getEventLog();
660 KeyToNodeActionList& node_changes = this->_myDeserializer->getNodeTrackerActions();
661 AttributesUpdatedSet& updated = this->_myDeserializer->getUpdatedAttributeNodeSet();
663 // Make document insensitive to undo
664 gboolean saved = sp_document_get_undo_sensitive(this->_myDoc);
665 sp_document_set_undo_sensitive(this->_myDoc, FALSE);
667 // Replay the log and push it onto the undo stack
668 sp_repr_replay_log(log);
670 // Call updateRepr on changed nodes
671 // This is required for some tools to function properly, i.e. text tool
672 // (TODO: we don't need to update _all_ changed nodes, just their parents)
673 AttributesUpdatedSet::iterator i = updated.begin();
674 for(; i != updated.end(); i++) {
675 SPObject* updated = this->_myDoc->getObjectByRepr(*i);
676 if (updated) {
677 updated->updateRepr();
678 }
679 }
681 // merge the events generated by updateRepr
682 sp_repr_coalesce_log(this->_myDoc->priv->partial, log);
683 this->_myDoc->priv->partial = NULL;
685 this->_myDoc->priv->undo = g_slist_prepend(this->_myDoc->priv->undo, log);
687 // Restore undo sensitivity
688 sp_document_set_undo_sensitive(this->_myDoc, saved);
690 // Add or delete nodes to/from the tracker
691 this->_myTracker->process(node_changes);
693 // Reset deserializer state
694 this->_myDeserializer->reset();
695 break;
697 } else if (part.tag == MESSAGE_UNDO) {
698 this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::UNDO_EVENT);
699 sp_document_undo(this->_myDoc);
700 this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::UNDO_EVENT);
701 msgcopy.erase(0, part.next_pos);
703 } else if (part.tag == MESSAGE_REDO) {
704 this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::REDO_EVENT);
705 sp_document_redo(this->_myDoc);
706 this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::REDO_EVENT);
707 msgcopy.erase(0, part.next_pos);
709 } else if (part.tag == MESSAGE_DOCBEGIN) {
710 msgcopy.erase(0, part.next_pos);
712 } else if (part.tag == MESSAGE_DOCEND) {
713 // Set this to be the new original state of the document
714 sp_document_done(this->document());
715 sp_document_clear_redo(this->document());
716 sp_document_clear_undo(this->document());
717 this->setupCommitListener();
718 msgcopy.erase(0, part.next_pos);
720 } else {
721 msgcopy.erase(0, part.next_pos);
723 }
724 }
726 this->_log(changemsg);
728 this->_commitLog();
729 }
731 bool
732 SessionManager::isPlayingSessionFile()
733 {
734 return this->session_data->status[PLAYING_SESSION_FILE];
735 }
737 void
738 SessionManager::loadSessionFile(Glib::ustring filename)
739 {
740 if (!this->session_data || !this->session_data->status[IN_WHITEBOARD]) {
741 try {
742 if (this->_mySessionFile) {
743 delete this->_mySessionFile;
744 }
745 this->_mySessionFile = new SessionFile(filename, true, false);
747 // Initialize objects needed for session playback
748 if (this->_mySessionPlayer == NULL) {
749 this->_mySessionPlayer = new SessionFilePlayer(16, this);
750 } else {
751 this->_mySessionPlayer->load(this->_mySessionFile);
752 }
754 if (this->_myTracker == NULL) {
755 this->_myTracker = new XMLNodeTracker(this);
756 } else {
757 this->_myTracker->reset();
758 }
760 if (!this->session_data) {
761 this->session_data = new SessionData(this);
762 }
764 if (!this->_myDeserializer) {
765 this->_myDeserializer = new Deserializer(this->node_tracker());
766 }
768 if (!this->_myUndoObserver) {
769 this->setupCommitListener();
770 }
772 this->session_data->status.set(PLAYING_SESSION_FILE, 1);
775 } catch (Glib::FileError e) {
776 g_warning("Could not load session file: %s", e.what().data());
777 }
778 }
779 }
781 void
782 SessionManager::userConnectedToWhiteboard(gchar const* JID)
783 {
784 sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("Established whiteboard session with <b>%s</b>."), JID);
785 }
788 void
789 SessionManager::userDisconnectedFromWhiteboard(std::string const& JID)
790 {
792 sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("<b>%s</b> has <b>left</b> the whiteboard session."), JID.c_str());
794 // Inform the user
795 // TRANSLATORS: %1 is the name of the user that disconnected, %2 is the name of the user whom the disconnected user disconnected from.
796 // This message is not used in a chatroom context.
797 Glib::ustring primary = String::ucompose(_("<span weight=\"bold\" size=\"larger\">The user <b>%1</b> has left the whiteboard session.</span>\n\n"), JID);
798 // TRANSLATORS: %1 and %2 are userids
799 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));
801 // TODO: parent this dialog to the active desktop
802 Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false);
803 /*
804 dialog.set_message(primary, true);
805 dialog.set_secondary_text(secondary, true);
806 */
807 dialog.run();
808 }
810 void
811 SessionManager::startSendQueueDispatch()
812 {
813 this->_send_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchSendQueue), SEND_TIMEOUT);
814 }
816 void
817 SessionManager::stopSendQueueDispatch()
818 {
819 if (this->_send_queue_dispatcher) {
820 this->_send_queue_dispatcher.disconnect();
821 }
822 }
824 void
825 SessionManager::startReceiveQueueDispatch()
826 {
827 this->_receive_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchReceiveQueue), SEND_TIMEOUT);
828 }
830 void
831 SessionManager::stopReceiveQueueDispatch()
832 {
833 if (this->_receive_queue_dispatcher) {
834 this->_receive_queue_dispatcher.disconnect();
835 }
836 }
838 void
839 SessionManager::clearDocument()
840 {
841 // clear all layers, definitions, and metadata
842 XML::Node* rroot = this->_myDoc->rroot;
844 // clear definitions
845 XML::Node* defs = SP_OBJECT_REPR((SPDefs*)SP_DOCUMENT_DEFS(this->_myDoc));
846 g_assert(SP_ROOT(this->_myDoc->root)->defs);
848 for(XML::Node* child = defs->firstChild(); child; child = child->next()) {
849 defs->removeChild(child);
850 }
852 // clear layers
853 for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
854 if (strcmp(child->name(), "svg:g") == 0) {
855 rroot->removeChild(child);
856 }
857 }
859 // clear metadata
860 for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
861 if (strcmp(child->name(), "svg:metadata") == 0) {
862 rroot->removeChild(child);
863 }
864 }
866 // sp_document_done(this->_myDoc);
867 }
869 void
870 SessionManager::setupInkscapeInterface()
871 {
872 this->session_data->status.set(IN_WHITEBOARD, 1);
873 this->startSendQueueDispatch();
874 this->startReceiveQueueDispatch();
875 if (!this->_myTracker) {
876 this->_myTracker = new XMLNodeTracker(this);
877 }
879 this->_mySerializer = new Serializer(this->node_tracker());
880 this->_myDeserializer = new Deserializer(this->node_tracker());
882 // We're in a whiteboard session now, so set verb sensitivity accordingly
883 this->_setVerbSensitivity(ESTABLISHED_SESSION);
884 }
886 void
887 SessionManager::setupCommitListener()
888 {
889 this->_myUndoObserver = new Whiteboard::UndoStackObserver(this);
890 this->_myDoc->addUndoObserver(*this->_myUndoObserver);
891 }
893 ::SPDesktop*
894 SessionManager::desktop()
895 {
896 return this->_myDesktop;
897 }
899 ::SPDocument*
900 SessionManager::document()
901 {
902 return this->_myDoc;
903 }
905 Callbacks*
906 SessionManager::callbacks()
907 {
908 return this->_myCallbacks;
909 }
911 Whiteboard::UndoStackObserver*
912 SessionManager::undo_stack_observer()
913 {
914 return this->_myUndoObserver;
915 }
917 Serializer*
918 SessionManager::serializer()
919 {
920 return this->_mySerializer;
921 }
923 XMLNodeTracker*
924 SessionManager::node_tracker()
925 {
926 return this->_myTracker;
927 }
930 ChatMessageHandler*
931 SessionManager::chat_handler()
932 {
933 return this->_myChatHandler;
934 }
936 SessionFilePlayer*
937 SessionManager::session_player()
938 {
939 return this->_mySessionPlayer;
940 }
942 SessionFile*
943 SessionManager::session_file()
944 {
945 return this->_mySessionFile;
946 }
948 void
949 SessionManager::_log(Glib::ustring const& message)
950 {
951 if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
952 this->_mySessionFile->addMessage(message);
953 }
954 }
956 void
957 SessionManager::_commitLog()
958 {
959 if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
960 this->_mySessionFile->commit();
961 }
962 }
964 void
965 SessionManager::_closeLog()
966 {
967 if (this->_mySessionFile) {
968 this->_mySessionFile->close();
969 }
970 }
972 void
973 SessionManager::startLog(Glib::ustring filename)
974 {
975 try {
976 this->_mySessionFile = new SessionFile(filename, false, false);
977 } catch (Glib::FileError e) {
978 g_warning("Caught I/O error %s while attemping to open file %s for session recording.", e.what().c_str(), filename.c_str());
979 throw;
980 }
981 }
983 void
984 SessionManager::_tryToStartLog()
985 {
986 if (this->session_data) {
987 if (!this->session_data->sessionFile.empty()) {
988 bool undecided = true;
989 while(undecided) {
990 try {
991 this->startLog(this->session_data->sessionFile);
992 undecided = false;
993 } catch (Glib::FileError e) {
994 undecided = true;
995 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());
996 Gtk::MessageDialog dlg(msg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE, false);
997 dlg.add_button(_("Choose a different location"), 0);
998 dlg.add_button(_("Skip session recording"), 1);
999 switch (dlg.run()) {
1000 case 0:
1001 {
1002 Gtk::FileChooserDialog sessionfiledlg(_("Select a location and filename"), Gtk::FILE_CHOOSER_ACTION_SAVE);
1003 sessionfiledlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
1004 sessionfiledlg.add_button(_("Set filename"), Gtk::RESPONSE_OK);
1005 int result = sessionfiledlg.run();
1006 switch (result) {
1007 case Gtk::RESPONSE_OK:
1008 {
1009 this->session_data->sessionFile = sessionfiledlg.get_filename();
1010 break;
1011 }
1012 case Gtk::RESPONSE_CANCEL:
1013 default:
1014 undecided = false;
1015 break;
1016 }
1017 break;
1018 }
1019 case 1:
1020 default:
1021 undecided = false;
1022 break;
1023 }
1024 }
1025 }
1026 }
1027 }
1028 }
1030 void
1031 SessionManager::_setVerbSensitivity(SensitivityMode mode)
1032 {
1033 return;
1035 switch (mode) {
1036 case ESTABLISHED_CONNECTION:
1037 // Upon successful connection, we can disconnect from the server.
1038 // We can also start sharing a document with a user or chatroom.
1039 // We cannot, however, connect to a new server without first disconnecting.
1040 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, false);
1041 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, true);
1042 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1043 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1044 break;
1046 case ESTABLISHED_SESSION:
1047 // When we have established a session, we should not permit the user to go and
1048 // establish another session from the same document without first disconnecting.
1049 //
1050 // TODO: Well, actually, we probably _should_, but there's no real reconnection logic just yet.
1051 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1052 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1053 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, true);
1054 break;
1055 case DISCONNECTED_FROM_SESSION:
1056 // Upon disconnecting from a session, we can establish a new session and disconnect
1057 // from the server, but we cannot disconnect from a session (since we just left it.)
1058 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1059 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1060 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1062 case INITIAL:
1063 default:
1064 // Upon construction, there is no active connection, so we cannot do the following:
1065 // (1) disconnect from a session
1066 // (2) disconnect from a server
1067 // (3) share with a user
1068 // (4) share with a chatroom
1069 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, true);
1070 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1071 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1072 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1073 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, false);
1074 break;
1075 };
1076 }
1078 /*
1079 void
1080 SessionManager::Listener::processXmppEvent(Pedro::XmppEvent const& event)
1081 {
1082 int type = event.getType();
1084 switch (type) {
1085 case Pedro::XmppEvent::EVENT_STATUS:
1086 break;
1087 case Pedro::XmppEvent::EVENT_ERROR:
1088 break;
1089 case Pedro::XmppEvent::EVENT_CONNECTED:
1090 break;
1091 case Pedro::XmppEvent::EVENT_DISCONNECTED:
1092 break;
1093 case Pedro::XmppEvent::EVENT_MESSAGE:
1094 break;
1095 case Pedro::XmppEvent::EVENT_PRESENCE:
1096 break;
1097 case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
1098 break;
1099 case Pedro::XmppEvent::EVENT_MUC_JOIN:
1100 break;
1101 case Pedro::XmppEvent::EVENT_MUC_LEAVE:
1102 break;
1103 case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
1104 break;
1105 }
1106 }
1107 */
1109 }
1111 }
1114 /*
1115 Local Variables:
1116 mode:c++
1117 c-file-style:"stroustrup"
1118 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1119 indent-tabs-mode:nil
1120 fill-column:99
1121 End:
1122 */
1123 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :