From: APTX Date: Fri, 31 Jul 2009 20:35:11 +0000 (+0200) Subject: - Initial, unstable, state machine api X-Git-Url: https://gitweb.tyo.aptx.org/?a=commitdiff_plain;h=a91a1cd1cc570ede7781b9e914ce86e4e9d760af;p=anidbudpclient.git - Initial, unstable, state machine api --- diff --git a/anidbudpclient.cpp b/anidbudpclient.cpp index 94be9ca..1b1a65c 100644 --- a/anidbudpclient.cpp +++ b/anidbudpclient.cpp @@ -1,9 +1,15 @@ #include "anidbudpclient.h" +#include +#include +#include +#include + #include #include #include +#include #include @@ -14,24 +20,18 @@ const int AniDBUdpClient::protocolVersion = PROTOCOL_VERSION; 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"; @@ -40,8 +40,89 @@ qDebug() << "Api instance init!"; 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() @@ -121,8 +202,7 @@ int AniDBUdpClient::floodInterval() const void AniDBUdpClient::setFloodInterval(int interval) { - m_floodInterval = interval; - commandTimer->setInterval(m_floodInterval * 1000); + m_floodInterval = interval * 1000; } AniDBUdpClient::IdlePolicy AniDBUdpClient::idlePolicy() const @@ -135,12 +215,6 @@ void AniDBUdpClient::setIdlePolicy(IdlePolicy policy) m_idlePolicy = policy; } - -AniDBUdpClient::State AniDBUdpClient::state() const -{ - return m_state; -} - AniDBUdpClient::Error AniDBUdpClient::error() const { return m_error; @@ -151,116 +225,70 @@ QString AniDBUdpClient::errorString() const 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) @@ -270,86 +298,84 @@ qDebug() << "doAuthenticate init"; { 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]; @@ -406,66 +432,135 @@ qDebug() << QString("Sending reply to command with id: %1").arg(commandId.constD 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) { @@ -478,7 +573,7 @@ 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) { @@ -497,51 +592,6 @@ QByteArray AniDBUdpClient::buildCmd(const QString &cmd, const QVariantMap &args) 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"; diff --git a/anidbudpclient.h b/anidbudpclient.h index 7a7da46..10e0e33 100644 --- a/anidbudpclient.h +++ b/anidbudpclient.h @@ -11,6 +11,11 @@ #include "authcommand.h" +class QtStateMachine; +class QtState; +class QtHistoryState; + + class QUdpSocket; class QTimer; @@ -33,9 +38,7 @@ class ANIDBUDPCLIENTSHARED_EXPORT AniDBUdpClient : public QObject Send commands in \interval seconds intervals */ Q_PROPERTY(int floodInterval READ floodInterval WRITE setFloodInterval); - Q_PROPERTY(IdlePolicy idlePolicy READ idlePolicy WRITE setIdlePolicy); - Q_PROPERTY(State state READ state); Q_PROPERTY(Error error READ error); Q_PROPERTY(QString errorString READ errorString); @@ -44,15 +47,6 @@ public: static const int clientVersion; static const int protocolVersion; - enum State - { - ErrorState = -1, - DisconnectedState, - ConnectingState, - ReconnectingState, - ConnectedState, - }; - enum Error { NoError, @@ -96,12 +90,9 @@ public: IdlePolicy idlePolicy() const; void setIdlePolicy(IdlePolicy policy); - State state() const; Error error() const; QString errorString() const; - bool isIdle(); - // ---------------- END Properties ---------------- void clearCommandQueue(); @@ -115,43 +106,63 @@ public slots: */ void disconnect(bool graceful = false); - void authenticate(); - void send(AbstractCommand *command); void sendRaw(QByteArray command); signals: + + void startConnecting(); void connected(); + void startDisconnecting(); void disconnected(); - void pong(); - void uptime(); - - void stateChanged(State newState, State oldState); + void startAuthentication(); + void authenticated(); + void authenticationFailure(); + + void startSending(); + void commandSent(); + void queueEmpty(); + + void replyRecieved(); + void sendFailed(); + + void connectionError(); + void notLoggedInError(); + + void startLogout(); + void loggedOut(); private slots: + void enterErrorState(); + void enterDisconnectedState(); + + void enterConnectingState(); void lookedUp(QHostInfo hostInfo); - void doConnect(); - void doAuthenticate(bool success); - void logout(); + void enterConnectedState(); + void enterAuthenticatingState(); + void doAuthenticate(bool success); - void enqueueCommand(AbstractCommand *command, bool first = false); - void sendNextCommand(); - void sendCommand(AbstractCommand *command); + void enterSendState(); + void enterWaitState(); + void enterIdleState(); + void exitIdleState(); + void enterIdleTiemoutState(); + void enterLogoutState(); + void enterLoggedOutState(); - void readReplies(); + void exitRecieveState(); + void enterRecieveFailState(); - void enterIdleState(); - void leaveIdleState(); + void logout(); - void idleTimeout(); + void enqueueCommand(AbstractCommand *command, bool first = false); + void sendCommand(AbstractCommand *command); private: QByteArray buildCmd(const QString &cmd, const QVariantMap &args); - - void changeState(State newState); QByteArray nextCommandId(int len = 5); QTimer *commandTimer; @@ -182,12 +193,10 @@ private: // Misc params IdlePolicy m_idlePolicy; - State m_state; Error m_error; QString m_errorString; bool disconnecting; - bool m_idle; AuthCommand *authCommand; bool authenticateOnConnect; @@ -195,6 +204,26 @@ private: static const int UDP_DATAGRAM_MAXIMUM_SIZE = 1400; static const int UDP_API_INACTIVITY_LOGOUT = 30 * 60; + + QtStateMachine *stateMachine; + QtState *errorState; + QtState *disconnectedState; + QtState *connectingState; + QtState *connectedState; + QtState *authenticatingState; + QtState *authenticatedState; + QtState *logoutState; + QtState *loggedOutState; + + QtState *idleState; + QtState *idleTimeoutState; + QtState *sendState; + QtState *waitState; + + QtState *recieveState; + QtState *recieveFailState; + + QtHistoryState *connectedHistoryState; }; #endif // ANIDBUDPCLIENT_H diff --git a/anidbudpclient.pro b/anidbudpclient.pro index 6ad72ce..c3f0267 100644 --- a/anidbudpclient.pro +++ b/anidbudpclient.pro @@ -5,13 +5,13 @@ QT += network QT -= gui TEMPLATE = lib TARGET = anidbudpclient -static { - message(anidbpudpclinet: Static build) - DESTDIR = ../../build-static +static { + message(anidbpudpclinet: Static build) + DESTDIR = ../../build-static } -!static { - message(anidbpudpclinet: Dynamic build) - DESTDIR = ../../build +!static { + message(anidbpudpclinet: Dynamic build) + DESTDIR = ../../build } INCLUDEPATH += $$PWD DEPENDPATH += $$PWD @@ -21,12 +21,13 @@ SOURCES += anidbudpclient.cpp \ abstractcommand.cpp \ authcommand.cpp \ rawcommand.cpp \ - mylistaddcommand.cpp + mylistaddcommand.cpp \ + logoutcommand.cpp HEADERS += anidbudpclient.h \ anidbudpclient_global.h \ abstractcommand.h \ authcommand.h \ rawcommand.h \ - mylistaddcommand.h - + mylistaddcommand.h \ + logoutcommand.h include(../../lib/qtstatemachine/src/qtstatemachine.pri) diff --git a/anidbudpclient_global.h b/anidbudpclient_global.h index a0a429a..2f39cda 100644 --- a/anidbudpclient_global.h +++ b/anidbudpclient_global.h @@ -4,7 +4,7 @@ #include #define CLIENT_NAME "anidbudpclient" -#define CLIENT_VERSION 0x000001 +#define CLIENT_VERSION 0x000002 #define PROTOCOL_VERSION 3 #if defined(ANIDBUDPCLIENT_LIBRARY) diff --git a/logoutcommand.cpp b/logoutcommand.cpp new file mode 100644 index 0000000..10039b6 --- /dev/null +++ b/logoutcommand.cpp @@ -0,0 +1,13 @@ +#include "logoutcommand.h" + +LogoutCommand::LogoutCommand() +{ +} + +Command LogoutCommand::rawCommand() const +{ + Command command; + command.first = "LOGOUT"; + + return command; +} diff --git a/logoutcommand.h b/logoutcommand.h new file mode 100644 index 0000000..250a1af --- /dev/null +++ b/logoutcommand.h @@ -0,0 +1,14 @@ +#ifndef LOGOUTCOMMAND_H +#define LOGOUTCOMMAND_H + +#include "abstractcommand.h" + +class ANIDBUDPCLIENTSHARED_EXPORT LogoutCommand : public AbstractCommand +{ + Q_OBJECT +public: + LogoutCommand(); + Command rawCommand() const; +}; + +#endif // LOGOUTCOMMAND_H