diff options
author | Mateusz Piekos <mateuszpiekos@gmail.com> | 2012-05-30 12:40:27 (GMT) |
---|---|---|
committer | Remko Tronçon <git@el-tramo.be> | 2012-09-08 16:14:27 (GMT) |
commit | fe8a8aa031da734f3112184420b372021d9e10c7 (patch) | |
tree | 9f8e0b7d11d7612eb25a8682cf940fa647ca67f2 /Swiften/Whiteboard | |
parent | 0174723efbc1f612433d45c6916a2ad4596b96ba (diff) | |
download | swift-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')
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); + }; +} |