diff --git a/doomsday/libshell/include/de/shell/AbstractLink b/doomsday/libshell/include/de/shell/AbstractLink new file mode 100644 index 0000000000..914749e869 --- /dev/null +++ b/doomsday/libshell/include/de/shell/AbstractLink @@ -0,0 +1 @@ +#include "abstractlink.h" diff --git a/doomsday/libshell/include/de/shell/abstractlink.h b/doomsday/libshell/include/de/shell/abstractlink.h new file mode 100644 index 0000000000..3b618c9b1d --- /dev/null +++ b/doomsday/libshell/include/de/shell/abstractlink.h @@ -0,0 +1,134 @@ +/** @file abstractlink.h Network connection to a server. + * + * @authors Copyright © 2013 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#ifndef LIBSHELL_ABSTRACTLINK_H +#define LIBSHELL_ABSTRACTLINK_H + +#include "libshell.h" +#include +#include +#include +#include +#include + +namespace de { +namespace shell { + +/** + * Abstract network connection to a server. + * + * Derived implementations must provide a protocol for incoming messages. + */ +class LIBSHELL_PUBLIC AbstractLink : public QObject, public Transmitter +{ + Q_OBJECT + +public: + enum Status + { + Disconnected, + Connecting, + Connected + }; + +public: + AbstractLink(); + + virtual ~AbstractLink(); + + /** + * Opens a connection to a server over the network. + * + * @param domain Domain/IP address of the server. + * @param timeout Keep trying until this much time has passed. + */ + void connect(String const &domain, TimeDelta const &timeout = 0); + + /** + * Opens a connection to a server over the network. + * + * @param address Address of the server. + */ + void connect(Address const &address); + + /** + * Takes over an existing socket. + * + * @param openSocket Socket. AbstractLink takes ownership. + */ + void takeOver(Socket *openSocket); + + /** + * Closes the connection. + */ + void disconnect(); + + /** + * Peer address of the link. The address may be a null address if the IP + * address hasn't been resolved yet. + */ + Address address() const; + + /** + * Current status of the connection. + */ + Status status() const; + + /** + * Returns the time when the link was successfully connected. + */ + Time connectedAt() const; + + /** + * Returns the next received packet. The packet has been interpreted + * using the virtual interpret() method. + * + * @return Received packet. Ownership given to caller. Returns @c NULL if + * there are no more packets ready. + */ + Packet *nextPacket(); + + // Transmitter. + void send(IByteArray const &data); + +protected: + virtual Packet *interpret(Message const &msg) = 0; + + /** + * Called immediately after a connection has been formed. + */ + virtual void initiateCommunications() = 0; + +protected slots: + void socketConnected(); + void socketDisconnected(); + +signals: + void addressResolved(); + void connected(); + void disconnected(); + void packetsReady(); + +private: + DENG2_PRIVATE(d) +}; + +} // namespace shell +} // namespace de + +#endif // LIBSHELL_ABSTRACTLINK_H diff --git a/doomsday/libshell/include/de/shell/link.h b/doomsday/libshell/include/de/shell/link.h index df95956e23..f1246c8335 100644 --- a/doomsday/libshell/include/de/shell/link.h +++ b/doomsday/libshell/include/de/shell/link.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -33,18 +34,10 @@ namespace shell { /** * Network connection to a server using the shell protocol. */ -class LIBSHELL_PUBLIC Link : public QObject, public Transmitter +class LIBSHELL_PUBLIC Link : public AbstractLink { Q_OBJECT -public: - enum Status - { - Disconnected, - Connecting, - Connected - }; - public: /** * Opens a connection to a server over the network. @@ -70,47 +63,14 @@ class LIBSHELL_PUBLIC Link : public QObject, public Transmitter virtual ~Link(); - /** - * Peer address of the link. The address may be a null address if the IP - * address hasn't been resolved yet. - */ - Address address() const; - - /** - * Current status of the connection. - */ - Status status() const; - - /** - * Returns the time when the link was successfully connected. - */ - Time connectedAt() const; - /** * Shell protocol for constructing and interpreting packets. */ Protocol &protocol(); - /** - * Returns the next received packet. - * - * @return Received packet. Ownership given to caller. Returns @c NULL if - * there are no more packets ready. - */ - Packet *nextPacket(); - - // Transmitter. - void send(IByteArray const &data); - -protected slots: - void socketConnected(); - void socketDisconnected(); - -signals: - void addressResolved(); - void connected(); - void disconnected(); - void packetsReady(); +protected: + Packet *interpret(Message const &msg); + void initiateCommunications(); private: DENG2_PRIVATE(d) diff --git a/doomsday/libshell/libshell.pro b/doomsday/libshell/libshell.pro index 278fcff6e4..b61513b7f5 100644 --- a/doomsday/libshell/libshell.pro +++ b/doomsday/libshell/libshell.pro @@ -24,6 +24,7 @@ INCLUDEPATH += include # Public headers. HEADERS += \ + include/de/shell/AbstractLink \ include/de/shell/Action \ include/de/shell/ChoiceWidget \ include/de/shell/CommandLineWidget \ @@ -44,6 +45,7 @@ HEADERS += \ include/de/shell/TextRootWidget \ include/de/shell/TextWidget \ \ + include/de/shell/abstractlink.h \ include/de/shell/action.h \ include/de/shell/choicewidget.h \ include/de/shell/commandlinewidget.h \ @@ -67,6 +69,7 @@ HEADERS += \ # Sources and private headers. SOURCES += \ + src/abstractlink.cpp \ src/action.cpp \ src/choicewidget.cpp \ src/commandlinewidget.cpp \ diff --git a/doomsday/libshell/src/abstractlink.cpp b/doomsday/libshell/src/abstractlink.cpp new file mode 100644 index 0000000000..3645fd4164 --- /dev/null +++ b/doomsday/libshell/src/abstractlink.cpp @@ -0,0 +1,208 @@ +/** @file abstractlink.h Network connection to a server. + * + * @authors Copyright © 2013 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "de/shell/AbstractLink" +#include +#include +#include +#include +#include +#include + +namespace de { +namespace shell { + +DENG2_PIMPL(AbstractLink) +{ + String tryingToConnectToHost; + Time startedTryingAt; + TimeDelta timeout; + Address peerAddress; + std::auto_ptr socket; + Status status; + Time connectedAt; + + Instance(Public *i) + : Base(i), + status(Disconnected), + connectedAt(Time::invalidTime()) {} +}; + +AbstractLink::AbstractLink() : d(new Instance(this)) +{} + +AbstractLink::~AbstractLink() +{ + delete d; +} + +void AbstractLink::connect(String const &domain, TimeDelta const &timeout) +{ + disconnect(); + + d->socket.reset(new Socket); + + QObject::connect(d->socket.get(), SIGNAL(addressResolved()), this, SIGNAL(addressResolved())); + QObject::connect(d->socket.get(), SIGNAL(connected()), this, SLOT(socketConnected())); + QObject::connect(d->socket.get(), SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + QObject::connect(d->socket.get(), SIGNAL(messagesReady()), this, SIGNAL(packetsReady())); + + // Fallback to default port. + d->tryingToConnectToHost = domain; + d->socket->setQuiet(true); // we'll be retrying a few times + d->socket->connectToDomain(d->tryingToConnectToHost, 13209 /* default port */); + + d->status = Connecting; + d->startedTryingAt = Time(); + d->timeout = timeout; +} + +void AbstractLink::connect(Address const &address) +{ + disconnect(); + + d->peerAddress = address; + d->socket.reset(new Socket); + + QObject::connect(d->socket.get(), SIGNAL(connected()), this, SLOT(socketConnected())); + QObject::connect(d->socket.get(), SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + QObject::connect(d->socket.get(), SIGNAL(messagesReady()), this, SIGNAL(packetsReady())); + + // Fallback to default port. + if(!d->peerAddress.port()) d->peerAddress.setPort(13209); + + d->socket->connect(d->peerAddress); + + d->status = Connecting; + d->startedTryingAt = Time(); + d->timeout = 0; +} + +void AbstractLink::takeOver(Socket *openSocket) +{ + disconnect(); + + d->peerAddress = openSocket->peerAddress(); + d->socket.reset(openSocket); + + // Note: socketConnected() not used because the socket is already open. + QObject::connect(d->socket.get(), SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + QObject::connect(d->socket.get(), SIGNAL(messagesReady()), this, SIGNAL(packetsReady())); + + d->status = Connected; + d->connectedAt = Time(); +} + +void AbstractLink::disconnect() +{ + if(d->status != Disconnected) + { + DENG2_ASSERT(d->socket.get() != 0); + + d->timeout = 0; + d->socket->close(); // emits signal + + QObject::disconnect(d->socket.get(), SIGNAL(addressResolved()), this, SIGNAL(addressResolved())); + QObject::disconnect(d->socket.get(), SIGNAL(connected()), this, SLOT(socketConnected())); + QObject::disconnect(d->socket.get(), SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + QObject::disconnect(d->socket.get(), SIGNAL(messagesReady()), this, SIGNAL(packetsReady())); + } + + DENG2_ASSERT(d->status == Disconnected); +} + +Address AbstractLink::address() const +{ + if(d->socket->isOpen()) return d->socket->peerAddress(); + return d->peerAddress; +} + +AbstractLink::Status AbstractLink::status() const +{ + return d->status; +} + +Time AbstractLink::connectedAt() const +{ + return d->connectedAt; +} + +Packet *AbstractLink::nextPacket() +{ + if(!d->socket->hasIncoming()) return 0; + + std::auto_ptr data(d->socket->receive()); + Packet *packet = interpret(*data.get()); + if(packet) packet->setFrom(data->address()); + return packet; +} + +void AbstractLink::send(IByteArray const &data) +{ + d->socket->send(data); +} + +void AbstractLink::socketConnected() +{ + LOG_AS("AbstractLink"); + LOG_VERBOSE("Successfully connected to server %s") << d->socket->peerAddress(); + + initiateCommunications(); + + d->status = Connected; + d->connectedAt = Time(); + d->peerAddress = d->socket->peerAddress(); + + emit connected(); +} + +void AbstractLink::socketDisconnected() +{ + LOG_AS("AbstractLink"); + + if(d->status == Connecting) + { + if(d->startedTryingAt.since() < d->timeout) + { + // Let's try again a bit later. + QTimer::singleShot(500, d->socket.get(), SLOT(reconnect())); + return; + } + d->socket->setQuiet(false); + } + + if(!d->peerAddress.isNull()) + { + LOG_INFO("Disconnected from %s") << d->peerAddress; + } + else + { + LOG_INFO("Disconnected"); + } + + d->status = Disconnected; + + emit disconnected(); + + // Slots have now had an opportunity to observe the total + // duration of the connection that has just ended. + d->connectedAt = Time::invalidTime(); +} + +} // namespace shell +} // namespace de diff --git a/doomsday/libshell/src/link.cpp b/doomsday/libshell/src/link.cpp index aed8ff81a4..8476c01b90 100644 --- a/doomsday/libshell/src/link.cpp +++ b/doomsday/libshell/src/link.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace de { @@ -29,76 +30,25 @@ namespace shell { DENG2_PIMPL(Link) { - String tryingToConnectToHost; - Time startedTryingAt; - TimeDelta timeout; - Address peerAddress; - Socket *socket; Protocol protocol; - Status status; - Time connectedAt; - Instance(Public &i) - : Base(i), - socket(0), - status(Disconnected), - connectedAt(Time::invalidTime()) {} - - ~Instance() - { - delete socket; - } + Instance(Public *i) : Base(i) + {} }; -Link::Link(String const &domain, TimeDelta const &timeout) : d(new Instance(*this)) +Link::Link(String const &domain, TimeDelta const &timeout) : d(new Instance(this)) { - d->socket = new Socket; - - connect(d->socket, SIGNAL(addressResolved()), this, SIGNAL(addressResolved())); - connect(d->socket, SIGNAL(connected()), this, SLOT(socketConnected())); - connect(d->socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); - connect(d->socket, SIGNAL(messagesReady()), this, SIGNAL(packetsReady())); - - // Fallback to default port. - d->tryingToConnectToHost = domain; - d->socket->setQuiet(true); // we'll be retrying a few times - d->socket->connectToDomain(d->tryingToConnectToHost, 13209 /* default port */); - - d->status = Connecting; - d->startedTryingAt = Time(); - d->timeout = timeout; + connect(domain, timeout); } -Link::Link(Address const &address) : d(new Instance(*this)) +Link::Link(Address const &address) : d(new Instance(this)) { - d->peerAddress = address; - d->socket = new Socket; - - connect(d->socket, SIGNAL(connected()), this, SLOT(socketConnected())); - connect(d->socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); - connect(d->socket, SIGNAL(messagesReady()), this, SIGNAL(packetsReady())); - - // Fallback to default port. - if(!d->peerAddress.port()) d->peerAddress.setPort(13209); - - d->socket->connect(d->peerAddress); - - d->status = Connecting; - d->startedTryingAt = Time(); - d->timeout = 0; + connect(address); } -Link::Link(Socket *openSocket) : d(new Instance(*this)) +Link::Link(Socket *openSocket) : d(new Instance(this)) { - d->peerAddress = openSocket->peerAddress(); - d->socket = openSocket; - - // Note: socketConnected() not used because the socket is already open. - connect(d->socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); - connect(d->socket, SIGNAL(messagesReady()), this, SIGNAL(packetsReady())); - - d->status = Connected; - d->connectedAt = Time(); + takeOver(openSocket); } Link::~Link() @@ -106,88 +56,20 @@ Link::~Link() delete d; } -Address Link::address() const -{ - if(d->socket->isOpen()) return d->socket->peerAddress(); - return d->peerAddress; -} - -Link::Status Link::status() const -{ - return d->status; -} - -Time Link::connectedAt() const -{ - return d->connectedAt; -} - Protocol &Link::protocol() { return d->protocol; } -Packet *Link::nextPacket() +Packet *Link::interpret(Message const &msg) { - if(!d->socket->hasIncoming()) return 0; - - std::auto_ptr data(d->socket->receive()); - Packet *packet = d->protocol.interpret(*data.get()); - if(packet) packet->setFrom(data->address()); - return packet; + return d->protocol.interpret(msg); } -void Link::send(IByteArray const &data) +void Link::initiateCommunications() { - d->socket->send(data); -} - -void Link::socketConnected() -{ - LOG_AS("Link"); - LOG_VERBOSE("Successfully connected to server %s") << d->socket->peerAddress(); - // Tell the server to switch to shell mode (v1). - *d->socket << String("Shell").toLatin1(); - - d->status = Connected; - d->connectedAt = Time(); - d->peerAddress = d->socket->peerAddress(); - - emit connected(); -} - -void Link::socketDisconnected() -{ - LOG_AS("Link"); - - if(d->status == Connecting) - { - if(d->startedTryingAt.since() < d->timeout) - { - // Let's try again a bit later. - QTimer::singleShot(500, d->socket, SLOT(reconnect())); - return; - } - d->socket->setQuiet(false); - } - - if(!d->peerAddress.isNull()) - { - LOG_INFO("Disconnected from %s") << d->peerAddress; - } - else - { - LOG_INFO("Disconnected"); - } - - d->status = Disconnected; - - emit disconnected(); - - // Slots have now had an opportunity to observe the total - // duration of the connection that has just ended. - d->connectedAt = Time::invalidTime(); + *this << ByteRefArray("Shell", 5); } } // namespace shell