From eda3475756f88098de69d5bea05b328b53d7ec04 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Mon, 11 Apr 2011 20:28:11 +0200
Subject: Added chained connector.

This connector will be useful for fallbacks in case of proxies.

diff --git a/Swiften/Network/ChainedConnector.cpp b/Swiften/Network/ChainedConnector.cpp
new file mode 100644
index 0000000..e08ffc6
--- /dev/null
+++ b/Swiften/Network/ChainedConnector.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/Network/ChainedConnector.h>
+
+#include <boost/bind.hpp>
+
+#include <Swiften/Base/Log.h>
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Network/Connector.h>
+
+using namespace Swift;
+
+ChainedConnector::ChainedConnector(
+		const std::string& hostname, 
+		DomainNameResolver* resolver, 
+		const std::vector<ConnectionFactory*>& connectionFactories, 
+		TimerFactory* timerFactory) : 
+			hostname(hostname), 
+			resolver(resolver), 
+			connectionFactories(connectionFactories), 
+			timerFactory(timerFactory), 
+			timeoutMilliseconds(0) {
+}
+
+void ChainedConnector::setTimeoutMilliseconds(int milliseconds) {
+	timeoutMilliseconds = milliseconds;
+}
+
+void ChainedConnector::start() {
+	SWIFT_LOG(debug) << "Starting queued connector for " << hostname << std::endl;
+
+	connectionFactoryQueue = std::deque<ConnectionFactory*>(connectionFactories.begin(), connectionFactories.end());
+	tryNextConnectionFactory();
+}
+
+void ChainedConnector::stop() {
+	if (currentConnector) {
+		currentConnector->onConnectFinished.disconnect(boost::bind(&ChainedConnector::handleConnectorFinished, this, _1));
+		currentConnector->stop();
+		currentConnector.reset();
+	}
+	finish(boost::shared_ptr<Connection>());
+}
+
+void ChainedConnector::tryNextConnectionFactory() {
+	SWIFT_LOG(debug) << "Trying next connection factory" << std::endl;
+	assert(!currentConnector);
+	if (connectionFactoryQueue.empty()) {
+		SWIFT_LOG(debug) << "No more connection factories" << std::endl;
+		finish(boost::shared_ptr<Connection>());
+	}
+	else {
+		ConnectionFactory* connectionFactory = connectionFactoryQueue.front();
+		connectionFactoryQueue.pop_front();
+		currentConnector = Connector::create(hostname, resolver, connectionFactory, timerFactory);
+		currentConnector->setTimeoutMilliseconds(timeoutMilliseconds);
+		currentConnector->onConnectFinished.connect(boost::bind(&ChainedConnector::handleConnectorFinished, this, _1));
+		currentConnector->start();
+	}
+}
+
+void ChainedConnector::handleConnectorFinished(boost::shared_ptr<Connection> connection) {
+	SWIFT_LOG(debug) << "Connector finished" << std::endl;
+	currentConnector->onConnectFinished.disconnect(boost::bind(&ChainedConnector::handleConnectorFinished, this, _1));
+	currentConnector.reset();
+	if (connection) {
+		finish(connection);
+	}
+	else {
+		tryNextConnectionFactory();
+	}
+}
+
+void ChainedConnector::finish(boost::shared_ptr<Connection> connection) {
+	onConnectFinished(connection);
+}
diff --git a/Swiften/Network/ChainedConnector.h b/Swiften/Network/ChainedConnector.h
new file mode 100644
index 0000000..15b17f3
--- /dev/null
+++ b/Swiften/Network/ChainedConnector.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include <deque>
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/Base/boost_bsignals.h>
+
+namespace Swift {
+	class Connection;
+	class Connector;
+	class ConnectionFactory;
+	class TimerFactory;
+	class DomainNameResolver;
+
+	class ChainedConnector {
+		public:
+			ChainedConnector(const std::string& hostname, DomainNameResolver*, const std::vector<ConnectionFactory*>&, TimerFactory*);
+
+			void setTimeoutMilliseconds(int milliseconds);
+			void start();
+			void stop();
+
+			boost::signal<void (boost::shared_ptr<Connection>)> onConnectFinished;
+
+		private:
+			void finish(boost::shared_ptr<Connection> connection);
+			void tryNextConnectionFactory();
+			void handleConnectorFinished(boost::shared_ptr<Connection>);
+
+		private:
+			std::string hostname;
+			DomainNameResolver* resolver;
+			std::vector<ConnectionFactory*> connectionFactories;
+			TimerFactory* timerFactory;
+			int timeoutMilliseconds;
+			std::deque<ConnectionFactory*> connectionFactoryQueue;
+			boost::shared_ptr<Connector> currentConnector;
+	};
+};
diff --git a/Swiften/Network/SConscript b/Swiften/Network/SConscript
index 420dff5..93732d1 100644
--- a/Swiften/Network/SConscript
+++ b/Swiften/Network/SConscript
@@ -14,6 +14,7 @@ sourceList = [
 			"ConnectionServer.cpp",
 			"DummyConnection.cpp",
 			"FakeConnection.cpp",
+ 			"ChainedConnector.cpp",
  			"Connector.cpp",
 			"TimerFactory.cpp",
 			"DummyTimerFactory.cpp",
diff --git a/Swiften/Network/UnitTest/ChainedConnectorTest.cpp b/Swiften/Network/UnitTest/ChainedConnectorTest.cpp
new file mode 100644
index 0000000..9c4c99d
--- /dev/null
+++ b/Swiften/Network/UnitTest/ChainedConnectorTest.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <boost/bind.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <Swiften/Network/ChainedConnector.h>
+#include <Swiften/Network/Connection.h>
+#include <Swiften/Network/ConnectionFactory.h>
+#include <Swiften/Network/HostAddressPort.h>
+#include <Swiften/Network/StaticDomainNameResolver.h>
+#include <Swiften/Network/DummyTimerFactory.h>
+#include <Swiften/EventLoop/DummyEventLoop.h>
+
+using namespace Swift;
+
+class ChainedConnectorTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(ChainedConnectorTest);
+		CPPUNIT_TEST(testConnect_FirstConnectorSucceeds);
+		CPPUNIT_TEST(testConnect_SecondConnectorSucceeds);
+		CPPUNIT_TEST(testConnect_NoConnectorSucceeds);
+		CPPUNIT_TEST(testStop);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void setUp() {
+			host = HostAddressPort(HostAddress("1.1.1.1"), 1234);
+			eventLoop = new DummyEventLoop();
+			resolver = new StaticDomainNameResolver(eventLoop);
+			resolver->addXMPPClientService("foo.com", host);
+			connectionFactory1 = new MockConnectionFactory(eventLoop, 1);
+			connectionFactory2 = new MockConnectionFactory(eventLoop, 2);
+			timerFactory = new DummyTimerFactory();
+		}
+
+		void tearDown() {
+			delete timerFactory;
+			delete connectionFactory2;
+			delete connectionFactory1;
+			delete resolver;
+			delete eventLoop;
+		}
+
+		void testConnect_FirstConnectorSucceeds() {
+			boost::shared_ptr<ChainedConnector> testling(createConnector());
+			connectionFactory1->connects = true;
+			connectionFactory2->connects = false;
+
+			testling->start();
+			eventLoop->processEvents();
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size()));
+			CPPUNIT_ASSERT(connections[0]);
+			CPPUNIT_ASSERT_EQUAL(1, boost::dynamic_pointer_cast<MockConnection>(connections[0])->id);
+		}
+
+		void testConnect_SecondConnectorSucceeds() {
+			boost::shared_ptr<ChainedConnector> testling(createConnector());
+			connectionFactory1->connects = false;
+			connectionFactory2->connects = true;
+
+			testling->start();
+			eventLoop->processEvents();
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size()));
+			CPPUNIT_ASSERT(connections[0]);
+			CPPUNIT_ASSERT_EQUAL(2, boost::dynamic_pointer_cast<MockConnection>(connections[0])->id);
+		}
+
+		void testConnect_NoConnectorSucceeds() {
+			boost::shared_ptr<ChainedConnector> testling(createConnector());
+			connectionFactory1->connects = false;
+			connectionFactory2->connects = false;
+
+			testling->start();
+			eventLoop->processEvents();
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size()));
+			CPPUNIT_ASSERT(!connections[0]);
+		}
+
+		void testStop() {
+			boost::shared_ptr<ChainedConnector> testling(createConnector());
+			connectionFactory1->connects = true;
+			connectionFactory2->connects = false;
+
+			testling->start();
+			testling->stop();
+			eventLoop->processEvents();
+
+			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(connections.size()));
+			CPPUNIT_ASSERT(!connections[0]);
+		}
+
+	private:
+		boost::shared_ptr<ChainedConnector> createConnector() {
+			std::vector<ConnectionFactory*> factories;
+			factories.push_back(connectionFactory1);
+			factories.push_back(connectionFactory2);
+			boost::shared_ptr<ChainedConnector> connector = boost::make_shared<ChainedConnector>("foo.com", resolver, factories, timerFactory);
+			connector->onConnectFinished.connect(boost::bind(&ChainedConnectorTest::handleConnectorFinished, this, _1));
+			return connector;
+		}
+
+		void handleConnectorFinished(boost::shared_ptr<Connection> connection) {
+			boost::shared_ptr<MockConnection> c(boost::dynamic_pointer_cast<MockConnection>(connection));
+			if (connection) {
+				assert(c);
+			}
+			connections.push_back(c);
+		}
+
+		struct MockConnection : public Connection {
+			public:
+				MockConnection(bool connects, int id, EventLoop* eventLoop) : connects(connects), id(id), eventLoop(eventLoop) {
+				}
+
+				void listen() { assert(false); }
+				void connect(const HostAddressPort&) {
+					eventLoop->postEvent(boost::bind(boost::ref(onConnectFinished), !connects));
+				}
+
+				HostAddressPort getLocalAddress() const { return HostAddressPort(); }
+				void disconnect() { assert(false); }
+				void write(const ByteArray&) { assert(false); }
+
+				bool connects;
+				int id;
+				EventLoop* eventLoop;
+		};
+
+		struct MockConnectionFactory : public ConnectionFactory {
+			MockConnectionFactory(EventLoop* eventLoop, int id) : eventLoop(eventLoop), connects(true), id(id) {
+			}
+
+			boost::shared_ptr<Connection> createConnection() {
+				return boost::make_shared<MockConnection>(connects, id, eventLoop);
+			}
+
+			EventLoop* eventLoop;
+			bool connects;
+			int id;
+		};
+
+	private:
+		HostAddressPort host;
+		DummyEventLoop* eventLoop;
+		StaticDomainNameResolver* resolver;
+		MockConnectionFactory* connectionFactory1;
+		MockConnectionFactory* connectionFactory2;
+		DummyTimerFactory* timerFactory;
+		std::vector< boost::shared_ptr<MockConnection> > connections;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ChainedConnectorTest);
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 12714ae..d66bfb3 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -226,6 +226,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("MUC/UnitTest/MUCTest.cpp"),
 			File("Network/UnitTest/HostAddressTest.cpp"),
 			File("Network/UnitTest/ConnectorTest.cpp"),
+			File("Network/UnitTest/ChainedConnectorTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/BodyParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp"),
-- 
cgit v0.10.2-6-g49f6