summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMateusz Piekos <mateuszpiekos@gmail.com>2012-05-30 12:40:27 (GMT)
committerRemko Tronçon <git@el-tramo.be>2012-09-08 16:14:27 (GMT)
commitfe8a8aa031da734f3112184420b372021d9e10c7 (patch)
tree9f8e0b7d11d7612eb25a8682cf940fa647ca67f2 /Swiften/Whiteboard
parent0174723efbc1f612433d45c6916a2ad4596b96ba (diff)
downloadswift-fe8a8aa031da734f3112184420b372021d9e10c7.zip
swift-fe8a8aa031da734f3112184420b372021d9e10c7.tar.bz2
Support whiteboarding.
License: This patch is BSD-licensed, see http://www.opensource.org/licenses/bsd-license.php
Diffstat (limited to 'Swiften/Whiteboard')
-rw-r--r--Swiften/Whiteboard/IncomingWhiteboardSession.cpp58
-rw-r--r--Swiften/Whiteboard/IncomingWhiteboardSession.h30
-rw-r--r--Swiften/Whiteboard/OutgoingWhiteboardSession.cpp59
-rw-r--r--Swiften/Whiteboard/OutgoingWhiteboardSession.h31
-rw-r--r--Swiften/Whiteboard/UnitTest/WhiteboardClientTest.cpp676
-rw-r--r--Swiften/Whiteboard/UnitTest/WhiteboardServerTest.cpp128
-rw-r--r--Swiften/Whiteboard/WhiteboardClient.cpp120
-rw-r--r--Swiften/Whiteboard/WhiteboardClient.h36
-rw-r--r--Swiften/Whiteboard/WhiteboardResponder.cpp37
-rw-r--r--Swiften/Whiteboard/WhiteboardResponder.h25
-rw-r--r--Swiften/Whiteboard/WhiteboardServer.cpp48
-rw-r--r--Swiften/Whiteboard/WhiteboardServer.h23
-rw-r--r--Swiften/Whiteboard/WhiteboardSession.cpp67
-rw-r--r--Swiften/Whiteboard/WhiteboardSession.h54
-rw-r--r--Swiften/Whiteboard/WhiteboardSessionManager.cpp111
-rw-r--r--Swiften/Whiteboard/WhiteboardSessionManager.h53
-rw-r--r--Swiften/Whiteboard/WhiteboardTransformer.cpp213
-rw-r--r--Swiften/Whiteboard/WhiteboardTransformer.h28
18 files changed, 1797 insertions, 0 deletions
diff --git a/Swiften/Whiteboard/IncomingWhiteboardSession.cpp b/Swiften/Whiteboard/IncomingWhiteboardSession.cpp
new file mode 100644
index 0000000..d64a0d2
--- /dev/null
+++ b/Swiften/Whiteboard/IncomingWhiteboardSession.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/IncomingWhiteboardSession.h>
+#include <Swiften/Elements/WhiteboardPayload.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h>
+
+namespace Swift {
+ IncomingWhiteboardSession::IncomingWhiteboardSession(const JID& jid, IQRouter* router) : WhiteboardSession(jid, router) {
+ }
+
+ IncomingWhiteboardSession::~IncomingWhiteboardSession() {
+ }
+
+ void IncomingWhiteboardSession::accept() {
+ boost::shared_ptr<WhiteboardPayload> payload = boost::make_shared<WhiteboardPayload>(WhiteboardPayload::SessionAccept);
+ boost::shared_ptr<GenericRequest<WhiteboardPayload> > request = boost::make_shared<GenericRequest<WhiteboardPayload> >(IQ::Set, toJID_, payload, router_);
+ request->send();
+ onRequestAccepted(toJID_);
+ }
+
+ void IncomingWhiteboardSession::handleIncomingOperation(WhiteboardOperation::ref operation) {
+ WhiteboardClient::Result pairResult = client.handleServerOperationReceived(operation);
+ if (pairResult.client) {
+ if (pairResult.client->getPos() != -1) {
+ onOperationReceived(pairResult.client);
+ }
+ lastOpID = pairResult.client->getID();
+ }
+
+ if (pairResult.server) {
+ WhiteboardPayload::ref payload = boost::make_shared<WhiteboardPayload>();
+ payload->setOperation(pairResult.server);
+ sendPayload(payload);
+ }
+ }
+
+ void IncomingWhiteboardSession::sendOperation(WhiteboardOperation::ref operation) {
+ operation->setID(idGenerator.generateID());
+ operation->setParentID(lastOpID);
+ lastOpID = operation->getID();
+
+ WhiteboardOperation::ref result = client.handleLocalOperationReceived(operation);
+
+ if (result) {
+ WhiteboardPayload::ref payload = boost::make_shared<WhiteboardPayload>();
+ payload->setOperation(result);
+ sendPayload(payload);
+ }
+ }
+}
diff --git a/Swiften/Whiteboard/IncomingWhiteboardSession.h b/Swiften/Whiteboard/IncomingWhiteboardSession.h
new file mode 100644
index 0000000..beaf267
--- /dev/null
+++ b/Swiften/Whiteboard/IncomingWhiteboardSession.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Whiteboard/WhiteboardClient.h>
+#include <boost/shared_ptr.hpp>
+
+namespace Swift {
+ class IncomingWhiteboardSession : public WhiteboardSession {
+ public:
+ typedef boost::shared_ptr<IncomingWhiteboardSession> ref;
+
+ public:
+ IncomingWhiteboardSession(const JID& jid, IQRouter* router);
+ ~IncomingWhiteboardSession();
+
+ void accept();
+
+ private:
+ void handleIncomingOperation(WhiteboardOperation::ref operation);
+ void sendOperation(WhiteboardOperation::ref operation);
+
+ WhiteboardClient client;
+ };
+}
diff --git a/Swiften/Whiteboard/OutgoingWhiteboardSession.cpp b/Swiften/Whiteboard/OutgoingWhiteboardSession.cpp
new file mode 100644
index 0000000..ad81daa
--- /dev/null
+++ b/Swiften/Whiteboard/OutgoingWhiteboardSession.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/OutgoingWhiteboardSession.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/bind.hpp>
+#include <Swiften/Elements/WhiteboardPayload.h>
+
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h>
+
+namespace Swift {
+ OutgoingWhiteboardSession::OutgoingWhiteboardSession(const JID& jid, IQRouter* router) : WhiteboardSession(jid, router) {
+ }
+
+ OutgoingWhiteboardSession::~OutgoingWhiteboardSession() {
+ }
+
+ void OutgoingWhiteboardSession::startSession() {
+ boost::shared_ptr<WhiteboardPayload> payload = boost::make_shared<WhiteboardPayload>(WhiteboardPayload::SessionRequest);
+ boost::shared_ptr<GenericRequest<WhiteboardPayload> > request = boost::make_shared<GenericRequest<WhiteboardPayload> >(IQ::Set, toJID_, payload, router_);
+ request->onResponse.connect(boost::bind(&OutgoingWhiteboardSession::handleRequestResponse, this, _1, _2));
+ request->send();
+ }
+
+ void OutgoingWhiteboardSession::handleRequestResponse(boost::shared_ptr<WhiteboardPayload> /*payload*/, ErrorPayload::ref error) {
+ if (error) {
+ onRequestRejected(toJID_);
+ }
+ }
+
+ void OutgoingWhiteboardSession::handleIncomingOperation(WhiteboardOperation::ref operation) {
+ WhiteboardOperation::ref op = server.handleClientOperationReceived(operation);
+ if (op->getPos() != -1) {
+ onOperationReceived(op);
+ }
+ lastOpID = op->getID();
+
+ WhiteboardPayload::ref payload = boost::make_shared<WhiteboardPayload>();
+ payload->setOperation(op);
+ sendPayload(payload);
+ }
+
+ void OutgoingWhiteboardSession::sendOperation(WhiteboardOperation::ref operation) {
+ operation->setID(idGenerator.generateID());
+ operation->setParentID(lastOpID);
+ lastOpID = operation->getID();
+
+ server.handleLocalOperationReceived(operation);
+ WhiteboardPayload::ref payload = boost::make_shared<WhiteboardPayload>();
+ payload->setOperation(operation);
+ sendPayload(payload);
+ }
+}
diff --git a/Swiften/Whiteboard/OutgoingWhiteboardSession.h b/Swiften/Whiteboard/OutgoingWhiteboardSession.h
new file mode 100644
index 0000000..631f7ba
--- /dev/null
+++ b/Swiften/Whiteboard/OutgoingWhiteboardSession.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Whiteboard/WhiteboardServer.h>
+#include <boost/shared_ptr.hpp>
+#include <Swiften/Queries/GenericRequest.h>
+
+namespace Swift {
+ class OutgoingWhiteboardSession : public WhiteboardSession {
+ public:
+ typedef boost::shared_ptr<OutgoingWhiteboardSession> ref;
+
+ public:
+ OutgoingWhiteboardSession(const JID& jid, IQRouter* router);
+ virtual ~OutgoingWhiteboardSession();
+ void startSession();
+
+ private:
+ void handleRequestResponse(boost::shared_ptr<WhiteboardPayload> /*payload*/, ErrorPayload::ref error);
+ void handleIncomingOperation(WhiteboardOperation::ref operation);
+ void sendOperation(WhiteboardOperation::ref operation);
+
+ WhiteboardServer server;
+ };
+}
diff --git a/Swiften/Whiteboard/UnitTest/WhiteboardClientTest.cpp b/Swiften/Whiteboard/UnitTest/WhiteboardClientTest.cpp
new file mode 100644
index 0000000..7d767d3
--- /dev/null
+++ b/Swiften/Whiteboard/UnitTest/WhiteboardClientTest.cpp
@@ -0,0 +1,676 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <Swiften/Whiteboard/WhiteboardClient.h>
+#include <Swiften/Whiteboard/Operations/WhiteboardInsertOperation.h>
+#include <Swiften/Whiteboard/Operations/WhiteboardUpdateOperation.h>
+#include <Swiften/Whiteboard/Operations/WhiteboardDeleteOperation.h>
+#include <Swiften/Whiteboard/Elements/WhiteboardEllipseElement.h>
+
+using namespace Swift;
+
+class WhiteboardClientTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(WhiteboardClientTest);
+ CPPUNIT_TEST(testSynchronize_simplestSync);
+ CPPUNIT_TEST(testSynchronize_withoutTranslation);
+ CPPUNIT_TEST(testSynchronize_nonInterrupted);
+ CPPUNIT_TEST(testSynchronize_clientInterruption);
+ CPPUNIT_TEST(testSynchronize_serverInterruption);
+ CPPUNIT_TEST(testSynchronize_nonInterruptedMixOperations);
+ CPPUNIT_TEST(testSynchronize_nonInterruptedMixOperations2);
+ CPPUNIT_TEST_SUITE_END();
+public:
+
+ /*!
+ * /\
+ * \/
+ * \
+ */
+ void testSynchronize_simplestSync() {
+ WhiteboardClient client;
+ WhiteboardInsertOperation::ref serverOp;
+ serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+ CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation::ref clientOp;
+ clientOp = createInsertOperation("a", "0", 1);
+ WhiteboardEllipseElement::ref aElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setElement(aElement);
+ WhiteboardInsertOperation::ref result;
+ checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+ //Client receives server operation parented off "0", which isn't last client operation
+ //so it have to be transformed against local operations and then transformed value can
+ //be returned to draw
+ serverOp = createInsertOperation("b", "0", 1);
+ WhiteboardEllipseElement::ref bElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ serverOp->setElement(bElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "b", "a", 2, bElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives confirmation from the server about processed "a" operation, it had to
+ //be transformed against "b" on the server side to receive operation parented off "b".
+ //There aren't any waiting operations to send to server, this operation should return
+ //nothing
+ serverOp = createInsertOperation("a", "b", 1);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives local operation, it doesn't have to be transformed against anything
+ //but operation returned to send to the server should be parented off last server
+ //operation, which is "b"
+ clientOp = createInsertOperation("c", "b", 3);
+ WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setElement(cElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "c", "a", 3, cElement);
+
+ //Client receives confirmation from the server about processed "a" operation, it
+ //should be the same operation as it was sent because server didn't have to
+ //transform it
+ clientOp = createInsertOperation("c", "a", 3);
+ clientOp->setElement(cElement);
+ pairResult = client.handleServerOperationReceived(clientOp);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Results:
+ //Client operations:
+ //ID pos
+ //0 0
+ //a 1
+ //b 2
+ //c 3
+ //
+ //Server operations:
+ //ID pos
+ //0 0
+ //b 1
+ //a 1
+ //c 3
+ //
+ //what gives 0abc on both sides
+ }
+
+ /*!
+ * /
+ * /
+ * \
+ */
+ void testSynchronize_withoutTranslation() {
+ WhiteboardClient client;
+ WhiteboardInsertOperation::ref serverOp;
+ serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+ CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation::ref clientOp = createInsertOperation("c", "0", 1);
+ WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setElement(cElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "c", "0", 1, cElement);
+
+ //Client receives second local operation, client didn't receive ack about previous
+ //operation from the server so it can't be send.
+ clientOp = createInsertOperation("d", "c", 2);
+ WhiteboardEllipseElement::ref dElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setElement(dElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientOp));
+
+ //Client receives confirmation about processing "c" operation, it should be the
+ //same as sent operation because it wasn't transformed, client could send now
+ //operation "d"
+ clientOp = createInsertOperation("c", "0", 1);
+ clientOp->setElement(cElement);
+ pairResult = client.handleServerOperationReceived(clientOp);
+ checkOperation(pairResult.server, "d", "c", 2, dElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+ //Client receives confirmation about processing "d", it should be the same as
+ //sent operation. There aren't any operations in queue to send.
+ clientOp = createInsertOperation("d", "c", 2);
+ clientOp->setElement(dElement);
+ pairResult = client.handleServerOperationReceived(clientOp);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives new operation from server, it's parented off "d" which is at
+ //the end of local history so it doesn't have to be transformed, so operation
+ //to pass to window should be the same
+ serverOp = createInsertOperation("e", "d", 3);
+ WhiteboardEllipseElement::ref eElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ serverOp->setElement(eElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ WhiteboardInsertOperation::ref result = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client);
+ CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+
+ //Client operations:
+ //ID pos
+ //0 0
+ //c 1
+ //d 2
+ //e 3
+ //
+ //Server operations:
+ //ID pos
+ //0 0
+ //c 1
+ //d 2
+ //e 3
+ }
+
+ /*!
+ * /\
+ * / \
+ * \ /
+ * \/
+ */
+ void testSynchronize_nonInterrupted() {
+ WhiteboardClient client;
+ WhiteboardInsertOperation::ref serverOp;
+ serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+ CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation::ref clientOp;
+ clientOp = createInsertOperation("a", "0", 1);
+ WhiteboardEllipseElement::ref aElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setElement(aElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+ //Client receives second local operation, client didn't receive ack about previous
+ //operation from the server so it can't be send.
+ clientOp = createInsertOperation("b", "a", 2);
+ WhiteboardEllipseElement::ref bElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setElement(bElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientOp));
+
+ //Client receives new operation from server, it should be transformed against
+ //"a" and "b" before adding to local operations history because it's parented off "0".
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ serverOp = createInsertOperation("c", "0", 1);
+ WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ serverOp->setElement(cElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "c", "b", 3, cElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives new operation from server, it should be transformed against
+ //results of previous transformations, returned operation should be parented off
+ //"c" existing in local history.
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ serverOp = createInsertOperation("d", "c", 2);
+ WhiteboardEllipseElement::ref dElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ serverOp->setElement(dElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "d", "c", 4, dElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives confirmation about processing "a", it should send next operation
+ //to server which is "b", but it should be version parented of transformed "a"
+ serverOp = createInsertOperation("a", "d", 1);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.server, "b", "a", 2, bElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+
+ //Client receives confirmation about processing "b", there aren't any operations
+ //waiting so it should return nothing.
+ serverOp = createInsertOperation("b", "a", 2);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client operations:
+ //ID pos
+ //0 0
+ //a 1
+ //b 2
+ //c 3
+ //d 4
+ //
+ //Server operations:
+ //ID pos
+ //0 0
+ //c 1
+ //d 2
+ //a 1
+ //b 2
+ //
+ //what gives 0abcd on both sides.
+ }
+
+ /*!
+ * /\
+ * / \
+ * \ /
+ * / /
+ * \/
+ */
+ void testSynchronize_clientInterruption() {
+ WhiteboardClient client;
+ WhiteboardInsertOperation::ref serverOp;
+ serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+ CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation::ref clientOp;
+ clientOp = createInsertOperation("a", "0", 1);
+ WhiteboardEllipseElement::ref aElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setElement(aElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+ //Client receives second local operation, client didn't receive ack about previous
+ //operation from the server so it can't be send.
+ clientOp = createInsertOperation("b", "a", 2);
+ WhiteboardEllipseElement::ref bElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setElement(bElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientOp));
+
+ //Client receives new operation from server, it should be transformed against
+ //"a" and "b" before adding to local operations history because it's parented off "0".
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ serverOp = createInsertOperation("c", "0", 1);
+ WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ serverOp->setElement(cElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "c", "b", 3, cElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives new local operation, client is still waiting for ack so, it
+ //should return nothing
+ clientOp = createInsertOperation("e", "a", 4);
+ WhiteboardEllipseElement::ref eElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setElement(eElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientOp));
+
+ //Client receives new server operation, to add it to local history it should be transformed
+ //against result of previous transformations and operation "e", returned operation should
+ //be parented off "e", which was last local operation
+ serverOp = createInsertOperation("d", "c", 2);
+ WhiteboardEllipseElement::ref dElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ serverOp->setElement(dElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "d", "e", 5, dElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives confirmation about processing "a", it had to be transformed against
+ //"c" and "d" and it is now parented off "d", returned value should be next operation
+ //which have to be send to server("b" parented off server version of "a").
+ serverOp = createInsertOperation("a", "d", 1);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.server, "b", "a", 2, bElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+ //Client receives confirmation about processing "b", it is the same operation as sent because
+ //it didn't have to be transformed, returned value should be next operation
+ //which have to be send to server("e" parented off server version of "b").
+ serverOp = createInsertOperation("b", "a", 2);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.server, "e", "b", 4, eElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+ //Client receives confirmation about processing "b", it is the same operation as sent because
+ //it didn't have to be transformed, there aren't any operations to send so this function returns
+ //nothing
+ serverOp = createInsertOperation("e", "b", 4);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Result:
+ //Client operations:
+ //ID pos
+ //0 0
+ //a 1
+ //b 2
+ //c 3
+ //e 4
+ //d 5
+ //
+ //Server operations:
+ //0 0
+ //c 1
+ //d 2
+ //a 1
+ //b 2
+ //e 4
+ //what gives 0abced on both sides
+ }
+
+ /*!
+ * /\
+ * / /
+ * \ \
+ * \/
+ */
+ void testSynchronize_serverInterruption() {
+ WhiteboardClient client;
+ WhiteboardInsertOperation::ref serverOp;
+ serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+ CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation::ref clientOp;
+ clientOp = createInsertOperation("a", "0", 1);
+ WhiteboardEllipseElement::ref aElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setElement(aElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+ //Client receives second local operation, client didn't receive ack about previous
+ //operation from the server so it can't be send.
+ clientOp = createInsertOperation("b", "a", 2);
+ WhiteboardEllipseElement::ref bElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setElement(bElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientOp));
+
+ //Client receives new operation from server, it should be transformed against
+ //"a" and "b" before adding to local operations history because it's parented off "0".
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ serverOp = createInsertOperation("c", "0", 1);
+ WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ serverOp->setElement(cElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "c", "b", 3, cElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives confirmation about processing "a", it had to be transformed against
+ //"c" and it is now parented off "c", returned value should be next operation
+ //which have to be send to server("b" parented off server version of "a").
+ serverOp = createInsertOperation("a", "c", 1);
+ serverOp->setElement(aElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.server, "b", "a", 2, bElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+ //Client receives new server operation, to add it to local history it should be transformed
+ //against result of previous transformation(but only with transformation of "b"), returned
+ //operation should be parented off "c", which was last local operation
+ serverOp = createInsertOperation("d", "a", 3);
+ WhiteboardEllipseElement::ref dElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ serverOp->setElement(dElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "d", "c", 4, dElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives confirmation about processing "b", it had to be transformed against
+ //"d" because both operations was parented off server version of "a".
+ //there aren't any operations to send so this function returns nothing.
+ serverOp = createInsertOperation("b", "d", 2);
+ serverOp->setElement(bElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client operations:
+ //ID pos
+ //0 0
+ //a 1
+ //b 2
+ //c 3
+ //d 4
+ //
+ //Server operations:
+ //ID pos
+ //0 0
+ //c 1
+ //a 1
+ //d 3
+ //b 2
+ //
+ //what gives 0abcd on both sides
+
+ }
+
+ /*!
+ * /\
+ * / \
+ * \ /
+ * \/
+ */
+ void testSynchronize_nonInterruptedMixOperations() {
+ WhiteboardClient client;
+ WhiteboardInsertOperation::ref serverOp;
+ serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+ CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation::ref clientOp;
+ clientOp = createInsertOperation("a", "0", 1);
+ WhiteboardEllipseElement::ref aElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setElement(aElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+ //Client receives second local operation, client didn't receive ack about previous
+ //operation from the server so it can't be send.
+ WhiteboardUpdateOperation::ref clientUpdateOp = createUpdateOperation("b", "a", 0);
+ WhiteboardEllipseElement::ref bElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientUpdateOp->setElement(bElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientUpdateOp));
+
+ //Client receives new operation from server, it should be transformed against
+ //"a" and "b" before adding to local operations history because it's parented off "0".
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ WhiteboardUpdateOperation::ref serverUpdateOp = createUpdateOperation("c", "0", 0);
+ WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ serverUpdateOp->setElement(cElement);
+ pairResult = client.handleServerOperationReceived(serverUpdateOp);
+ checkOperation(pairResult.client, "c", "b", 0, cElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives new operation from server, it should be transformed against
+ //results of previous transformations, returned operation should be parented off
+ //"c" existing in local history.
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ serverOp = createInsertOperation("d", "c", 1);
+ WhiteboardEllipseElement::ref dElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ serverOp->setElement(dElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "d", "c", 2, dElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives confirmation about processing "a", it should send next operation
+ //to server which is "b", but it should be version parented of transformed "a"
+ serverOp = createInsertOperation("a", "d", 1);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.server, "b", "a", 0, cElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+
+ //Client receives confirmation about processing "b", there aren't any operations
+ //waiting so it should return nothing.
+ serverUpdateOp = createUpdateOperation("b", "a", 0);
+ pairResult = client.handleServerOperationReceived(serverUpdateOp);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client operations:
+ //ID pos
+ //0 0
+ //a 1
+ //b 2
+ //c 3
+ //d 4
+ //
+ //Server operations:
+ //ID pos
+ //0 0
+ //c 1
+ //d 2
+ //a 1
+ //b 2
+ //
+ //what gives 0abcd on both sides.
+ }
+
+ /*!
+ * /\
+ * / \
+ * \ /
+ * \/
+ */
+ void testSynchronize_nonInterruptedMixOperations2() {
+ WhiteboardClient client;
+ WhiteboardInsertOperation::ref serverOp;
+ serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient::Result pairResult = client.handleServerOperationReceived(serverOp);
+ CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ serverOp = createInsertOperation("1", "0", 1);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ CPPUNIT_ASSERT_EQUAL(serverOp, boost::dynamic_pointer_cast<WhiteboardInsertOperation>(pairResult.client));
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation::ref clientOp;
+ WhiteboardUpdateOperation::ref clientUpdateOp;
+ WhiteboardDeleteOperation::ref clientDeleteOp;
+ clientUpdateOp = createUpdateOperation("a", "1", 0);
+ WhiteboardEllipseElement::ref aElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientUpdateOp->setElement(aElement);
+ checkOperation(client.handleLocalOperationReceived(clientUpdateOp), "a", "1", 0, aElement);
+
+ //Client receives second local operation, client didn't receive ack about previous
+ //operation from the server so it can't be send.
+ clientDeleteOp = createDeleteOperation("b", "a", 1);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), client.handleLocalOperationReceived(clientDeleteOp));
+
+ //Client receives new operation from server, it should be transformed against
+ //"a" and "b" before adding to local operations history because it's parented off "0".
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ serverOp = createInsertOperation("c", "1", 2);
+ WhiteboardEllipseElement::ref cElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ serverOp->setElement(cElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "c", "b", 1, cElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives new operation from server, it should be transformed against
+ //results of previous transformations, returned operation should be parented off
+ //"c" existing in local history.
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ WhiteboardUpdateOperation::ref serverUpdateOp = createUpdateOperation("d", "c", 0);
+ WhiteboardEllipseElement::ref dElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ serverUpdateOp->setElement(dElement);
+ pairResult = client.handleServerOperationReceived(serverUpdateOp);
+ checkOperation(pairResult.client, "d", "c", 0, dElement);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client receives confirmation about processing "a", it should send next operation
+ //to server which is "b", but it should be version parented of transformed "a"
+ serverUpdateOp = createUpdateOperation("a", "d", 0);
+ pairResult = client.handleServerOperationReceived(serverUpdateOp);
+ checkOperation(pairResult.server, "b", "a", 1);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+
+
+ //Client receives confirmation about processing "b", there aren't any operations
+ //waiting so it should return nothing.
+ WhiteboardDeleteOperation::ref serverDeleteOp = createDeleteOperation("b", "a", 0);
+ pairResult = client.handleServerOperationReceived(serverDeleteOp);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.client);
+ CPPUNIT_ASSERT_EQUAL(WhiteboardOperation::ref(), pairResult.server);
+
+ //Client operations:
+ //ID pos
+ //0 0
+ //a 1
+ //b 2
+ //c 3
+ //d 4
+ //
+ //Server operations:
+ //ID pos
+ //0 0
+ //c 1
+ //d 2
+ //a 1
+ //b 2
+ //
+ //what gives 0abcd on both sides.
+ }
+
+
+ WhiteboardInsertOperation::ref createInsertOperation(std::string id, std::string parent, int pos) {
+ WhiteboardInsertOperation::ref operation = boost::make_shared<WhiteboardInsertOperation>();
+ operation->setParentID(parent);
+ operation->setID(id);
+ operation->setPos(pos);
+ return operation;
+ }
+
+ WhiteboardUpdateOperation::ref createUpdateOperation(std::string id, std::string parent, int pos) {
+ WhiteboardUpdateOperation::ref operation = boost::make_shared<WhiteboardUpdateOperation>();
+ operation->setParentID(parent);
+ operation->setID(id);
+ operation->setPos(pos);
+ return operation;
+ }
+
+ WhiteboardDeleteOperation::ref createDeleteOperation(std::string id, std::string parent, int pos) {
+ WhiteboardDeleteOperation::ref operation = boost::make_shared<WhiteboardDeleteOperation>();
+ operation->setParentID(parent);
+ operation->setID(id);
+ operation->setPos(pos);
+ return operation;
+ }
+
+ void checkOperation(WhiteboardOperation::ref operation, std::string id, std::string parent, int pos = -1, WhiteboardElement::ref element = WhiteboardElement::ref()) {
+ CPPUNIT_ASSERT_EQUAL(id, operation->getID());
+ CPPUNIT_ASSERT_EQUAL(parent, operation->getParentID());
+ if (pos != -1) {
+ CPPUNIT_ASSERT_EQUAL(pos, operation->getPos());
+ }
+
+ if (element) {
+ WhiteboardInsertOperation::ref insertOp = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(operation);
+ if (insertOp) {
+ CPPUNIT_ASSERT_EQUAL(element, insertOp->getElement());
+ }
+
+ WhiteboardUpdateOperation::ref updateOp = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(operation);
+ if (updateOp) {
+ CPPUNIT_ASSERT_EQUAL(element, updateOp->getElement());
+ }
+ }
+ }
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(WhiteboardClientTest);
diff --git a/Swiften/Whiteboard/UnitTest/WhiteboardServerTest.cpp b/Swiften/Whiteboard/UnitTest/WhiteboardServerTest.cpp
new file mode 100644
index 0000000..563be54
--- /dev/null
+++ b/Swiften/Whiteboard/UnitTest/WhiteboardServerTest.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <Swiften/Whiteboard/WhiteboardServer.h>
+#include <Swiften/Whiteboard/Operations/WhiteboardInsertOperation.h>
+#include <Swiften/Whiteboard/Operations/WhiteboardDeleteOperation.h>
+#include <Swiften/Whiteboard/Operations/WhiteboardUpdateOperation.h>
+#include <Swiften/Whiteboard/Elements/WhiteboardEllipseElement.h>
+
+using namespace Swift;
+
+class WhiteboardServerTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(WhiteboardServerTest);
+ CPPUNIT_TEST(testSimpleOp);
+ CPPUNIT_TEST(testSimpleOp1);
+ CPPUNIT_TEST(testSimpleOp2);
+ CPPUNIT_TEST(testFewSimpleOps);
+ CPPUNIT_TEST_SUITE_END();
+public:
+ void testSimpleOp() {
+ WhiteboardServer server;
+ WhiteboardInsertOperation::ref firstOp = boost::make_shared<WhiteboardInsertOperation>();
+ firstOp->setID("0");
+ server.handleLocalOperationReceived(firstOp);
+ WhiteboardInsertOperation::ref serverOp = boost::make_shared<WhiteboardInsertOperation>();
+ serverOp->setID("b");
+ serverOp->setParentID("0");
+ serverOp->setPos(1);
+ server.handleLocalOperationReceived(serverOp);
+ WhiteboardInsertOperation::ref clientOp = boost::make_shared<WhiteboardInsertOperation>();
+ WhiteboardEllipseElement::ref clientElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setID("a");
+ clientOp->setParentID("0");
+ clientOp->setPos(1);
+ clientOp->setElement(clientElement);
+ WhiteboardInsertOperation::ref op = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(server.handleClientOperationReceived(clientOp));
+ CPPUNIT_ASSERT_EQUAL(std::string("b"), op->getParentID());
+ CPPUNIT_ASSERT_EQUAL(std::string("a"), op->getID());
+ CPPUNIT_ASSERT_EQUAL(1, op->getPos());
+ CPPUNIT_ASSERT_EQUAL(clientElement, boost::dynamic_pointer_cast<WhiteboardEllipseElement>(op->getElement()));
+ }
+
+ void testSimpleOp1() {
+ WhiteboardServer server;
+ WhiteboardInsertOperation::ref firstOp = boost::make_shared<WhiteboardInsertOperation>();
+ firstOp->setID("0");
+ server.handleLocalOperationReceived(firstOp);
+ WhiteboardDeleteOperation::ref serverOp = boost::make_shared<WhiteboardDeleteOperation>();
+ serverOp->setID("b");
+ serverOp->setParentID("0");
+ serverOp->setPos(1);
+ server.handleLocalOperationReceived(serverOp);
+ WhiteboardUpdateOperation::ref clientOp = boost::make_shared<WhiteboardUpdateOperation>();
+ WhiteboardEllipseElement::ref clientElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setID("a");
+ clientOp->setParentID("0");
+ clientOp->setPos(1);
+ clientOp->setElement(clientElement);
+ WhiteboardDeleteOperation::ref op = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(server.handleClientOperationReceived(clientOp));
+ CPPUNIT_ASSERT_EQUAL(std::string("b"), op->getParentID());
+ CPPUNIT_ASSERT_EQUAL(std::string("a"), op->getID());
+ CPPUNIT_ASSERT_EQUAL(-1, op->getPos());
+ }
+
+ void testSimpleOp2() {
+ WhiteboardServer server;
+ WhiteboardInsertOperation::ref firstOp = boost::make_shared<WhiteboardInsertOperation>();
+ firstOp->setID("0");
+ server.handleLocalOperationReceived(firstOp);
+ WhiteboardUpdateOperation::ref serverOp = boost::make_shared<WhiteboardUpdateOperation>();
+ serverOp->setID("b");
+ serverOp->setParentID("0");
+ serverOp->setPos(1);
+ server.handleLocalOperationReceived(serverOp);
+ WhiteboardDeleteOperation::ref clientOp = boost::make_shared<WhiteboardDeleteOperation>();
+ clientOp->setID("a");
+ clientOp->setParentID("0");
+ clientOp->setPos(1);
+ WhiteboardDeleteOperation::ref op = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(server.handleClientOperationReceived(clientOp));
+ CPPUNIT_ASSERT_EQUAL(std::string("b"), op->getParentID());
+ CPPUNIT_ASSERT_EQUAL(std::string("a"), op->getID());
+ CPPUNIT_ASSERT_EQUAL(1, op->getPos());
+ }
+
+
+ void testFewSimpleOps() {
+ WhiteboardServer server;
+ WhiteboardInsertOperation::ref firstOp = boost::make_shared<WhiteboardInsertOperation>();
+ firstOp->setID("0");
+ server.handleLocalOperationReceived(firstOp);
+ WhiteboardInsertOperation::ref serverOp = boost::make_shared<WhiteboardInsertOperation>();
+ serverOp->setID("a");
+ serverOp->setParentID("0");
+ serverOp->setPos(1);
+ server.handleLocalOperationReceived(serverOp);
+ serverOp = boost::make_shared<WhiteboardInsertOperation>();
+ serverOp->setID("b");
+ serverOp->setParentID("a");
+ serverOp->setPos(2);
+ server.handleLocalOperationReceived(serverOp);
+ serverOp = boost::make_shared<WhiteboardInsertOperation>();
+ serverOp->setID("c");
+ serverOp->setParentID("b");
+ serverOp->setPos(3);
+ server.handleLocalOperationReceived(serverOp);
+ WhiteboardInsertOperation::ref clientOp = boost::make_shared<WhiteboardInsertOperation>();
+ WhiteboardEllipseElement::ref clientElement = boost::make_shared<WhiteboardEllipseElement>(0,0,0,0);
+ clientOp->setID("d");
+ clientOp->setParentID("0");
+ clientOp->setPos(1);
+ clientOp->setElement(clientElement);
+ WhiteboardInsertOperation::ref op = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(server.handleClientOperationReceived(clientOp));
+ CPPUNIT_ASSERT_EQUAL(std::string("c"), op->getParentID());
+ CPPUNIT_ASSERT_EQUAL(std::string("d"), op->getID());
+ CPPUNIT_ASSERT_EQUAL(1, op->getPos());
+ CPPUNIT_ASSERT_EQUAL(clientElement, boost::dynamic_pointer_cast<WhiteboardEllipseElement>(op->getElement()));
+ }
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(WhiteboardServerTest);
diff --git a/Swiften/Whiteboard/WhiteboardClient.cpp b/Swiften/Whiteboard/WhiteboardClient.cpp
new file mode 100644
index 0000000..4dacc90
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardClient.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/WhiteboardClient.h>
+#include <Swiften/Whiteboard/WhiteboardTransformer.h>
+#include <boost/smart_ptr/make_shared.hpp>
+
+namespace Swift {
+ WhiteboardOperation::ref WhiteboardClient::handleLocalOperationReceived(WhiteboardOperation::ref operation) {
+ localOperations_.push_back(operation);
+
+ WhiteboardOperation::ref op;
+ WhiteboardInsertOperation::ref insertOp = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(operation);
+ if (insertOp) {
+ op = boost::make_shared<WhiteboardInsertOperation>(*insertOp);
+ }
+ WhiteboardUpdateOperation::ref updateOp = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(operation);
+ if (updateOp) {
+ op = boost::make_shared<WhiteboardUpdateOperation>(*updateOp);
+ }
+ WhiteboardDeleteOperation::ref deleteOp = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(operation);
+ if (deleteOp) {
+ op = boost::make_shared<WhiteboardDeleteOperation>(*deleteOp);
+ }
+
+ if (bridge_.size() > 0) {
+ op->setParentID(bridge_.back()->getID());
+ }
+ bridge_.push_back(op);
+
+ if (lastSentOperationID_.empty())
+ {
+ WhiteboardInsertOperation::ref insertOp = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(operation);
+ if (insertOp) {
+ op = boost::make_shared<WhiteboardInsertOperation>(*insertOp);
+ }
+ WhiteboardUpdateOperation::ref updateOp = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(operation);
+ if (updateOp) {
+ op = boost::make_shared<WhiteboardUpdateOperation>(*updateOp);
+ }
+ WhiteboardDeleteOperation::ref deleteOp = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(operation);
+ if (deleteOp) {
+ op = boost::make_shared<WhiteboardDeleteOperation>(*deleteOp);
+ }
+
+
+ if (serverOperations_.size() > 0) {
+ op->setParentID(serverOperations_.back()->getID());
+ }
+ lastSentOperationID_ = operation->getID();
+ return op;
+ } else {
+ return WhiteboardOperation::ref();
+ }
+ }
+
+ WhiteboardClient::Result WhiteboardClient::handleServerOperationReceived(WhiteboardOperation::ref operation) {
+ serverOperations_.push_back(operation);
+ Result result;
+// if (localOperations_.empty()) {// || localOperations_.back()->getID() == operation->getParentID()) {
+ //Situation where client and server are in sync
+ if (localOperations_.size() == serverOperations_.size()-1) {
+ localOperations_.push_back(operation);
+// clientOp = operation;
+ result.client = operation;
+ } else if (lastSentOperationID_ == operation->getID()) {
+ //Client received confirmation about own operation and it sends next operation to server
+ if (bridge_.size() > 0 && lastSentOperationID_ == bridge_.front()->getID()) {
+ bridge_.erase(bridge_.begin());
+ }
+
+ if (bridge_.size() > 0 && (bridge_.front())->getParentID() == lastSentOperationID_) {
+ lastSentOperationID_ = (bridge_.front())->getID();
+ result.server = bridge_.front();
+ }
+ if (!result.server) {
+ lastSentOperationID_.clear();
+ }
+ } else {
+ std::list<WhiteboardOperation::ref>::iterator it = bridge_.begin();
+ std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> opPair;
+ WhiteboardOperation::ref temp;
+ opPair = WhiteboardTransformer::transform(*it, operation);
+ temp = opPair.first;
+
+ *it = opPair.second;
+ std::string previousID = (*it)->getID();
+ ++it;
+ for (; it != bridge_.end(); ++it) {
+ opPair = WhiteboardTransformer::transform(*it, temp);
+ temp = opPair.first;
+ *it = opPair.second;
+ (*it)->setParentID(previousID);
+ previousID = (*it)->getID();
+ }
+
+ temp->setParentID(localOperations_.back()->getID());
+ localOperations_.push_back(temp);
+ result.client = temp;
+ }
+
+ return result;
+ }
+
+ void WhiteboardClient::print() {
+ std::list<WhiteboardOperation::ref>::iterator it;
+ std::cout << "Client" << std::endl;
+ for(it = localOperations_.begin(); it != localOperations_.end(); ++it) {
+ std::cout << (*it)->getID() << " " << (*it)->getPos() << std::endl;
+ }
+
+ std::cout << "Server" << std::endl;
+ for(it = serverOperations_.begin(); it != serverOperations_.end(); ++it) {
+ std::cout << (*it)->getID() << " " << (*it)->getPos() << std::endl;
+ }
+ }
+}
diff --git a/Swiften/Whiteboard/WhiteboardClient.h b/Swiften/Whiteboard/WhiteboardClient.h
new file mode 100644
index 0000000..388f948
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardClient.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+#include <list>
+#include <utility>
+
+namespace Swift {
+ class WhiteboardClient {
+ public:
+ struct Result {
+ WhiteboardOperation::ref client;
+ WhiteboardOperation::ref server;
+ };
+ /*!
+ * @return Operation to send
+ */
+ WhiteboardOperation::ref handleLocalOperationReceived(WhiteboardOperation::ref operation);
+ /*!
+ * @return pair.first-element to handle locally, pair.second-element to send to server
+ */
+ Result handleServerOperationReceived(WhiteboardOperation::ref operation);
+ void print();
+
+ private:
+ std::list<WhiteboardOperation::ref> localOperations_;
+ std::list<WhiteboardOperation::ref> serverOperations_;
+ std::list<WhiteboardOperation::ref> bridge_;
+ std::string lastSentOperationID_;
+ };
+}
diff --git a/Swiften/Whiteboard/WhiteboardResponder.cpp b/Swiften/Whiteboard/WhiteboardResponder.cpp
new file mode 100644
index 0000000..f72861f
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardResponder.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/WhiteboardResponder.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <Swiften/Whiteboard/WhiteboardSessionManager.h>
+#include <Swiften/Whiteboard/IncomingWhiteboardSession.h>
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Queries/IQRouter.h>
+
+namespace Swift {
+ WhiteboardResponder::WhiteboardResponder(WhiteboardSessionManager* sessionManager, IQRouter* router) : SetResponder<WhiteboardPayload>(router), sessionManager_(sessionManager), router_(router) {
+ }
+
+ bool WhiteboardResponder::handleSetRequest(const JID& from, const JID& /*to*/, const std::string& id, boost::shared_ptr<WhiteboardPayload> payload) {
+ if (payload->getType() == WhiteboardPayload::SessionRequest) {
+ if (sessionManager_->getSession(from)) {
+ sendError(from, id, ErrorPayload::Conflict, ErrorPayload::Cancel);
+ } else {
+ sendResponse(from, id, boost::shared_ptr<WhiteboardPayload>());
+ IncomingWhiteboardSession::ref session = boost::make_shared<IncomingWhiteboardSession>(from, router_);
+ sessionManager_->handleIncomingSession(session);
+ }
+ } else {
+ sendResponse(from, id, boost::shared_ptr<WhiteboardPayload>());
+ WhiteboardSession::ref session = sessionManager_->getSession(from);
+ if (session != NULL) {
+ session->handleIncomingAction(payload);
+ }
+ }
+ return true;
+ }
+}
diff --git a/Swiften/Whiteboard/WhiteboardResponder.h b/Swiften/Whiteboard/WhiteboardResponder.h
new file mode 100644
index 0000000..c05be23
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardResponder.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Queries/SetResponder.h>
+#include <Swiften/Elements/WhiteboardPayload.h>
+
+namespace Swift {
+ class IQRouter;
+ class WhiteboardSessionManager;
+
+ class WhiteboardResponder : public SetResponder<WhiteboardPayload> {
+ public:
+ WhiteboardResponder(WhiteboardSessionManager* sessionManager, IQRouter* router);
+ bool handleSetRequest(const JID& from, const JID& /*to*/, const std::string& id, boost::shared_ptr<WhiteboardPayload> payload);
+
+ private:
+ WhiteboardSessionManager* sessionManager_;
+ IQRouter* router_;
+ };
+}
diff --git a/Swiften/Whiteboard/WhiteboardServer.cpp b/Swiften/Whiteboard/WhiteboardServer.cpp
new file mode 100644
index 0000000..384372b
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardServer.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/WhiteboardServer.h>
+#include <Swiften/Whiteboard/WhiteboardTransformer.h>
+
+namespace Swift {
+ void WhiteboardServer::handleLocalOperationReceived(WhiteboardOperation::ref operation) {
+ operations_.push_back(operation);
+ }
+
+ WhiteboardOperation::ref WhiteboardServer::handleClientOperationReceived(WhiteboardOperation::ref newOperation) {
+ std::list<WhiteboardOperation::ref>::reverse_iterator it;
+ if (operations_.size() == 0 || newOperation->getParentID() == operations_.back()->getID()) {
+ operations_.push_back(newOperation);
+ return newOperation;
+ }
+ for (it = operations_.rbegin(); it != operations_.rend(); ++it) {
+ WhiteboardOperation::ref operation = *it;
+ while (newOperation->getParentID() == operation->getParentID()) {
+ std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> tResult = WhiteboardTransformer::transform(newOperation, operation);
+
+ if (it == operations_.rbegin()) {
+ operations_.push_back(tResult.second);
+ return tResult.second;
+ } else {
+ newOperation = tResult.second;
+ --it;
+ operation = *it;
+ }
+
+ }
+ }
+ return WhiteboardOperation::ref();
+ }
+
+ void WhiteboardServer::print() {
+ std::list<WhiteboardOperation::ref>::iterator it;
+ std::cout << "Server:" << std::endl;
+ for(it = operations_.begin(); it != operations_.end(); ++it) {
+ std::cout << (*it)->getID() << " " << (*it)->getPos() << std::endl;
+ }
+ }
+
+}
diff --git a/Swiften/Whiteboard/WhiteboardServer.h b/Swiften/Whiteboard/WhiteboardServer.h
new file mode 100644
index 0000000..658254b
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardServer.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+
+#include <list>
+
+namespace Swift {
+ class WhiteboardServer {
+ public:
+ void handleLocalOperationReceived(WhiteboardOperation::ref operation);
+ WhiteboardOperation::ref handleClientOperationReceived(WhiteboardOperation::ref operation);
+ void print();
+
+ private:
+ std::list<WhiteboardOperation::ref> operations_;
+ };
+}
diff --git a/Swiften/Whiteboard/WhiteboardSession.cpp b/Swiften/Whiteboard/WhiteboardSession.cpp
new file mode 100644
index 0000000..cffcf07
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardSession.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/Elements/WhiteboardPayload.h>
+#include <Swiften/Elements/ErrorPayload.h>
+
+#include <iostream>
+
+namespace Swift {
+ WhiteboardSession::WhiteboardSession(const JID& jid, IQRouter* router) : toJID_(jid), router_(router) {
+ }
+
+ WhiteboardSession::~WhiteboardSession() {
+ }
+
+ void WhiteboardSession::handleIncomingAction(boost::shared_ptr<WhiteboardPayload> payload) {
+ switch (payload->getType()) {
+ case WhiteboardPayload::Data:
+ handleIncomingOperation(payload->getOperation());
+ return;
+ case WhiteboardPayload::SessionAccept:
+ onRequestAccepted(toJID_);
+ return;
+ case WhiteboardPayload::SessionTerminate:
+ onSessionTerminated(toJID_);
+ return;
+
+ //handled elsewhere
+ case WhiteboardPayload::SessionRequest:
+
+ case WhiteboardPayload::UnknownType:
+ return;
+ }
+ }
+
+ void WhiteboardSession::sendElement(const WhiteboardElement::ref element) {
+ boost::shared_ptr<WhiteboardPayload> payload = boost::make_shared<WhiteboardPayload>();
+ payload->setElement(element);
+ boost::shared_ptr<GenericRequest<WhiteboardPayload> > request = boost::make_shared<GenericRequest<WhiteboardPayload> >(IQ::Set, toJID_, payload, router_);
+ request->send();
+ }
+
+ void WhiteboardSession::sendPayload(boost::shared_ptr<WhiteboardPayload> payload) {
+ boost::shared_ptr<GenericRequest<WhiteboardPayload> > request = boost::make_shared<GenericRequest<WhiteboardPayload> >(IQ::Set, toJID_, payload, router_);
+ request->send();
+ }
+
+ void WhiteboardSession::cancel() {
+ if (router_->isAvailable()) {
+ boost::shared_ptr<WhiteboardPayload> payload = boost::make_shared<WhiteboardPayload>(WhiteboardPayload::SessionTerminate);
+ boost::shared_ptr<GenericRequest<WhiteboardPayload> > request = boost::make_shared<GenericRequest<WhiteboardPayload> >(IQ::Set, toJID_, payload, router_);
+ request->send();
+ }
+ onSessionTerminated(toJID_);
+ }
+
+ const JID& WhiteboardSession::getTo() const {
+ return toJID_;
+ }
+}
diff --git a/Swiften/Whiteboard/WhiteboardSession.h b/Swiften/Whiteboard/WhiteboardSession.h
new file mode 100644
index 0000000..39fa341
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardSession.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/JID/JID.h>
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Base/IDGenerator.h>
+#include <Swiften/Queries/GenericRequest.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardElement.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardOperation.h>
+
+namespace Swift {
+ class IQRouter;
+ class ErrorPayload;
+ class WhiteboardPayload;
+
+ class WhiteboardSession {
+ public:
+ typedef boost::shared_ptr<WhiteboardSession> ref;
+
+ public:
+ WhiteboardSession(const JID& jid, IQRouter* router);
+ virtual ~WhiteboardSession();
+ void handleIncomingAction(boost::shared_ptr<WhiteboardPayload> payload);
+ void sendElement(const WhiteboardElement::ref element);
+ virtual void sendOperation(WhiteboardOperation::ref operation) = 0;
+ void cancel();
+ const JID& getTo() const;
+
+ public:
+ boost::signal< void(const WhiteboardElement::ref element)> onElementReceived;
+ boost::signal< void(const WhiteboardOperation::ref operation)> onOperationReceived;
+ boost::signal< void(const JID& contact)> onSessionTerminated;
+ boost::signal< void(const JID& contact)> onRequestAccepted;
+ boost::signal< void(const JID& contact)> onRequestRejected;
+
+ private:
+ virtual void handleIncomingOperation(WhiteboardOperation::ref operation) = 0;
+
+ protected:
+ void sendPayload(boost::shared_ptr<WhiteboardPayload> payload);
+
+ JID toJID_;
+ IQRouter* router_;
+ std::string lastOpID;
+ IDGenerator idGenerator;
+ };
+}
diff --git a/Swiften/Whiteboard/WhiteboardSessionManager.cpp b/Swiften/Whiteboard/WhiteboardSessionManager.cpp
new file mode 100644
index 0000000..c8e9a6a
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardSessionManager.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+
+#include <Swiften/Whiteboard/WhiteboardSessionManager.h>
+
+#include <Swiften/Base/foreach.h>
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/bind.hpp>
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/Whiteboard/WhiteboardResponder.h>
+#include <Swiften/Presence/PresenceOracle.h>
+#include "Swiften/Disco/EntityCapsProvider.h"
+
+namespace Swift {
+ WhiteboardSessionManager::WhiteboardSessionManager(IQRouter* router, StanzaChannel* stanzaChannel, PresenceOracle* presenceOracle, EntityCapsProvider* capsProvider) : router_(router), stanzaChannel_(stanzaChannel), presenceOracle_(presenceOracle), capsProvider_(capsProvider) {
+ responder = new WhiteboardResponder(this, router);
+ responder->start();
+ stanzaChannel_->onPresenceReceived.connect(boost::bind(&WhiteboardSessionManager::handlePresenceReceived, this, _1));
+ stanzaChannel_->onAvailableChanged.connect(boost::bind(&WhiteboardSessionManager::handleAvailableChanged, this, _1));
+ }
+
+ WhiteboardSessionManager::~WhiteboardSessionManager() {
+ responder->stop();
+ delete responder;
+ }
+
+ WhiteboardSession::ref WhiteboardSessionManager::getSession(const JID& to) {
+ if (sessions_.find(to) == sessions_.end()) {
+ return boost::shared_ptr<WhiteboardSession>();
+ }
+ return sessions_[to];
+ }
+
+ OutgoingWhiteboardSession::ref WhiteboardSessionManager::createOutgoingSession(const JID& to) {
+ JID fullJID = to;
+ if (fullJID.isBare()) {
+ fullJID = getFullJID(fullJID);
+ }
+ OutgoingWhiteboardSession::ref session = boost::make_shared<OutgoingWhiteboardSession>(fullJID, router_);
+ sessions_[fullJID] = session;
+ session->onSessionTerminated.connect(boost::bind(&WhiteboardSessionManager::deleteSessionEntry, this, _1));
+ session->onRequestRejected.connect(boost::bind(&WhiteboardSessionManager::deleteSessionEntry, this, _1));
+ return session;
+ }
+
+ WhiteboardSession::ref WhiteboardSessionManager::requestSession(const JID& to) {
+ WhiteboardSession::ref session = getSession(to);
+ if (!session) {
+ OutgoingWhiteboardSession::ref outgoingSession = createOutgoingSession(to);
+ outgoingSession->startSession();
+ return outgoingSession;
+ } else {
+ return session;
+ }
+ }
+
+ void WhiteboardSessionManager::handleIncomingSession(IncomingWhiteboardSession::ref session) {
+ sessions_[session->getTo()] = session;
+ session->onSessionTerminated.connect(boost::bind(&WhiteboardSessionManager::deleteSessionEntry, this, _1));
+ onSessionRequest(session);
+ }
+
+ JID WhiteboardSessionManager::getFullJID(const JID& bareJID) {
+ JID fullReceipientJID;
+ int priority = INT_MIN;
+
+ //getAllPresence(bareJID) gives you all presences for the bare JID (i.e. all resources) Remko Tronçon @ 11:11
+ std::vector<Presence::ref> presences = presenceOracle_->getAllPresence(bareJID);
+
+ //iterate over them
+ foreach(Presence::ref pres, presences) {
+ if (pres->getPriority() > priority) {
+ // look up caps from the jid
+ DiscoInfo::ref info = capsProvider_->getCaps(pres->getFrom());
+ if (info && info->hasFeature(DiscoInfo::WhiteboardFeature)) {
+ priority = pres->getPriority();
+ fullReceipientJID = pres->getFrom();
+ }
+ }
+ }
+
+ return fullReceipientJID;
+ }
+
+ void WhiteboardSessionManager::deleteSessionEntry(const JID& contact) {
+ sessions_.erase(contact);
+ }
+
+ void WhiteboardSessionManager::handlePresenceReceived(Presence::ref presence) {
+ if (!presence->isAvailable()) {
+ WhiteboardSession::ref session = getSession(presence->getFrom());
+ if (session) {
+ session->cancel();
+ }
+ }
+ }
+
+ void WhiteboardSessionManager::handleAvailableChanged(bool available) {
+ if (!available) {
+ std::map<JID, WhiteboardSession::ref> sessionsCopy = sessions_;
+ std::map<JID, WhiteboardSession::ref>::iterator it;
+ for (it = sessionsCopy.begin(); it != sessionsCopy.end(); ++it) {
+ it->second->cancel();
+ }
+ }
+ }
+}
diff --git a/Swiften/Whiteboard/WhiteboardSessionManager.h b/Swiften/Whiteboard/WhiteboardSessionManager.h
new file mode 100644
index 0000000..f696eb8
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardSessionManager.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <map>
+
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Whiteboard/WhiteboardSession.h>
+#include <Swiften/Whiteboard/IncomingWhiteboardSession.h>
+#include <Swiften/Whiteboard/OutgoingWhiteboardSession.h>
+
+namespace Swift {
+ class IQRouter;
+ class WhiteboardResponder;
+ class PresenceOracle;
+ class EntityCapsProvider;
+
+ class WhiteboardSessionManager {
+ friend class WhiteboardResponder;
+ public:
+ WhiteboardSessionManager(IQRouter* router, StanzaChannel* stanzaChannel, PresenceOracle* presenceOracle, EntityCapsProvider* capsProvider);
+ ~WhiteboardSessionManager();
+
+ WhiteboardSession::ref getSession(const JID& to);
+ WhiteboardSession::ref requestSession(const JID& to);
+
+ public:
+ boost::signal< void (IncomingWhiteboardSession::ref)> onSessionRequest;
+
+ private:
+ JID getFullJID(const JID& bareJID);
+ OutgoingWhiteboardSession::ref createOutgoingSession(const JID& to);
+ void handleIncomingSession(IncomingWhiteboardSession::ref session);
+ void handlePresenceReceived(Presence::ref presence);
+ void handleAvailableChanged(bool available);
+ void deleteSessionEntry(const JID& contact);
+
+ private:
+ std::map<JID, boost::shared_ptr<WhiteboardSession> > sessions_;
+ IQRouter* router_;
+ StanzaChannel* stanzaChannel_;
+ PresenceOracle* presenceOracle_;
+ EntityCapsProvider* capsProvider_;
+ WhiteboardResponder* responder;
+ };
+}
diff --git a/Swiften/Whiteboard/WhiteboardTransformer.cpp b/Swiften/Whiteboard/WhiteboardTransformer.cpp
new file mode 100644
index 0000000..8b9c927
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardTransformer.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Whiteboard/WhiteboardTransformer.h>
+#include <boost/smart_ptr/make_shared.hpp>
+
+namespace Swift {
+ std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardOperation::ref clientOp, WhiteboardOperation::ref serverOp) {
+ WhiteboardInsertOperation::ref clientInsert = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(clientOp);
+ WhiteboardInsertOperation::ref serverInsert = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(serverOp);
+ WhiteboardUpdateOperation::ref clientUpdate = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(clientOp);
+ WhiteboardUpdateOperation::ref serverUpdate = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(serverOp);
+ WhiteboardDeleteOperation::ref clientDelete = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(clientOp);
+ WhiteboardDeleteOperation::ref serverDelete = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(serverOp);
+ if (clientInsert && serverInsert) {
+ return transform(clientInsert, serverInsert);
+ } else if (clientUpdate && serverUpdate) {
+ return transform(clientUpdate, serverUpdate);
+ } else if (clientInsert && serverUpdate) {
+ return transform(clientInsert, serverUpdate);
+ } else if (clientUpdate && serverInsert) {
+ return transform(clientUpdate, serverInsert);
+ } else if (clientDelete && serverDelete) {
+ return transform(clientDelete, serverDelete);
+ } else if (clientInsert && serverDelete) {
+ return transform(clientInsert, serverDelete);
+ } else if (clientDelete && serverInsert) {
+ return transform(clientDelete, serverInsert);
+ } else if (clientUpdate && serverDelete) {
+ return transform(clientUpdate, serverDelete);
+ } else if (clientDelete && serverUpdate) {
+ return transform(clientDelete, serverUpdate);
+ } else {
+ return std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref>();
+ }
+ }
+
+ std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardInsertOperation::ref clientOp, WhiteboardInsertOperation::ref serverOp) {
+ std::pair<WhiteboardInsertOperation::ref, WhiteboardInsertOperation::ref> result;
+ result.first = boost::make_shared<WhiteboardInsertOperation>(*serverOp);
+ result.first->setParentID(clientOp->getID());
+ result.second = boost::make_shared<WhiteboardInsertOperation>(*clientOp);
+ result.second->setParentID(serverOp->getID());
+ if (clientOp->getPos() <= serverOp->getPos()) {
+ result.first->setPos(result.first->getPos()+1);
+ } else {
+ result.second->setPos(result.second->getPos()+1);
+ }
+ return result;
+ }
+
+ std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardUpdateOperation::ref clientOp, WhiteboardUpdateOperation::ref serverOp) {
+ std::pair<WhiteboardUpdateOperation::ref, WhiteboardUpdateOperation::ref> result;
+ result.first = boost::make_shared<WhiteboardUpdateOperation>(*serverOp);
+ result.first->setParentID(clientOp->getID());
+
+ if (clientOp->getPos() == serverOp->getPos()) {
+ result.second = boost::make_shared<WhiteboardUpdateOperation>(*serverOp);
+ result.second->setID(clientOp->getID());
+ result.second->setParentID(serverOp->getID());
+ } else {
+ result.second = boost::make_shared<WhiteboardUpdateOperation>(*clientOp);
+ result.second->setParentID(serverOp->getID());
+ }
+
+ if (clientOp->getPos() < serverOp->getPos() && clientOp->getNewPos() > serverOp->getPos()) {
+ result.first->setPos(result.first->getPos()-1);
+ if (clientOp->getNewPos() >= serverOp->getNewPos()) {
+ result.first->setNewPos(result.first->getNewPos()-1);
+ }
+ } else if (clientOp->getNewPos() >= serverOp->getNewPos()) {
+ result.first->setNewPos(result.first->getNewPos()-1);
+ }
+
+ if (serverOp->getPos() < clientOp->getPos() && serverOp->getNewPos() > clientOp->getPos()) {
+ result.second->setPos(result.second->getPos()-1);
+ if (serverOp->getNewPos() >= clientOp->getNewPos()) {
+ result.second->setNewPos(result.second->getNewPos()-1);
+ }
+ } else if (serverOp->getNewPos() >= clientOp->getNewPos()) {
+ result.second->setNewPos(result.second->getNewPos()-1);
+ }
+ return result;
+ }
+
+ std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardUpdateOperation::ref clientOp, WhiteboardInsertOperation::ref serverOp) {
+ std::pair<WhiteboardInsertOperation::ref, WhiteboardUpdateOperation::ref> result;
+ result.first = boost::make_shared<WhiteboardInsertOperation>(*serverOp);
+ result.first->setParentID(clientOp->getID());
+ result.second = boost::make_shared<WhiteboardUpdateOperation>(*clientOp);
+ result.second->setParentID(serverOp->getID());
+ if (serverOp->getPos() <= clientOp->getPos()) {
+ result.second->setPos(result.second->getPos()+1);
+ }
+ return result;
+ }
+
+ std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardInsertOperation::ref clientOp, WhiteboardUpdateOperation::ref serverOp) {
+ std::pair<WhiteboardUpdateOperation::ref, WhiteboardInsertOperation::ref> result;
+ result.first = boost::make_shared<WhiteboardUpdateOperation>(*serverOp);
+ result.first->setParentID(clientOp->getID());
+ result.second = boost::make_shared<WhiteboardInsertOperation>(*clientOp);
+ result.second->setParentID(serverOp->getID());
+ if (serverOp->getPos() >= clientOp->getPos()) {
+ result.first->setPos(result.first->getPos()+1);
+ }
+ return result;
+ }
+
+ std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardDeleteOperation::ref clientOp, WhiteboardDeleteOperation::ref serverOp) {
+ std::pair<WhiteboardDeleteOperation::ref, WhiteboardDeleteOperation::ref> result;
+ result.first = boost::make_shared<WhiteboardDeleteOperation>(*serverOp);
+ result.first->setParentID(clientOp->getID());
+ result.second = boost::make_shared<WhiteboardDeleteOperation>(*clientOp);
+ result.second->setParentID(serverOp->getID());
+ if (clientOp->getPos() == -1) {
+ result.second->setPos(-1);
+ }
+ if (serverOp->getPos() == -1) {
+ result.first->setPos(-1);
+ }
+ if (clientOp->getPos() < serverOp->getPos()) {
+ result.first->setPos(result.first->getPos()-1);
+ } else if (clientOp->getPos() > serverOp->getPos()) {
+ result.second->setPos(result.second->getPos()-1);
+ } else {
+ result.first->setPos(-1);
+ result.second->setPos(-1);
+ }
+ return result;
+ }
+
+ std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardInsertOperation::ref clientOp, WhiteboardDeleteOperation::ref serverOp) {
+ std::pair<WhiteboardDeleteOperation::ref, WhiteboardInsertOperation::ref> result;
+ result.first = boost::make_shared<WhiteboardDeleteOperation>(*serverOp);
+ result.first->setParentID(clientOp->getID());
+ result.second = boost::make_shared<WhiteboardInsertOperation>(*clientOp);
+ result.second->setParentID(serverOp->getID());
+ if (clientOp->getPos() <= serverOp->getPos()) {
+ result.first->setPos(result.first->getPos()+1);
+ } else if (serverOp->getPos() != -1) {
+ result.second->setPos(result.second->getPos()-1);
+ }
+ return result;
+ }
+
+ std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardDeleteOperation::ref clientOp, WhiteboardInsertOperation::ref serverOp) {
+ std::pair<WhiteboardInsertOperation::ref, WhiteboardDeleteOperation::ref> result;
+ result.first = boost::make_shared<WhiteboardInsertOperation>(*serverOp);
+ result.first->setParentID(clientOp->getID());
+ result.second = boost::make_shared<WhiteboardDeleteOperation>(*clientOp);
+ result.second->setParentID(serverOp->getID());
+ if (serverOp->getPos() <= clientOp->getPos()) {
+ result.second->setPos(result.second->getPos()+1);
+ } else if (clientOp->getPos() != -1) {
+ result.first->setPos(result.first->getPos()-1);
+ }
+ return result;
+ }
+
+ std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardUpdateOperation::ref clientOp, WhiteboardDeleteOperation::ref serverOp) {
+ std::pair<WhiteboardDeleteOperation::ref, WhiteboardOperation::ref> result;
+ result.first = boost::make_shared<WhiteboardDeleteOperation>(*serverOp);
+ result.first->setParentID(clientOp->getID());
+ WhiteboardUpdateOperation::ref updateOp = boost::make_shared<WhiteboardUpdateOperation>(*clientOp);
+ result.second = updateOp;
+ result.second->setParentID(serverOp->getID());
+ if (clientOp->getPos() == serverOp->getPos()) {
+ WhiteboardDeleteOperation::ref deleteOp = boost::make_shared<WhiteboardDeleteOperation>();
+ result.second = deleteOp;
+ result.second->setPos(-1);
+ result.second->setID(clientOp->getID());
+ result.second->setParentID(serverOp->getID());
+ deleteOp->setElementID(serverOp->getElementID());
+ } else if (clientOp->getPos() > serverOp->getPos() && clientOp->getNewPos() <= serverOp->getPos()) {
+ result.second->setPos(result.second->getPos()-1);
+ } else if (clientOp->getPos() < serverOp->getPos() && clientOp->getNewPos() >= serverOp->getPos()) {
+ updateOp->setNewPos(updateOp->getNewPos()-1);
+ } else if (clientOp->getPos() > serverOp->getPos()) {
+ result.second->setPos(result.second->getPos()-1);
+ updateOp->setNewPos(updateOp->getNewPos()-1);
+ }
+ return result;
+ }
+
+ std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> WhiteboardTransformer::transform(WhiteboardDeleteOperation::ref clientOp, WhiteboardUpdateOperation::ref serverOp) {
+ std::pair<WhiteboardOperation::ref, WhiteboardDeleteOperation::ref> result;
+ WhiteboardUpdateOperation::ref updateOp = boost::make_shared<WhiteboardUpdateOperation>(*serverOp);
+ result.first = updateOp;
+ result.first->setParentID(clientOp->getID());
+ result.second = boost::make_shared<WhiteboardDeleteOperation>(*clientOp);
+ result.second->setParentID(serverOp->getID());
+ if (clientOp->getPos() == serverOp->getPos()) {
+ WhiteboardDeleteOperation::ref deleteOp = boost::make_shared<WhiteboardDeleteOperation>();
+ result.first = deleteOp;
+ result.first->setPos(-1);
+ result.first->setID(serverOp->getID());
+ result.first->setParentID(clientOp->getID());
+ deleteOp->setElementID(clientOp->getElementID());
+ } else if (clientOp->getPos() < serverOp->getPos() && clientOp->getPos() >= serverOp->getNewPos()) {
+ result.first->setPos(result.first->getPos()-1);
+ } else if (clientOp->getPos() > serverOp->getPos() && clientOp->getPos() <= serverOp->getNewPos()) {
+ updateOp->setNewPos(updateOp->getNewPos()-1);
+ } else if (clientOp->getPos() < serverOp->getPos()) {
+ result.first->setPos(result.first->getPos()-1);
+ updateOp->setNewPos(updateOp->getNewPos()-1);
+ }
+ return result;
+ }
+}
diff --git a/Swiften/Whiteboard/WhiteboardTransformer.h b/Swiften/Whiteboard/WhiteboardTransformer.h
new file mode 100644
index 0000000..5811f9f
--- /dev/null
+++ b/Swiften/Whiteboard/WhiteboardTransformer.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2012 Mateusz Piękos
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h>
+#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h>
+#include <utility>
+
+namespace Swift {
+ class WhiteboardTransformer {
+ public:
+ static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardOperation::ref clientOp, WhiteboardOperation::ref serverOp);
+ static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardInsertOperation::ref clientOp, WhiteboardInsertOperation::ref serverOp);
+ static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardUpdateOperation::ref clientOp, WhiteboardUpdateOperation::ref serverOp);
+ static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardUpdateOperation::ref clientOp, WhiteboardInsertOperation::ref serverOp);
+ static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardInsertOperation::ref clientOp, WhiteboardUpdateOperation::ref serverOp);
+ static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardDeleteOperation::ref clientOp, WhiteboardDeleteOperation::ref serverOp);
+ static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardInsertOperation::ref clientOp, WhiteboardDeleteOperation::ref serverOp);
+ static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardDeleteOperation::ref clientOp, WhiteboardInsertOperation::ref serverOp);
+ static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardUpdateOperation::ref clientOp, WhiteboardDeleteOperation::ref serverOp);
+ static std::pair<WhiteboardOperation::ref, WhiteboardOperation::ref> transform(WhiteboardDeleteOperation::ref clientOp, WhiteboardUpdateOperation::ref serverOp);
+ };
+}