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 #ifdef WIN32
64 static bool lm_initialize_called = false;
65 #endif
67 SessionData::SessionData(SessionManager *sm)
68 {
69 this->_sm = sm;
70 this->recipient = NULL;
71 this->connection = NULL;
72 this->ssl = NULL;
73 this->ignoreFurtherSSLErrors = false;
74 this->send_queue = new SendMessageQueue(sm);
75 this->sequence_number = 1;
76 }
78 SessionData::~SessionData()
79 {
80 this->receive_queues.clear();
82 if (this->send_queue) {
83 delete this->send_queue;
84 }
85 }
87 SessionManager::SessionManager(::SPDesktop *desktop)
88 {
90 // Initialize private members to NULL to facilitate deletion in destructor
91 this->_myDoc = NULL;
92 this->session_data = NULL;
93 this->_myCallbacks = NULL;
94 this->_myTracker = NULL;
95 this->_myChatHandler = NULL;
96 this->_mySessionFile = NULL;
97 this->_mySessionPlayer = NULL;
98 this->_myMessageHandler = NULL;
99 this->_myUndoObserver = NULL;
100 this->_mySerializer = NULL;
101 this->_myDeserializer = NULL;
103 this->setDesktop(desktop);
105 if (this->_myDoc == NULL) {
106 g_error("Initializing SessionManager on null document object!");
107 }
110 #ifdef WIN32
111 //# lm_initialize() must be called before any network code
112 if (!lm_initialize_called) {
113 lm_initialize();
114 lm_initialize_called = true;
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::connectToServer(Glib::ustring const& server, Glib::ustring const& port, Glib::ustring const& username, Glib::ustring const& pw, bool usessl)
184 {
185 GError* error = NULL;
186 Glib::ustring jid;
188 // JID format is username@server/resource
189 jid += username + "@" + server + "/" + RESOURCE_NAME;
191 LmMessage* m;
192 LmMessageHandler* mh;
194 if (!this->session_data) {
195 this->session_data = new SessionData(this);
196 }
198 if (!this->_myMessageHandler) {
199 this->_myMessageHandler = new MessageHandler(this);
200 }
202 // Connect to server
203 // We need to check to see if this object already exists, because
204 // the user may be reusing an old connection that failed due to e.g.
205 // authentication failure.
206 if (this->session_data->connection) {
207 lm_connection_close(this->session_data->connection, &error);
208 lm_connection_unref(this->session_data->connection);
209 }
211 this->session_data->connection = lm_connection_new(server.c_str());
213 lm_connection_set_port(this->session_data->connection, atoi(port.c_str()));
215 if (usessl) {
216 if (lm_ssl_is_supported()) {
217 this->session_data->ssl = lm_ssl_new(NULL, ssl_error_handler, reinterpret_cast< gpointer >(this), NULL);
219 lm_ssl_ref(this->session_data->ssl);
220 } else {
221 return SSL_INITIALIZATION_ERROR;
222 }
223 lm_connection_set_ssl(this->session_data->connection, this->session_data->ssl);
224 }
226 // Send authorization
227 lm_connection_set_jid(this->session_data->connection, jid.c_str());
229 // TODO:
230 // Asynchronous connection and authentication would be nice,
231 // but it's a huge mess of mixing C callbacks and C++ method calls.
232 // I've tried to do it and only managed to severely destabilize the Jabber
233 // server connection routines.
234 //
235 // This, of course, is an invitation to anyone more capable than me
236 // to convert this from synchronous to asynchronous Loudmouth calls.
237 if (!lm_connection_open_and_block(this->session_data->connection, &error)) {
238 if (error != NULL) {
239 g_warning("Failed to open: %s", error->message);
240 }
241 return FAILED_TO_CONNECT;
242 }
244 // Authenticate
245 if (!lm_connection_authenticate_and_block(this->session_data->connection, username.c_str(), pw.c_str(), RESOURCE_NAME, &error)) {
246 if (error != NULL) {
247 g_warning("Failed to authenticate: %s", error->message);
248 }
249 lm_connection_close(this->session_data->connection, NULL);
250 lm_connection_unref(this->session_data->connection);
251 this->session_data->connection = NULL;
252 return INVALID_AUTH;
253 }
255 // Register message handler for presence messages
256 mh = lm_message_handler_new((LmHandleMessageFunction)presence_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
257 lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_PRESENCE, LM_HANDLER_PRIORITY_NORMAL);
259 // Register message handler for stream error messages
260 mh = lm_message_handler_new((LmHandleMessageFunction)stream_error_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
261 lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_STREAM_ERROR, LM_HANDLER_PRIORITY_NORMAL);
263 // Register message handler for chat messages
264 mh = lm_message_handler_new((LmHandleMessageFunction)default_handler, reinterpret_cast< gpointer >(this->_myMessageHandler), NULL);
265 lm_connection_register_message_handler(this->session_data->connection, mh, LM_MESSAGE_TYPE_MESSAGE, LM_HANDLER_PRIORITY_NORMAL);
267 // Send presence message to server
268 m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_NOT_SET);
269 if (!lm_connection_send(this->session_data->connection, m, &error)) {
270 if (error != NULL) {
271 g_warning("Presence message could not be sent: %s", error->message);
272 }
273 lm_connection_close(this->session_data->connection, NULL);
274 lm_connection_unref(this->session_data->connection);
275 this->session_data->connection = NULL;
276 return FAILED_TO_CONNECT;
277 }
279 this->session_data->status.set(LOGGED_IN, 1);
281 this->_myCallbacks = new Callbacks(this);
283 lm_message_unref(m);
285 this->_setVerbSensitivity(ESTABLISHED_CONNECTION);
287 return CONNECT_SUCCESS;
288 }
290 LmSSLResponse
291 SessionManager::handleSSLError(LmSSL* ssl, LmSSLStatus status)
292 {
293 if (this->session_data->ignoreFurtherSSLErrors) {
294 return LM_SSL_RESPONSE_CONTINUE;
295 }
297 Glib::ustring msg;
299 // TODO: It'd be nice to provide the user with additional information in some cases,
300 // like fingerprints, hostname, etc.
301 switch(status) {
302 case LM_SSL_STATUS_NO_CERT_FOUND:
303 msg = _("No SSL certificate was found.");
304 break;
305 case LM_SSL_STATUS_UNTRUSTED_CERT:
306 msg = _("The SSL certificate provided by the Jabber server is untrusted.");
307 break;
308 case LM_SSL_STATUS_CERT_EXPIRED:
309 msg = _("The SSL certificate provided by the Jabber server is expired.");
310 break;
311 case LM_SSL_STATUS_CERT_NOT_ACTIVATED:
312 msg = _("The SSL certificate provided by the Jabber server has not been activated.");
313 break;
314 case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:
315 msg = _("The SSL certificate provided by the Jabber server contains a hostname that does not match the Jabber server's hostname.");
316 break;
317 case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH:
318 msg = _("The SSL certificate provided by the Jabber server contains an invalid fingerprint.");
319 break;
320 case LM_SSL_STATUS_GENERIC_ERROR:
321 msg = _("An unknown error occurred while setting up the SSL connection.");
322 break;
323 }
325 // TRANSLATORS: %1 is the message that describes the specific error that occurred when
326 // establishing the SSL connection.
327 Glib::ustring mainmsg = String::ucompose(_("<span weight=\"bold\" size=\"larger\">%1</span>\n\nDo you wish to continue connecting to the Jabber server?"), msg);
329 Gtk::MessageDialog dlg(mainmsg, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, false);
330 dlg.add_button(_("Continue connecting and ignore further errors"), 0);
331 dlg.add_button(_("Continue connecting, but warn me of further errors"), 1);
332 dlg.add_button(_("Cancel connection"), 2);
334 switch(dlg.run()) {
335 case 0:
336 this->session_data->ignoreFurtherSSLErrors = true;
337 /* FALL-THROUGH */
338 case 1:
339 return LM_SSL_RESPONSE_CONTINUE;
341 default:
342 return LM_SSL_RESPONSE_STOP;
343 }
344 }
346 void
347 SessionManager::disconnectFromServer()
348 {
349 if (this->session_data->connection) {
350 GError* error = NULL;
352 LmMessage *m;
353 this->disconnectFromDocument();
354 m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, LM_MESSAGE_SUB_TYPE_UNAVAILABLE);
355 if (!lm_connection_send(this->session_data->connection, m, &error)) {
356 g_warning("Could not send unavailable presence message: %s", error->message);
357 }
359 lm_message_unref(m);
360 lm_connection_close(this->session_data->connection, NULL);
361 lm_connection_unref(this->session_data->connection);
362 if (this->session_data->ssl) {
363 lm_ssl_unref(this->session_data->ssl);
364 }
366 this->session_data->connection = NULL;
367 this->session_data->ssl = NULL;
368 this->_setVerbSensitivity(INITIAL);
369 }
370 }
372 void
373 SessionManager::disconnectFromDocument()
374 {
375 if (this->session_data->status[IN_WHITEBOARD] || !this->session_data->status[IN_CHATROOM]) {
376 this->sendMessage(DISCONNECTED_FROM_USER_SIGNAL, 0, "", this->session_data->recipient, false);
377 }
378 this->closeSession();
379 this->_setVerbSensitivity(DISCONNECTED_FROM_SESSION);
380 }
382 void
383 SessionManager::closeSession()
384 {
386 if (this->session_data->status[IN_WHITEBOARD]) {
387 this->session_data->status.set(IN_WHITEBOARD, 0);
388 this->session_data->receive_queues.clear();
389 this->session_data->send_queue->clear();
390 this->stopSendQueueDispatch();
391 this->stopReceiveQueueDispatch();
392 }
394 if (this->_myUndoObserver) {
395 this->_myDoc->removeUndoObserver(*this->_myUndoObserver);
396 }
398 delete this->_myUndoObserver;
399 delete this->_mySerializer;
400 delete this->_myDeserializer;
402 this->_myUndoObserver = NULL;
403 this->_mySerializer = NULL;
404 this->_myDeserializer = NULL;
406 if (this->_myTracker) {
407 delete this->_myTracker;
408 this->_myTracker = NULL;
409 }
412 this->setRecipient(NULL);
413 }
415 void
416 SessionManager::setRecipient(char const* recipientJID)
417 {
418 if (this->session_data->recipient) {
419 free(const_cast< gchar* >(this->session_data->recipient));
420 }
422 if (recipientJID == NULL) {
423 this->session_data->recipient = NULL;
424 } else {
425 this->session_data->recipient = g_strdup(recipientJID);
426 }
427 }
429 void
430 SessionManager::sendChange(Glib::ustring const& msg, MessageType type, std::string const& recipientJID, bool chatroom)
431 {
432 if (!this->session_data->status[IN_WHITEBOARD]) {
433 return;
434 }
436 std::string& recipient = const_cast< std::string& >(recipientJID);
437 if (recipient.empty()) {
438 recipient = this->session_data->recipient;
439 }
442 switch (type) {
443 case DOCUMENT_BEGIN:
444 case DOCUMENT_END:
445 case CHANGE_NOT_REPEATABLE:
446 case CHANGE_REPEATABLE:
447 case CHANGE_COMMIT:
448 {
449 MessageNode *newNode = new MessageNode(this->session_data->sequence_number++, lm_connection_get_jid(this->session_data->connection), recipient, msg, type, false, chatroom);
450 this->session_data->send_queue->insert(newNode);
451 Inkscape::GC::release(newNode);
452 break;
453 }
454 default:
455 g_warning("Cannot insert MessageNode with unknown change type into send queue; discarding message. This may lead to desynchronization!");
456 break;
457 }
458 }
461 // FIXME:
462 // This method needs a massive, massive, massive overhaul.
463 int
464 SessionManager::sendMessage(MessageType msgtype, unsigned int sequence, Glib::ustring const& msg, char const* recipientJID, bool chatroom)
465 {
466 LmMessage* m;
467 GError* error = NULL;
468 char* type, * seq;
470 if (recipientJID == NULL || recipientJID == "") {
471 g_warning("Null recipient JID specified; not sending message.");
472 return NO_RECIPIENT_JID;
473 } else {
474 }
476 // create message
477 m = lm_message_new(recipientJID, LM_MESSAGE_TYPE_MESSAGE);
479 // add sender
480 lm_message_node_set_attribute(m->node, "from", lm_connection_get_jid(this->session_data->connection));
482 // set message subtype according to whether or not this is
483 // destined for a chatroom
484 if (chatroom) {
485 lm_message_node_set_attribute(m->node, "type", "groupchat");
486 } else {
487 lm_message_node_set_attribute(m->node, "type", "chat");
488 }
490 // set protocol version;
491 // we are currently fixed at version 1
492 lm_message_node_add_child(m->node, MESSAGE_PROTOCOL_VER, MESSAGE_PROTOCOL_V1);
494 // add message type
495 type = (char *)calloc(TYPE_FIELD_SIZE, sizeof(char));
496 snprintf(type, TYPE_FIELD_SIZE, "%i", msgtype);
497 lm_message_node_add_child(m->node, MESSAGE_TYPE, type);
498 free(type);
500 // add message body
501 if (!msg.empty()) {
502 lm_message_node_add_child(m->node, MESSAGE_BODY, msg.c_str());
503 } else {
504 }
506 // add sequence number
507 switch(msgtype) {
508 case CHANGE_REPEATABLE:
509 case CHANGE_NOT_REPEATABLE:
510 case DUMMY_CHANGE:
511 case CHANGE_COMMIT:
512 case DOCUMENT_BEGIN:
513 case DOCUMENT_END:
514 case CONNECT_REQUEST_RESPONSE_CHAT:
515 case CONNECT_REQUEST_RESPONSE_USER:
516 case CHATROOM_SYNCHRONIZE_RESPONSE:
517 seq = (char* )calloc(SEQNUM_FIELD_SIZE, sizeof(char));
518 sprintf(seq, "%u", sequence);
519 lm_message_node_add_child(m->node, MESSAGE_SEQNUM, seq);
520 free(seq);
521 break;
523 case CONNECT_REQUEST_USER:
524 case CONNECTED_SIGNAL:
525 case DISCONNECTED_FROM_USER_SIGNAL:
526 break;
528 // Error messages and synchronization requests do not need a sequence number
529 case CHATROOM_SYNCHRONIZE_REQUEST:
530 case CONNECT_REQUEST_REFUSED_BY_PEER:
531 case UNSUPPORTED_PROTOCOL_VERSION:
532 case ALREADY_IN_SESSION:
533 break;
535 default:
536 g_warning("Outgoing message type not recognized; not sending.");
537 lm_message_unref(m);
538 return UNKNOWN_OUTGOING_TYPE;
539 }
541 // We want to log messages even if they were not successfully sent,
542 // since the user may opt to re-synchronize a session using the session
543 // file record.
544 if (!msg.empty()) {
545 this->_log(msg);
546 this->_commitLog();
547 }
549 // send message
551 if (!lm_connection_send(this->session_data->connection, m, &error)) {
552 g_warning("Send failed: %s", error->message);
553 lm_message_unref(m);
554 return CONNECTION_ERROR;
555 }
557 lm_message_unref(m);
558 return SEND_SUCCESS;
559 }
561 void
562 SessionManager::connectionError(Glib::ustring const& errmsg)
563 {
564 Gtk::MessageDialog dlg(errmsg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE);
565 dlg.run();
566 // sp_whiteboard_connect_dialog(const_cast< gchar* >(errmsg));
567 }
569 void
570 SessionManager::resendDocument(char const* recipientJID, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf)
571 {
572 Glib::ustring docbegin = MessageUtilities::makeTagWithContent(MESSAGE_DOCBEGIN, "");
573 this->sendChange(docbegin, DOCUMENT_BEGIN, recipientJID, false);
575 Inkscape::XML::Node* root = sp_document_repr_root(this->_myDoc);
577 if(root == NULL) {
578 return;
579 }
581 NewChildObjectMessageList newchildren;
582 MessageAggregator& agg = MessageAggregator::instance();
583 Glib::ustring buf;
585 for ( Inkscape::XML::Node *child = root->firstChild() ; child != NULL ; child = child->next() ) {
586 // TODO: replace with Serializer methods
587 MessageUtilities::newObjectMessage(&buf, newidsbuf, newnodesbuf, newchildren, this->_myTracker, child);
589 NewChildObjectMessageList::iterator j = newchildren.begin();
590 Glib::ustring aggbuf;
592 for(; j != newchildren.end(); j++) {
593 if (!agg.addOne(*j, aggbuf)) {
594 this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
595 aggbuf.clear();
596 agg.addOne(*j, aggbuf);
597 }
598 }
600 // send remaining changes
601 if (!aggbuf.empty()) {
602 this->sendChange(aggbuf, CHANGE_REPEATABLE, recipientJID, false);
603 aggbuf.clear();
604 }
606 newchildren.clear();
607 buf.clear();
608 }
610 Glib::ustring commit = MessageUtilities::makeTagWithContent(MESSAGE_COMMIT, "");
611 this->sendChange(commit, CHANGE_COMMIT, recipientJID, false);
612 Glib::ustring docend = MessageUtilities::makeTagWithContent(MESSAGE_DOCEND, "");
613 this->sendChange(docend, DOCUMENT_END, recipientJID, false);
614 }
616 void
617 SessionManager::receiveChange(Glib::ustring const& changemsg)
618 {
620 struct Node part;
622 Glib::ustring msgcopy = changemsg.c_str();
625 while(MessageUtilities::getFirstMessageTag(part, msgcopy) != false) {
626 // TODO:
627 // Yikes. This is ugly.
628 if (part.tag == MESSAGE_CHANGE) {
629 this->_myDeserializer->deserializeEventChgAttr(part.data);
630 msgcopy.erase(0, part.next_pos);
632 } else if (part.tag == MESSAGE_NEWOBJ) {
633 this->_myDeserializer->deserializeEventAdd(part.data);
634 msgcopy.erase(0, part.next_pos);
636 } else if (part.tag == MESSAGE_DELETE) {
637 this->_myDeserializer->deserializeEventDel(part.data);
638 msgcopy.erase(0, part.next_pos);
640 } else if (part.tag == MESSAGE_DOCUMENT) {
641 // no special handler, just keep going with the rest of the message
642 msgcopy.erase(0, part.next_pos);
644 } else if (part.tag == MESSAGE_NODECONTENT) {
645 this->_myDeserializer->deserializeEventChgContent(part.data);
646 msgcopy.erase(0, part.next_pos);
648 } else if (part.tag == MESSAGE_ORDERCHANGE) {
649 this->_myDeserializer->deserializeEventChgOrder(part.data);
650 msgcopy.erase(0, part.next_pos);
652 } else if (part.tag == MESSAGE_COMMIT) {
653 // Retrieve the deserialized event log, node actions, and nodes with updated attributes
654 XML::Event* log = this->_myDeserializer->getEventLog();
655 KeyToNodeActionList& node_changes = this->_myDeserializer->getNodeTrackerActions();
656 AttributesUpdatedSet& updated = this->_myDeserializer->getUpdatedAttributeNodeSet();
658 // Make document insensitive to undo
659 gboolean saved = sp_document_get_undo_sensitive(this->_myDoc);
660 sp_document_set_undo_sensitive(this->_myDoc, FALSE);
662 // Replay the log and push it onto the undo stack
663 sp_repr_replay_log(log);
665 // Call updateRepr on changed nodes
666 // This is required for some tools to function properly, i.e. text tool
667 // (TODO: we don't need to update _all_ changed nodes, just their parents)
668 AttributesUpdatedSet::iterator i = updated.begin();
669 for(; i != updated.end(); i++) {
670 SPObject* updated = this->_myDoc->getObjectByRepr(*i);
671 if (updated) {
672 updated->updateRepr();
673 }
674 }
676 // merge the events generated by updateRepr
677 sp_repr_coalesce_log(this->_myDoc->priv->partial, log);
678 this->_myDoc->priv->partial = NULL;
680 this->_myDoc->priv->undo = g_slist_prepend(this->_myDoc->priv->undo, log);
682 // Restore undo sensitivity
683 sp_document_set_undo_sensitive(this->_myDoc, saved);
685 // Add or delete nodes to/from the tracker
686 this->_myTracker->process(node_changes);
688 // Reset deserializer state
689 this->_myDeserializer->reset();
690 break;
692 } else if (part.tag == MESSAGE_UNDO) {
693 this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::UNDO_EVENT);
694 sp_document_undo(this->_myDoc);
695 this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::UNDO_EVENT);
696 msgcopy.erase(0, part.next_pos);
698 } else if (part.tag == MESSAGE_REDO) {
699 this->_myUndoObserver->lockObserverFromSending(UndoStackObserver::REDO_EVENT);
700 sp_document_redo(this->_myDoc);
701 this->_myUndoObserver->unlockObserverFromSending(UndoStackObserver::REDO_EVENT);
702 msgcopy.erase(0, part.next_pos);
704 } else if (part.tag == MESSAGE_DOCBEGIN) {
705 msgcopy.erase(0, part.next_pos);
707 } else if (part.tag == MESSAGE_DOCEND) {
708 // Set this to be the new original state of the document
709 sp_document_done(this->document());
710 sp_document_clear_redo(this->document());
711 sp_document_clear_undo(this->document());
712 this->setupCommitListener();
713 msgcopy.erase(0, part.next_pos);
715 } else {
716 msgcopy.erase(0, part.next_pos);
718 }
719 }
721 this->_log(changemsg);
723 this->_commitLog();
724 }
726 bool
727 SessionManager::isPlayingSessionFile()
728 {
729 return this->session_data->status[PLAYING_SESSION_FILE];
730 }
732 void
733 SessionManager::loadSessionFile(Glib::ustring filename)
734 {
735 if (!this->session_data || !this->session_data->status[IN_WHITEBOARD]) {
736 try {
737 if (this->_mySessionFile) {
738 delete this->_mySessionFile;
739 }
740 this->_mySessionFile = new SessionFile(filename, true, false);
742 // Initialize objects needed for session playback
743 if (this->_mySessionPlayer == NULL) {
744 this->_mySessionPlayer = new SessionFilePlayer(16, this);
745 } else {
746 this->_mySessionPlayer->load(this->_mySessionFile);
747 }
749 if (this->_myTracker == NULL) {
750 this->_myTracker = new XMLNodeTracker(this);
751 } else {
752 this->_myTracker->reset();
753 }
755 if (!this->session_data) {
756 this->session_data = new SessionData(this);
757 }
759 if (!this->_myDeserializer) {
760 this->_myDeserializer = new Deserializer(this->node_tracker());
761 }
763 if (!this->_myUndoObserver) {
764 this->setupCommitListener();
765 }
767 this->session_data->status.set(PLAYING_SESSION_FILE, 1);
770 } catch (Glib::FileError e) {
771 g_warning("Could not load session file: %s", e.what().data());
772 }
773 }
774 }
776 void
777 SessionManager::userConnectedToWhiteboard(gchar const* JID)
778 {
779 sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("Established whiteboard session with <b>%s</b>."), JID);
780 }
783 void
784 SessionManager::userDisconnectedFromWhiteboard(std::string const& JID)
785 {
787 sp_desktop_message_stack(this->_myDesktop)->flashF(Inkscape::INFORMATION_MESSAGE, _("<b>%s</b> has <b>left</b> the whiteboard session."), JID.c_str());
789 // Inform the user
790 // TRANSLATORS: %1 is the name of the user that disconnected, %2 is the name of the user whom the disconnected user disconnected from.
791 // This message is not used in a chatroom context.
792 Glib::ustring primary = String::ucompose(_("<span weight=\"bold\" size=\"larger\">The user <b>%1</b> has left the whiteboard session.</span>\n\n"), JID);
793 // TRANSLATORS: %1 and %2 are userids
794 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));
796 // TODO: parent this dialog to the active desktop
797 Gtk::MessageDialog dialog(primary + secondary, true, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false);
798 /*
799 dialog.set_message(primary, true);
800 dialog.set_secondary_text(secondary, true);
801 */
802 dialog.run();
803 }
805 void
806 SessionManager::startSendQueueDispatch()
807 {
808 this->_send_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchSendQueue), SEND_TIMEOUT);
809 }
811 void
812 SessionManager::stopSendQueueDispatch()
813 {
814 if (this->_send_queue_dispatcher) {
815 this->_send_queue_dispatcher.disconnect();
816 }
817 }
819 void
820 SessionManager::startReceiveQueueDispatch()
821 {
822 this->_receive_queue_dispatcher = Glib::signal_timeout().connect(sigc::mem_fun(*(this->_myCallbacks), &Callbacks::dispatchReceiveQueue), SEND_TIMEOUT);
823 }
825 void
826 SessionManager::stopReceiveQueueDispatch()
827 {
828 if (this->_receive_queue_dispatcher) {
829 this->_receive_queue_dispatcher.disconnect();
830 }
831 }
833 void
834 SessionManager::clearDocument()
835 {
836 // clear all layers, definitions, and metadata
837 XML::Node* rroot = this->_myDoc->rroot;
839 // clear definitions
840 XML::Node* defs = SP_OBJECT_REPR((SPDefs*)SP_DOCUMENT_DEFS(this->_myDoc));
841 g_assert(SP_ROOT(this->_myDoc->root)->defs);
843 for(XML::Node* child = defs->firstChild(); child; child = child->next()) {
844 defs->removeChild(child);
845 }
847 // clear layers
848 for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
849 if (strcmp(child->name(), "svg:g") == 0) {
850 rroot->removeChild(child);
851 }
852 }
854 // clear metadata
855 for(XML::Node* child = rroot->firstChild(); child; child = child->next()) {
856 if (strcmp(child->name(), "svg:metadata") == 0) {
857 rroot->removeChild(child);
858 }
859 }
861 // sp_document_done(this->_myDoc);
862 }
864 void
865 SessionManager::setupInkscapeInterface()
866 {
867 this->session_data->status.set(IN_WHITEBOARD, 1);
868 this->startSendQueueDispatch();
869 this->startReceiveQueueDispatch();
870 if (!this->_myTracker) {
871 this->_myTracker = new XMLNodeTracker(this);
872 }
874 this->_mySerializer = new Serializer(this->node_tracker());
875 this->_myDeserializer = new Deserializer(this->node_tracker());
877 // We're in a whiteboard session now, so set verb sensitivity accordingly
878 this->_setVerbSensitivity(ESTABLISHED_SESSION);
879 }
881 void
882 SessionManager::setupCommitListener()
883 {
884 this->_myUndoObserver = new Whiteboard::UndoStackObserver(this);
885 this->_myDoc->addUndoObserver(*this->_myUndoObserver);
886 }
888 ::SPDesktop*
889 SessionManager::desktop()
890 {
891 return this->_myDesktop;
892 }
894 ::SPDocument*
895 SessionManager::document()
896 {
897 return this->_myDoc;
898 }
900 Callbacks*
901 SessionManager::callbacks()
902 {
903 return this->_myCallbacks;
904 }
906 Whiteboard::UndoStackObserver*
907 SessionManager::undo_stack_observer()
908 {
909 return this->_myUndoObserver;
910 }
912 Serializer*
913 SessionManager::serializer()
914 {
915 return this->_mySerializer;
916 }
918 XMLNodeTracker*
919 SessionManager::node_tracker()
920 {
921 return this->_myTracker;
922 }
925 ChatMessageHandler*
926 SessionManager::chat_handler()
927 {
928 return this->_myChatHandler;
929 }
931 SessionFilePlayer*
932 SessionManager::session_player()
933 {
934 return this->_mySessionPlayer;
935 }
937 SessionFile*
938 SessionManager::session_file()
939 {
940 return this->_mySessionFile;
941 }
943 void
944 SessionManager::_log(Glib::ustring const& message)
945 {
946 if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
947 this->_mySessionFile->addMessage(message);
948 }
949 }
951 void
952 SessionManager::_commitLog()
953 {
954 if (this->_mySessionFile && !this->_mySessionFile->isReadOnly()) {
955 this->_mySessionFile->commit();
956 }
957 }
959 void
960 SessionManager::_closeLog()
961 {
962 if (this->_mySessionFile) {
963 this->_mySessionFile->close();
964 }
965 }
967 void
968 SessionManager::startLog(Glib::ustring filename)
969 {
970 try {
971 this->_mySessionFile = new SessionFile(filename, false, false);
972 } catch (Glib::FileError e) {
973 g_warning("Caught I/O error %s while attemping to open file %s for session recording.", e.what().c_str(), filename.c_str());
974 throw;
975 }
976 }
978 void
979 SessionManager::_tryToStartLog()
980 {
981 if (this->session_data) {
982 if (!this->session_data->sessionFile.empty()) {
983 bool undecided = true;
984 while(undecided) {
985 try {
986 this->startLog(this->session_data->sessionFile);
987 undecided = false;
988 } catch (Glib::FileError e) {
989 undecided = true;
990 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());
991 Gtk::MessageDialog dlg(msg, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE, false);
992 dlg.add_button(_("Choose a different location"), 0);
993 dlg.add_button(_("Skip session recording"), 1);
994 switch (dlg.run()) {
995 case 0:
996 {
997 Gtk::FileChooserDialog sessionfiledlg(_("Select a location and filename"), Gtk::FILE_CHOOSER_ACTION_SAVE);
998 sessionfiledlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
999 sessionfiledlg.add_button(_("Set filename"), Gtk::RESPONSE_OK);
1000 int result = sessionfiledlg.run();
1001 switch (result) {
1002 case Gtk::RESPONSE_OK:
1003 {
1004 this->session_data->sessionFile = sessionfiledlg.get_filename();
1005 break;
1006 }
1007 case Gtk::RESPONSE_CANCEL:
1008 default:
1009 undecided = false;
1010 break;
1011 }
1012 break;
1013 }
1014 case 1:
1015 default:
1016 undecided = false;
1017 break;
1018 }
1019 }
1020 }
1021 }
1022 }
1023 }
1025 void
1026 SessionManager::_setVerbSensitivity(SensitivityMode mode)
1027 {
1028 return;
1030 switch (mode) {
1031 case ESTABLISHED_CONNECTION:
1032 // Upon successful connection, we can disconnect from the server.
1033 // We can also start sharing a document with a user or chatroom.
1034 // We cannot, however, connect to a new server without first disconnecting.
1035 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, false);
1036 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, true);
1037 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1038 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1039 break;
1041 case ESTABLISHED_SESSION:
1042 // When we have established a session, we should not permit the user to go and
1043 // establish another session from the same document without first disconnecting.
1044 //
1045 // TODO: Well, actually, we probably _should_, but there's no real reconnection logic just yet.
1046 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1047 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1048 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, true);
1049 break;
1050 case DISCONNECTED_FROM_SESSION:
1051 // Upon disconnecting from a session, we can establish a new session and disconnect
1052 // from the server, but we cannot disconnect from a session (since we just left it.)
1053 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1054 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, true);
1055 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, true);
1057 case INITIAL:
1058 default:
1059 // Upon construction, there is no active connection, so we cannot do the following:
1060 // (1) disconnect from a session
1061 // (2) disconnect from a server
1062 // (3) share with a user
1063 // (4) share with a chatroom
1064 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_CONNECT)->sensitive(this->_myDoc, true);
1065 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHUSER)->sensitive(this->_myDoc, false);
1066 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_SHAREWITHCHAT)->sensitive(this->_myDoc, false);
1067 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SESSION)->sensitive(this->_myDoc, false);
1068 Inkscape::Verb::get(SP_VERB_DIALOG_WHITEBOARD_DISCONNECT_FROM_SERVER)->sensitive(this->_myDoc, false);
1069 break;
1070 };
1071 }
1073 /*
1074 void
1075 SessionManager::Listener::processXmppEvent(Pedro::XmppEvent const& event)
1076 {
1077 int type = event.getType();
1079 switch (type) {
1080 case Pedro::XmppEvent::EVENT_STATUS:
1081 break;
1082 case Pedro::XmppEvent::EVENT_ERROR:
1083 break;
1084 case Pedro::XmppEvent::EVENT_CONNECTED:
1085 break;
1086 case Pedro::XmppEvent::EVENT_DISCONNECTED:
1087 break;
1088 case Pedro::XmppEvent::EVENT_MESSAGE:
1089 break;
1090 case Pedro::XmppEvent::EVENT_PRESENCE:
1091 break;
1092 case Pedro::XmppEvent::EVENT_MUC_MESSAGE:
1093 break;
1094 case Pedro::XmppEvent::EVENT_MUC_JOIN:
1095 break;
1096 case Pedro::XmppEvent::EVENT_MUC_LEAVE:
1097 break;
1098 case Pedro::XmppEvent::EVENT_MUC_PRESENCE:
1099 break;
1100 }
1101 }
1102 */
1104 }
1106 }
1109 /*
1110 Local Variables:
1111 mode:c++
1112 c-file-style:"stroustrup"
1113 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1114 indent-tabs-mode:nil
1115 fill-column:99
1116 End:
1117 */
1118 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :