#include <rawcommand.h>
#include <logoutcommand.h>
+#include <uptimecommand.h>
#include <QtDebug>
m_idlePolicy = DoNothingIdlePolicy;
disconnecting = false;
- authCommand = 0;
+ commandsTimedOut = 0;
socket = new QUdpSocket(this);
commandTimer = new QTimer(this);
+ commandTimer->setSingleShot(true);
+
idleTimer = new QTimer(this);
+ idleTimer->setSingleShot(true);
m_localPort = 9001;
m_host = "api.anidb.info";
QObject::connect(authCommand, SIGNAL(replyReady(bool)), this, SLOT(doAuthenticate(bool)));
logoutCommand = new LogoutCommand(this);
+ uptimeCommand = new UptimeCommand(this);
setFloodInterval(5);
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;
sendState->addTransition(this, SIGNAL(queueEmpty()), idleState);
sendState->addTransition(this, SIGNAL(commandSent()), waitState);
+ sendState->addTransition(this, SIGNAL(startAuthentication()), authenticatingState);
waitState->addTransition(commandTimer, SIGNAL(timeout()), sendState);
idleState->addTransition(this, SIGNAL(startSending()), sendState);
idleState->addTransition(idleTimer, SIGNAL(timeout()), idleTimeoutState);
-// idleState->addTransition(this, SIGNAL(startLogout()), logoutState);
idleTimeoutState->addTransition(this, SIGNAL(startSending()), sendState);
-//
-// logoutState->addTransition(this, SIGNAL(loggedOut()), loggedOutState);
recieveState->addTransition(this, SIGNAL(authenticated()), authenticatingState);
-// recieveState->addTransition(this, SIGNAL(loggedOut()), loggedOutState);
recieveState->addTransition(connectedHistoryState);
idleState->invokeMethodOnEntry(this, "enterIdleState");
idleState->invokeMethodOnExit(this, "exitIdleState");
idleTimeoutState->invokeMethodOnEntry(this, "enterIdleTiemoutState");
-// logoutState->invokeMethodOnEntry(this, "enterLogoutState");
-// loggedOutState->invokeMethodOnEntry(this, "enterLoggedOutState");
- recieveState->invokeMethodOnExit(this, "exitRecieveState");
+ recieveState->invokeMethodOnEntry(this, "enterRecieveState");
recieveFailState->invokeMethodOnEntry(this, "enterRecieveFailState");
// ------------ END Methods -------------------
disconnect();
clearCommandQueue();
+ foreach (CommandData *cmd, sentCommands)
+ {
+ if (cmd->command->waitForResult())
+ {
+ delete cmd->command;
+ }
+ else
+ {
+ // Send CLIENT_DESTROYED to indicate that no real reply will come.
+ cmd->command->setRawReply(AbstractCommand::CLIENT_DESTROYED, "", this);
+ }
+ }
+ qDeleteAll(sentCommands);
+
if (!m_sessionId.isEmpty())
{
while (commandTimer->isActive())
QCoreApplication::processEvents();
- sendCommand(new LogoutCommand);
+ sendCommand(new LogoutCommand, true);
socket->waitForBytesWritten(5);
}
}
void AniDBUdpClient::enterAuthenticatingState()
{
qDebug() << "Entering Authenticating State";
- authCommand->setUser(m_user);
- authCommand->setPass(m_pass);
- authCommand->setCompression(m_compression);
- enqueueControlCommand(authCommand, true);
- emit startSending();
+ if (m_sessionId.isEmpty())
+ {
+ authCommand->setUser(m_user);
+ authCommand->setPass(m_pass);
+ authCommand->setCompression(m_compression);
+
+ enqueueControlCommand(authCommand, true);
+ return;
+ }
+ emit authenticated();
}
void AniDBUdpClient::doAuthenticate(bool success)
// Happens if sendState is entered from recv* states.
if (commandTimer->isActive())
{
+qDebug() << "commandTimer activein sendState";
emit commandSent();
return;
}
// Control commands (auth and such) have priority over any other commands.
if (!controlCommandQueue.isEmpty())
{
- sendCommand(controlCommandQueue.dequeue());
+qDebug() << "Sending Control Command";
+ sendCommand(controlCommandQueue.dequeue(), true);
emit commandSent();
return;
}
switch (m_idlePolicy)
{
- case KeepAliveIdlePolicy:
case LogoutIdlePolicy:
idleTimer->start(UDP_API_INACTIVITY_LOGOUT * 1000);
break;
break;
case ImmediateLogoutIdlePolicy:
enqueueControlCommand(logoutCommand);
- emit startSending();
break;
default:
break;
m_sessionId = "";
break;
case KeepAliveIdlePolicy:
- enqueueControlCommand(updateCommand);
- emit startSending();
+ enqueueControlCommand(uptimeCommand);
default:
break;
}
}
-/*
-void AniDBUdpClient::enterLogoutState()
-{
-qDebug() << "Entering Logout State";
- logout();
-}
-
-void AniDBUdpClient::enterLoggedOutState()
-{
-qDebug() << "Entering LoggedOut State";
- m_sessionId = "";
-}
-*/
-void AniDBUdpClient::exitRecieveState()
+void AniDBUdpClient::enterRecieveState()
{
qDebug() << "Entering Recieve State";
while (socket->hasPendingDatagrams())
QByteArray commandId = tmp.mid(0, 5);
+ commandsTimedOut = 0;
+
// Do not parse reply for unknown commands.
if (!sentCommands.contains(commandId))
{
CommandData *commandData = sentCommands.take(commandId);
AbstractCommand *cmd = commandData->command;
+ bool controlCommand = commandData->controlCommand;
delete commandData;
// Requeue command and reauthenticate if not logged in.
case AbstractCommand::LOGIN_FIRST:
case AbstractCommand::INVALID_SESSION:
qDebug() << "LOGIN FIRST required, authing";
- enqueueCommand(cmd);
+ m_sessionId = "";
+ if (controlCommand)
+ enqueueControlCommand(cmd);
+ else
+ enqueueCommand(cmd);
emit startAuthentication();
goto continueLoop;
break;
case AbstractCommand::LOGGED_OUT:
- emit loggedOut();
+ m_sessionId = "";
+ break;
+ case AbstractCommand::BANNED:
+ m_error = BannedError;
+ m_errorString = reply.mid(10);
+ emit connectionError();
+ goto endLoop;
break;
+ case AbstractCommand::INTERNAL_SERVER_ERROR:
+ m_error = ServerError;
+ m_errorString = tr("Internal Server Error");
+ emit connectionError();
+ goto endLoop;
+ break;
+ case AbstractCommand::ANIDB_OUT_OF_SERVICE:
+ m_error = ServerError;
+ m_errorString = tr("AniDB out of service");
+ emit connectionError();
+ goto endLoop;
+ break;
+ case AbstractCommand::SERVER_BUSY:
+ m_error = ServerError;
+ m_errorString = tr("Server busy. Try again later. Wait at least 30 minutes.");
+ emit connectionError();
+ goto endLoop;
default:
+ if (replyCode > 601 && replyCode < 700)
+ {
+ qDebug() << QString("SERVER ERROR %1").arg(replyCode);
+ }
break;
}
// tag + space + replyCode + space = 5 + 1 + 3 + 1
continueLoop:
;
}
+endLoop:
+ ;
}
void AniDBUdpClient::enterRecieveFailState()
void AniDBUdpClient::clearCommandQueue()
{
// Delete all unsent commands that are managed by the client.
- while (!commandQueue.empty())
+ while (!controlCommandQueue.isEmpty())
+ {
+ 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);
+ }
+ }
+
+ while (!commandQueue.isEmpty())
{
AbstractCommand *cmd = commandQueue.dequeue();
if (!cmd->waitForResult())
void AniDBUdpClient::logout()
{
- enqueueCommand(new LogoutCommand);
- emit startSending();
+ if (!m_sessionId.isEmpty())
+ enqueueControlCommand(logoutCommand);
+}
+
+void AniDBUdpClient::commandTimeout(const QByteArray &commandId)
+{
+qDebug() << commandId << "timed out";
+ if (!sentCommands.contains(commandId))
+ return;
+
+ CommandData *cmd = sentCommands.take(commandId);
+
+ if (cmd->controlCommand)
+ enqueueControlCommand(cmd->command);
+ else
+ enqueueCommand(cmd->command);
+
+ delete cmd;
+
+ ++commandsTimedOut;
+
+ emit sendFailed();
}
void AniDBUdpClient::enqueueCommand(AbstractCommand *command, bool first)
if (m_sessionId.length())
datagram += "&s=" + m_sessionId;
- if (command->waitForResult())
- {
- sentCommands[commandId] = new CommandData(command, controlCommand);
- }
- else
- {
- command->deleteLater();
- }
+ CommandData *cmd = new CommandData(command, commandId, controlCommand);
+ QObject::connect(cmd, SIGNAL(timeout(QByteArray)), this, SLOT(commandTimeout(QByteArray)));
+
+ sentCommands[commandId] = cmd;
qDebug() << QString("SENDING datagram:\n\t%1\nto: %2 ([%3]:%4)")
.arg(datagram.constData())
return result;
}
-AniDBUdpClient::CommandData::CommandData(AbstractCommand *command, bool controlCommand)
+CommandData::CommandData(AbstractCommand *command, const QByteArray &commandId, bool controlCommand) : QObject()
{
this->command = command;
this->controlCommand = controlCommand;
-}
+ this->commandId = commandId;
+ connect(&timer, SIGNAL(timeout()), this, SLOT(timerTimeout()));
+ timer.start(AniDBUdpClient::UDP_API_COMMAND_TIMEOUT * 1000);
+}
+void CommandData::timerTimeout()
+{
+ emit timeout(commandId);
+}
class AbstractCommand;
class AuthCommand;
class LogoutCommand;
-class UpdateCommand;
+class UptimeCommand;
+
+class CommandData;
class ANIDBUDPCLIENTSHARED_EXPORT AniDBUdpClient : public QObject
{
- struct CommandData
- {
- AbstractCommand *command;
- bool controlCommand;
- QTimer timeout;
-
- CommandData(AbstractCommand *command, bool controlCommand = false);
- };
+ friend class CommandData;
Q_OBJECT
Q_ENUMS(State Error IdlePolicy AbstractCommand::ReplyCode);
HostUnreachableError,
AuthenticationError,
BannedError,
+ ServerError,
UnknownError,
};
void sendFailed();
void connectionError();
- void notLoggedInError();
-
- void startLogout();
- void loggedOut();
private slots:
void enterErrorState();
void enterIdleState();
void exitIdleState();
void enterIdleTiemoutState();
- void enterLogoutState();
- void enterLoggedOutState();
- void exitRecieveState();
+ void enterRecieveState();
void enterRecieveFailState();
void logout();
+ void commandTimeout(const QByteArray &commandId);
+
+private:
void enqueueCommand(AbstractCommand *command, bool first = false);
void enqueueControlCommand(AbstractCommand *command, bool first = false);
void sendCommand(AbstractCommand *command, bool controlCommand = false);
-private:
+
QByteArray buildCmd(const QString &cmd, const QVariantMap &args);
QByteArray nextCommandId(int len = 5);
bool disconnecting;
+ int commandsTimedOut;
+
AuthCommand *authCommand;
LogoutCommand *logoutCommand;
- UpdateCommand *updateCommand;
+ UptimeCommand *uptimeCommand;
static const int UDP_DATAGRAM_MAXIMUM_SIZE = 1400;
static const int UDP_API_INACTIVITY_UPDATE = 30 * 60;
static const int UDP_API_INACTIVITY_LOGOUT = 35 * 60;
+ static const int UDP_API_COMMAND_TIMEOUT = 10;
+
QtStateMachine *stateMachine;
QtState *errorState;
QtState *disconnectedState;
QtState *connectingState;
QtState *connectedState;
QtState *authenticatingState;
- QtState *authenticatedState;
QtState *logoutState;
QtState *loggedOutState;
QtHistoryState *connectedHistoryState;
};
+class CommandData : QObject
+{
+ friend class AniDBUdpClient;
+
+ Q_OBJECT
+public:
+ AbstractCommand *command;
+ bool controlCommand;
+ QByteArray commandId;
+ QTimer timer;
+
+ CommandData(AbstractCommand *command, const QByteArray &commandId, bool controlCommand = false);
+signals:
+ void timeout(QByteArray commandId);
+private slots:
+ void timerTimeout();
+};
+
#endif // ANIDBUDPCLIENT_H