#include "anidbudpclient.h"
+#include <QtStateMachine>
+#include <QtActionState>
+#include <QtHistoryState>
+#include <QtState>
+
#include <QUdpSocket>
#include <QTimer>
#include <rawcommand.h>
+#include <logoutcommand.h>
#include <QtDebug>
AniDBUdpClient::AniDBUdpClient(QObject *parent) : QObject(parent)
{
qDebug() << "Api instance init!";
- m_state = DisconnectedState;
+
m_error = NoError;
m_errorString;
m_idlePolicy = DoNothingIdlePolicy;
- m_idle = true;
disconnecting = false;
authCommand = 0;
- authenticateOnConnect = false;
socket = new QUdpSocket(this);
- QObject::connect(socket, SIGNAL(readyRead()), this, SLOT(readReplies()));
-
- commandTimer = new QTimer(this);
- QObject::connect(commandTimer, SIGNAL(timeout()), this, SLOT(sendNextCommand()));
+ commandTimer = new QTimer(this);
idleTimer = new QTimer(this);
- QObject::connect(idleTimer, SIGNAL(timeout()), this, SLOT(idleTimeout()));
m_localPort = 9001;
m_host = "api.anidb.info";
authCommand = new AuthCommand(this);
QObject::connect(authCommand, SIGNAL(replyReady(bool)), this, SLOT(doAuthenticate(bool)));
- commandTimer->setSingleShot(false);
setFloodInterval(5);
+
+ stateMachine = new QtStateMachine(this);
+
+ errorState = new QtState;
+ disconnectedState = new QtState;
+ connectingState = new QtState;
+ connectedState = new QtState;
+ authenticatingState = new QtState(connectedState);
+ authenticatedState = new QtState(connectedState);
+ idleState = new QtState(connectedState);
+ idleTimeoutState = new QtState(connectedState);
+ logoutState = new QtState(connectedState);
+ loggedOutState = new QtState(connectedState);
+ sendState = new QtState(connectedState);
+ waitState = new QtState(connectedState);
+ recieveState = new QtState;
+ recieveFailState = new QtState;
+ connectedHistoryState = connectedState->addHistoryState();
+
+ stateMachine->addState(errorState);
+ stateMachine->addState(disconnectedState);
+ stateMachine->addState(connectingState);
+ stateMachine->addState(connectedState);
+ stateMachine->addState(recieveState);
+ stateMachine->addState(recieveFailState);
+ stateMachine->setInitialState(disconnectedState);
+ stateMachine->setErrorState(errorState);
+
+ connectedState->setInitialState(authenticatingState);
+ connectedHistoryState->setDefaultState(authenticatingState);
+ // ------------- Transitions ---------------------
+
+ connectedState->addTransition(this, SIGNAL(startDisconnecting()), disconnectedState);
+ connectedState->addTransition(socket, SIGNAL(readyRead()), recieveState);
+ connectedState->addTransition(this, SIGNAL(sendFailed()), recieveFailState);
+
+ disconnectedState->addTransition(this, SIGNAL(startConnecting()), connectingState);
+
+ connectingState->addTransition(this, SIGNAL(connected()), connectedState);
+
+ authenticatingState->addTransition(this, SIGNAL(startSending()), sendState);
+ authenticatingState->addTransition(this, SIGNAL(authenticated()), sendState);
+
+ sendState->addTransition(this, SIGNAL(queueEmpty()), idleState);
+ sendState->addTransition(this, SIGNAL(commandSent()), waitState);
+
+ waitState->addTransition(commandTimer, SIGNAL(timeout()), sendState);
+
+ idleState->addTransition(this, SIGNAL(startSending()), sendState);
+ idleState->addTransition(idleTimer, SIGNAL(timeout()), idleTimeoutState);
+
+ idleTimeoutState->addTransition(this, SIGNAL(startLogout()), logoutState);
+
+ logoutState->addTransition(this, SIGNAL(loggedOut()), loggedOutState);
+
+ recieveState->addTransition(this, SIGNAL(authenticated()), sendState);
+ recieveState->addTransition(this, SIGNAL(loggedOut()), loggedOutState);
+
+ recieveState->addTransition(connectedHistoryState);
+
+ recieveFailState->addTransition(connectedHistoryState);
+ // ------------ END Transitions -------------------
+
+ // ------------- Methods ---------------------
+ errorState->invokeMethodOnEntry(this, "enterErrorState");
+ disconnectedState->invokeMethodOnEntry(this, "enterDisconnectedState");
+ connectingState->invokeMethodOnEntry(this, "enterConnectingState");
+ connectedState->invokeMethodOnEntry(this, "enterConnectedState");
+ authenticatingState->invokeMethodOnEntry(this, "enterAuthenticatingState");
+ sendState->invokeMethodOnEntry(this, "enterSendState");
+ waitState->invokeMethodOnEntry(this, "enterWaitState");
+ idleState->invokeMethodOnEntry(this, "enterIdleState");
+ idleState->invokeMethodOnExit(this, "exitIdleState");
+ idleTimeoutState->invokeMethodOnEntry(this, "enterIdleTiemoutState");
+ logoutState->invokeMethodOnEntry(this, "enterLogoutState");
+ loggedOutState->invokeMethodOnEntry(this, "enterLoggedOutState");
+
+ recieveState->invokeMethodOnExit(this, "exitRecieveState");
+ recieveFailState->invokeMethodOnEntry(this, "enterRecieveFailState");
+ // ------------ END Methods -------------------
+
+ stateMachine->start();
}
AniDBUdpClient::~AniDBUdpClient()
void AniDBUdpClient::setFloodInterval(int interval)
{
- m_floodInterval = interval;
- commandTimer->setInterval(m_floodInterval * 1000);
+ m_floodInterval = interval * 1000;
}
AniDBUdpClient::IdlePolicy AniDBUdpClient::idlePolicy() const
m_idlePolicy = policy;
}
-
-AniDBUdpClient::State AniDBUdpClient::state() const
-{
- return m_state;
-}
-
AniDBUdpClient::Error AniDBUdpClient::error() const
{
return m_error;
return m_errorString;
}
-bool AniDBUdpClient::isIdle()
+void AniDBUdpClient::enterErrorState()
{
- return m_idle;
+qDebug() << "Entering Error State";
}
-void AniDBUdpClient::clearCommandQueue()
+void AniDBUdpClient::enterDisconnectedState()
{
- // Delete all unsent commands that are managed by the client.
- while (!commandQueue.empty())
- {
- AbstractCommand *cmd = commandQueue.dequeue();
- if (!cmd->waitForResult())
- {
- // These would be deleted anyway
- delete cmd;
- }
- else
- {
- // Send CLIENT_DESTROYED to indicate that no real reply will come.
- cmd->setRawReply(AbstractCommand::CLIENT_DESTROYED, "", this);
- }
- }
+qDebug() << "Entering Disconnected State";
}
-void AniDBUdpClient::connect()
+void AniDBUdpClient::enterConnectingState()
{
-qDebug() << "Conneting";
- if (state() == ReconnectingState)
- {
- authenticate();
- return;
- }
-
- if (state() != DisconnectedState)
- return;
-
- changeState(ConnectingState);
+qDebug() << "Entering Connecting State";
if (!m_hostAddress.isNull())
{
- doConnect();
+ if (socket->bind(QHostAddress::Any, m_localPort))
+ {
+qDebug() << "Successful connection";
+ emit connected();
+ }
+ else
+ {
+ m_error = BindError;
+ m_errorString = socket->errorString();
+qDebug() << QString("Bind on Address: %1 port: %2 failed").arg(m_hostAddress.toString()).arg(m_localPort);
+ emit connectionError();
+ }
return;
}
QHostInfo::lookupHost(m_host, this, SLOT(lookedUp(QHostInfo)));
}
-void AniDBUdpClient::disconnect(bool graceful)
-{
-qDebug() << "Disconneting" << (graceful ? "gracefully" : "");
- if (graceful)
- {
- disconnecting = true;
- return;
- }
- changeState(DisconnectedState);
-}
-
-void AniDBUdpClient::send(AbstractCommand *command)
-{
- if (state() < ConnectingState)
- connect();
-
- enqueueCommand(command);
-}
-
-void AniDBUdpClient::sendRaw(QByteArray command)
-{
-qDebug() << QString("Sending RAW command: %1").arg(command.constData());
- enqueueCommand(new RawCommand(command));
-}
-
void AniDBUdpClient::lookedUp(QHostInfo hostInfo)
{
qDebug() << "Host lookup finished";
if (hostInfo.error() != QHostInfo::NoError)
{
qDebug() << "Lookup failed:" << hostInfo.errorString();
- changeState(ErrorState);
m_error = HostLookupError;
m_errorString = hostInfo.errorString();
+ emit connectionError();
return;
}
m_hostAddress = hostInfo.addresses()[0];
- doConnect();
-}
+ // TODO
+ enterConnectingState();
+}
-void AniDBUdpClient::doConnect()
+void AniDBUdpClient::enterConnectedState()
{
- if (socket->bind(QHostAddress::Any, m_localPort))
- {
-qDebug() << "Successful connection";
- authenticate();
- }
- else
- {
- changeState(ErrorState);
- m_error = BindError;
- m_errorString = socket->errorString();
-qDebug() << QString("Bind on Address: %1 port: %2 failed").arg(m_hostAddress.toString()).arg(m_localPort);
- }
+qDebug() << "Entering Connected State";
+ emit connected();
}
-void AniDBUdpClient::authenticate()
+void AniDBUdpClient::enterAuthenticatingState()
{
+qDebug() << "Entering Authenticating State";
authCommand->setUser(m_user);
authCommand->setPass(m_pass);
enqueueCommand(authCommand, true);
- changeState(ReconnectingState);
+ emit startSending();
}
void AniDBUdpClient::doAuthenticate(bool success)
{
qDebug() << "success!";
m_sessionId = authCommand->sessionId().toUtf8();
- changeState(ConnectedState);
+ emit authenticated();
}
else
{
- changeState(ErrorState);
m_error = AuthenticationError;
+ emit connectionError();
}
authenticateOnConnect = false;
}
-void AniDBUdpClient::logout()
+void AniDBUdpClient::enterSendState()
{
- if (state() != ConnectedState)
- // We are not logged in other states, don't try to logout again.
+qDebug() << "Entering Send State";
+ if (commandQueue.isEmpty())
+ {
+ emit queueEmpty();
return;
-
- enqueueCommand(new RawCommand("LOGOUT"), true);
- changeState(ReconnectingState);
- m_sessionId = "";
+ }
+ sendCommand(commandQueue.dequeue());
+ emit commandSent();
}
-void AniDBUdpClient::enqueueCommand(AbstractCommand *command, bool first)
+void AniDBUdpClient::enterWaitState()
{
- if (first)
- {
- commandQueue.push_front(command);
- }
- else
- {
- commandQueue.enqueue(command);
- }
-
- leaveIdleState();
+qDebug() << "Entering Wait State";
+ commandTimer->start(m_floodInterval);
}
-void AniDBUdpClient::sendNextCommand()
+void AniDBUdpClient::enterIdleState()
{
- if (commandQueue.isEmpty())
+qDebug() << "Entering Idle State";
+ switch (m_idlePolicy)
{
- enterIdleState();
- return;
+ case KeepAliveIdlePolicy:
+ idleTimer->start(UDP_API_INACTIVITY_LOGOUT * 1000);
+ break;
+ case LogoutIdlePolicy:
+ emit startLogout();
+ break;
+ default:
+ break;
}
-
- sendCommand(commandQueue.dequeue());
}
-void AniDBUdpClient::sendCommand(AbstractCommand *command)
+void AniDBUdpClient::exitIdleState()
{
- Command cmdPair = command->rawCommand();
- QByteArray datagram = buildCmd(cmdPair.first, cmdPair.second);
-
- QByteArray commandId = nextCommandId();
-
- datagram += datagram.contains(" ") ? "&" : " ";
- datagram += "tag=" + commandId;
-
- if (m_sessionId.length())
- datagram += "&s=" + m_sessionId;
+qDebug() << "Exiting Idle State";
+ idleTimer->stop();
+}
- if (command->waitForResult())
- {
- sentCommands[commandId] = command;
- }
- else
+void AniDBUdpClient::enterIdleTiemoutState()
+{
+qDebug() << "Entering IdleTiemout State";
+ switch (m_idlePolicy)
{
- command->deleteLater();
+ case KeepAliveIdlePolicy:
+ default:
+ break;
}
+}
-qDebug() << QString("SENDING datagram:\n\t%1\nto: %2 ([%3]:%4)")
- .arg(datagram.constData())
- .arg(m_host)
- .arg(m_hostAddress.toString())
- .arg(m_hostPort);
+void AniDBUdpClient::enterLogoutState()
+{
+qDebug() << "Entering Logout State";
+ enqueueCommand(new LogoutCommand);
+ emit startSending();
+}
- socket->writeDatagram(datagram, m_hostAddress, m_hostPort);
+void AniDBUdpClient::enterLoggedOutState()
+{
+qDebug() << "Entering LoggedOut State";
+ m_sessionId = "";
}
-void AniDBUdpClient::readReplies()
+void AniDBUdpClient::exitRecieveState()
{
+qDebug() << "Entering Recieve State";
while (socket->hasPendingDatagrams())
{
char data[UDP_DATAGRAM_MAXIMUM_SIZE];
AbstractCommand *cmd = sentCommands.take(commandId);
// Requeue command and reauthenticate if not logged in.
- if (replyCode == AbstractCommand::LOGIN_FIRST
- || replyCode == AbstractCommand::INVALID_SESSION)
+ switch (replyCode)
{
+ case AbstractCommand::LOGIN_FIRST:
+ case AbstractCommand::INVALID_SESSION:
qDebug() << "LOGIN FIRST required, authing";
enqueueCommand(cmd);
- authenticate();
- continue;
+ emit startAuthentication();
+ goto continueLoop;
+ break;
+ case AbstractCommand::LOGGED_OUT:
+ emit loggedOut();
+ break;
+ default:
+ break;
}
// tag + space + replyCode + space = 5 + 1 + 3 + 1
reply = reply.mid(10);
cmd->setRawReply(replyCode, reply, this);
+continueLoop:
+ ;
}
}
-void AniDBUdpClient::enterIdleState()
+void AniDBUdpClient::enterRecieveFailState()
{
- if (m_idle)
- return;
-qDebug() << "Entering idle state";
- m_idle = true;
+qDebug() << "Entering RecieveFail State";
+}
- switch (m_idlePolicy)
+// -------------------------------------------------------------------------------------
+
+void AniDBUdpClient::clearCommandQueue()
+{
+ // Delete all unsent commands that are managed by the client.
+ while (!commandQueue.empty())
{
- case DoNothingIdlePolicy:
- case KeepAliveIdlePolicy:
- commandTimer->stop();
- idleTimer->start();
- break;
- case LogoutIdlePolicy:
- default:
- idleTimeout();
- break;
+ AbstractCommand *cmd = commandQueue.dequeue();
+ if (!cmd->waitForResult())
+ {
+ // These would be deleted anyway
+ delete cmd;
+ }
+ else
+ {
+ // Send CLIENT_DESTROYED to indicate that no real reply will come.
+ cmd->setRawReply(AbstractCommand::CLIENT_DESTROYED, "", this);
+ }
}
+}
+void AniDBUdpClient::connect()
+{
+qDebug() << "Conneting";
+ emit startConnecting();
}
-void AniDBUdpClient::leaveIdleState()
+void AniDBUdpClient::disconnect(bool graceful)
{
- // Don't do anything untill connected!
- if (state() < ReconnectingState)
+qDebug() << "Disconneting" << (graceful ? "gracefully" : "");
+ if (graceful)
+ {
+ disconnecting = true;
return;
+ }
+ emit startDisconnecting();
+}
- if (!m_idle)
- return;
-qDebug() << "Leaving idle state";
- m_idle = false;
+void AniDBUdpClient::send(AbstractCommand *command)
+{
+ connect();
- idleTimer->stop();
+ enqueueCommand(command);
+}
+
+void AniDBUdpClient::sendRaw(QByteArray command)
+{
+qDebug() << QString("Sending RAW command: %1").arg(command.constData());
+ enqueueCommand(new RawCommand(command));
+}
- // Do not wait for the timer floodInterval seconds for the first command.
- sendNextCommand();
- commandTimer->start();
+void AniDBUdpClient::logout()
+{
+ emit startLogout();
}
-void AniDBUdpClient::idleTimeout()
+void AniDBUdpClient::enqueueCommand(AbstractCommand *command, bool first)
{
- logout();
+ if (first)
+ {
+ commandQueue.push_front(command);
+ }
+ else
+ {
+ commandQueue.enqueue(command);
+ }
+
+ emit startSending();
}
+void AniDBUdpClient::sendCommand(AbstractCommand *command)
+{
+ Command cmdPair = command->rawCommand();
+ QByteArray datagram = buildCmd(cmdPair.first, cmdPair.second);
+
+ QByteArray commandId = nextCommandId();
+
+ datagram += datagram.contains(" ") ? "&" : " ";
+ datagram += "tag=" + commandId;
+
+ if (m_sessionId.length())
+ datagram += "&s=" + m_sessionId;
+
+ if (command->waitForResult())
+ {
+ sentCommands[commandId] = command;
+ }
+ else
+ {
+ command->deleteLater();
+ }
+
+qDebug() << QString("SENDING datagram:\n\t%1\nto: %2 ([%3]:%4)")
+ .arg(datagram.constData())
+ .arg(m_host)
+ .arg(m_hostAddress.toString())
+ .arg(m_hostPort);
+
+ socket->writeDatagram(datagram, m_hostAddress, m_hostPort);
+}
QByteArray AniDBUdpClient::buildCmd(const QString &cmd, const QVariantMap &args)
{
continue;
}
- // The string version of bool is "true" or "false", but hte API expects 1 or 0
+ // The string version of bool is "true" or "false", but the API expects 1 or 0
QString value;
if (it.value().type() == QVariant::Bool)
{
return result.toUtf8();
}
-void AniDBUdpClient::changeState(State newState)
-{
- if (newState == m_state)
- return;
-
- State oldState = m_state;
-
- // BEFORE statechange
- switch(newState)
- {
- case DisconnectedState:
-
- if (m_sessionId.length())
- logout();
-
- socket->close();
- m_sessionId = "";
- emit disconnected();
-// break;
- case ErrorState:
- commandTimer->stop();
- break;
- default:
- break;
- }
-
- m_state = newState;
-
- // AFTER statechange
- switch (newState)
- {
- case ReconnectingState:
- leaveIdleState();
- break;
- case ConnectedState:
- emit connected();
- break;
- default:
- break;
- }
-
-qDebug() << "State changed from" << oldState << "to" << newState;
- emit stateChanged(newState, oldState);
-}
-
QByteArray AniDBUdpClient::nextCommandId(int len)
{
static const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";