--- /dev/null
+#include "abstractcommand.h"
+
+AbstractCommand::AbstractCommand(QObject *parent) : QObject(parent)
+{
+ m_replyCode = UNKNOWN_REPLY;
+}
+
+AbstractCommand::~AbstractCommand()
+{
+
+}
+
+Command AbstractCommand::rawCommand() const
+{
+ return Command("", QVariantMap());
+}
+
+bool AbstractCommand::waitForResult() const
+{
+ return false;
+}
+
+void AbstractCommand::setRawReply(ReplyCode replyCode, const QString &reply, AniDBUdpClient *client)
+{
+ Q_UNUSED(client);
+ m_replyCode = replyCode;
+ m_rawReply = reply;
+}
+
+QString AbstractCommand::rawReply() const
+{
+ return m_rawReply;
+}
+
+AbstractCommand::ReplyCode AbstractCommand::replyCode() const
+{
+ return m_replyCode;
+}
--- /dev/null
+#ifndef ABSTRACTCOMMAND_H
+#define ABSTRACTCOMMAND_H
+
+#include "anidbudpclient_global.h"
+#include <QObject>
+#include <QPair>
+#include <QVariantMap>
+
+class AniDBUdpClient;
+
+typedef QPair<QString, QVariantMap> Command;
+
+class ANIDBUDPCLIENTSHARED_EXPORT AbstractCommand : public QObject
+{
+ Q_OBJECT
+ Q_ENUMS(ReplyCode);
+
+public:
+ enum ReplyCode;
+
+ AbstractCommand(QObject *parent = 0);
+ virtual ~AbstractCommand();
+
+ virtual Command rawCommand() const;
+
+ virtual bool waitForResult() const;
+
+ virtual void setRawReply(ReplyCode replyCode, const QString &reply, AniDBUdpClient *client);
+ virtual QString rawReply() const;
+
+ virtual ReplyCode replyCode() const;
+
+signals:
+ void replyReady(bool success = false);
+
+public:
+ enum ReplyCode
+ {
+ CLIENT_DESTROYED = -1,
+ UNKNOWN_REPLY = 0,
+ // POSITIVE 2XX
+ LOGIN_ACCEPTED = 200, //a
+ LOGIN_ACCEPTED_NEW_VER = 201, //a
+ LOGGED_OUT = 203, //a
+ RESOURCE = 205, //d
+ STATS = 206, //b
+ TOP = 207, //b
+ UPTIME = 208, //b
+ ENCRYPTION_ENABLED = 209, //c
+
+ MYLIST_ENTRY_ADDED = 210, //a
+ MYLIST_ENTRY_DELETED = 211, //a
+
+ ADDED_FILE = 214, //e
+ ADDED_STREAM = 215, //e
+
+ ENCODING_CHANGED = 219, //c
+
+ FILE = 220, //a
+ MYLIST = 221, //a
+ MYLIST_STATS = 222, //b
+
+ ANIME = 230, //b
+ ANIME_BEST_MATCH = 231, //b
+ RANDOMANIME = 232, //b
+ ANIME_DESCRIPTION = 233, //b
+
+ EPISODE = 240, //b
+ PRODUCER = 245, //b
+ GROUP = 250, //b
+
+ BUDDY_LIST = 253, //c
+ BUDDY_STATE = 254, //c
+ BUDDY_ADDED = 255, //c
+ BUDDY_DELETED = 256, //c
+ BUDDY_ACCEPTED = 257, //c
+ BUDDY_DENIED = 258, //c
+
+ VOTED = 260, //b
+ VOTE_FOUND = 261, //b
+ VOTE_UPDATED = 262, //b
+ VOTE_REVOKED = 263, //b
+
+ NOTIFICATION_ENABLED = 270, //a
+ NOTIFICATION_NOTIFY = 271, //a
+ NOTIFICATION_MESSAGE = 272, //a
+ NOTIFICATION_BUDDY = 273, //c
+ NOTIFICATION_SHUTDOWN = 274, //c
+ PUSHACK_CONFIRMED = 280, //a
+ NOTIFYACK_SUCCESSFUL_M = 281, //a
+ NOTIFYACK_SUCCESSFUL_N = 282, //a
+ NOTIFICATION = 290, //a
+ NOTIFYLIST = 291, //a
+ NOTIFYGET_MESSAGE = 292, //a
+ NOTIFYGET_NOTIFY = 293, //a
+
+ SENDMSG_SUCCESSFUL = 294, //a
+ USER = 295, //d
+
+ // AFFIRMATIVE/NEGATIVE 3XX
+ PONG = 300, //a
+ AUTHPONG = 301, //c
+ NO_SUCH_RESOURCE = 305, //d
+ API_PASSWORD_NOT_DEFINED = 309, //c
+
+ FILE_ALREADY_IN_MYLIST = 310, //a
+ MYLIST_ENTRY_EDITED = 311, //a
+ MULTIPLE_MYLIST_ENTRIES = 312, //e
+
+ SIZE_HASH_EXISTS = 314, //c
+ INVALID_DATA = 315, //c
+ STREAMNOID_USED = 316, //c
+
+ NO_SUCH_FILE = 320, //a
+ NO_SUCH_ENTRY = 321, //a
+ MULTIPLE_FILES_FOUND = 322, //b
+
+ NO_SUCH_ANIME = 330, //b
+ NO_SUCH_ANIME_DESCRIPTION = 333, //b
+ NO_SUCH_EPISODE = 340, //b
+ NO_SUCH_PRODUCER = 345, //b
+ NO_SUCH_GROUP = 350, //b
+
+ BUDDY_ALREADY_ADDED = 355, //c
+ NO_SUCH_BUDDY = 356, //c
+ BUDDY_ALREADY_ACCEPTED = 357, //c
+ BUDDY_ALREADY_DENIED = 358, //c
+
+ NO_SUCH_VOTE = 360, //b
+ INVALID_VOTE_TYPE = 361, //b
+ INVALID_VOTE_VALUE = 362, //b
+ PERMVOTE_NOT_ALLOWED = 363, //b
+ ALREADY_PERMVOTED = 364, //b
+
+ NOTIFICATION_DISABLED = 370, //a
+ NO_SUCH_PACKET_PENDING = 380, //a
+ NO_SUCH_ENTRY_M = 381, //a
+ NO_SUCH_ENTRY_N = 382, //a
+
+ NO_SUCH_MESSAGE = 392, //a
+ NO_SUCH_NOTIFY = 393, //a
+ NO_SUCH_USER = 394, //a
+
+ // NEGATIVE 4XX
+ NOT_LOGGED_IN = 403, //a
+
+ NO_SUCH_MYLIST_FILE = 410, //a
+ NO_SUCH_MYLIST_ENTRY = 411, //a
+
+
+ // CLIENT SIDE FAILURE 5XX
+ LOGIN_FAILED = 500, //a
+ LOGIN_FIRST = 501, //a
+ ACCESS_DENIED = 502, //a
+ CLIENT_VERSION_OUTDATED = 503, //a
+ CLIENT_BANNED = 504, //a
+ ILLEGAL_INPUT_OR_ACCESS_DENIED = 505, //a
+ INVALID_SESSION = 506, //a
+ NO_SUCH_ENCRYPTION_TYPE = 509, //c
+ ENCODING_NOT_SUPPORTED = 519, //c
+
+ BANNED = 555, //a
+ UNKNOWN_COMMAND = 598, //a
+
+
+ // SERVER SIDE FAILURE 6XX
+ INTERNAL_SERVER_ERROR = 600, //a
+ ANIDB_OUT_OF_SERVICE = 601, //a
+ SERVER_BUSY = 602, //d
+ API_VIOLATION = 666, //a
+ };
+
+protected:
+ QString m_rawReply;
+ ReplyCode m_replyCode;
+};
+
+#endif // ABSTRACTCOMMAND_H
--- /dev/null
+#include "anidbudpclient.h"
+
+#include <QUdpSocket>
+#include <QTimer>
+
+#include <rawcommand.h>
+
+#include <QtDebug>
+
+const QByteArray AniDBUdpClient::clientName = CLIENT_NAME;
+const int AniDBUdpClient::clientVersion = CLIENT_VERSION;
+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;
+
+ 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()));
+
+ idleTimer = new QTimer(this);
+ QObject::connect(idleTimer, SIGNAL(timeout()), this, SLOT(idleTimeout()));
+
+ m_localPort = 9001;
+ m_host = "api.anidb.info";
+ m_hostPort = 9000;
+
+ authCommand = new AuthCommand(this);
+ QObject::connect(authCommand, SIGNAL(replyReady(bool)), this, SLOT(doAuthenticate(bool)));
+
+ commandTimer->setSingleShot(false);
+ setFloodInterval(5);
+}
+
+AniDBUdpClient::~AniDBUdpClient()
+{
+ disconnect();
+ clearCommandQueue();
+}
+
+QString AniDBUdpClient::host() const
+{
+ return m_host;
+}
+
+void AniDBUdpClient::setHost(const QString &host, quint16 port)
+{
+ m_host = host;
+ if (port)
+ m_hostPort = port;
+ m_hostAddress = QHostAddress();
+}
+
+quint16 AniDBUdpClient::hostPort() const
+{
+ return m_hostPort;
+}
+
+void AniDBUdpClient::setHostPort(quint16 port)
+{
+ m_hostPort = port;
+}
+
+quint16 AniDBUdpClient::localPort() const
+{
+ return m_localPort;
+}
+
+void AniDBUdpClient::setLocalPort(quint16 port)
+{
+ m_localPort = port;
+}
+
+QString AniDBUdpClient::user() const
+{
+ return m_user;
+}
+
+void AniDBUdpClient::setUser(const QString &user)
+{
+ // All usernames are lowercaase
+ m_user = user.toLower();
+}
+
+QString AniDBUdpClient::pass() const
+{
+ return m_pass;
+}
+
+void AniDBUdpClient::setPass(const QString &pass)
+{
+ m_pass = pass;
+}
+
+bool AniDBUdpClient::compression() const
+{
+ return m_compression;
+}
+
+void AniDBUdpClient::setCompression(bool compress)
+{
+ m_compression = compress;
+}
+
+int AniDBUdpClient::floodInterval() const
+{
+ return m_floodInterval;
+}
+
+void AniDBUdpClient::setFloodInterval(int interval)
+{
+ m_floodInterval = interval;
+ commandTimer->setInterval(m_floodInterval * 1000);
+}
+
+AniDBUdpClient::IdlePolicy AniDBUdpClient::idlePolicy() const
+{
+ return m_idlePolicy;
+}
+
+void AniDBUdpClient::setIdlePolicy(IdlePolicy policy)
+{
+ m_idlePolicy = policy;
+}
+
+
+AniDBUdpClient::State AniDBUdpClient::state() const
+{
+ return m_state;
+}
+
+AniDBUdpClient::Error AniDBUdpClient::error() const
+{
+ return m_error;
+}
+
+QString AniDBUdpClient::errorString() const
+{
+ return m_errorString;
+}
+
+bool AniDBUdpClient::isIdle()
+{
+ return m_idle;
+}
+
+void AniDBUdpClient::clearCommandQueue()
+{
+ // 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);
+ }
+ }
+}
+
+void AniDBUdpClient::connect()
+{
+qDebug() << "Conneting";
+ if (state() == ReconnectingState)
+ {
+ authenticate();
+ return;
+ }
+
+ if (state() != DisconnectedState)
+ return;
+
+ changeState(ConnectingState);
+
+ if (!m_hostAddress.isNull())
+ {
+ doConnect();
+ 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();
+ return;
+ }
+ m_hostAddress = hostInfo.addresses()[0];
+ doConnect();
+}
+
+
+void AniDBUdpClient::doConnect()
+{
+ 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);
+ }
+}
+
+void AniDBUdpClient::authenticate()
+{
+ authCommand->setUser(m_user);
+ authCommand->setPass(m_pass);
+
+ enqueueCommand(authCommand, true);
+ changeState(ReconnectingState);
+}
+
+void AniDBUdpClient::doAuthenticate(bool success)
+{
+qDebug() << "doAuthenticate init";
+ if (success)
+ {
+qDebug() << "success!";
+ m_sessionId = authCommand->sessionId().toUtf8();
+ changeState(ConnectedState);
+ }
+ else
+ {
+ changeState(ErrorState);
+ m_error = AuthenticationError;
+ }
+
+ authenticateOnConnect = false;
+}
+
+void AniDBUdpClient::logout()
+{
+ if (state() != ConnectedState)
+ // We are not logged in other states, don't try to logout again.
+ return;
+
+ enqueueCommand(new RawCommand("LOGOUT"), true);
+ changeState(ReconnectingState);
+ m_sessionId = "";
+}
+
+void AniDBUdpClient::enqueueCommand(AbstractCommand *command, bool first)
+{
+ if (first)
+ {
+ commandQueue.push_front(command);
+ }
+ else
+ {
+ commandQueue.enqueue(command);
+ }
+
+ leaveIdleState();
+}
+
+void AniDBUdpClient::sendNextCommand()
+{
+ if (commandQueue.isEmpty())
+ {
+ enterIdleState();
+ return;
+ }
+
+ sendCommand(commandQueue.dequeue());
+}
+
+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);
+}
+
+void AniDBUdpClient::readReplies()
+{
+ while (socket->hasPendingDatagrams())
+ {
+ char data[UDP_DATAGRAM_MAXIMUM_SIZE];
+ int size;
+ QHostAddress sender;
+ quint16 senderPort;
+ size = socket->readDatagram(data, UDP_DATAGRAM_MAXIMUM_SIZE, &sender, &senderPort);
+
+ QByteArray tmp(data, size);
+
+ if (sender != m_hostAddress)
+ {
+ qDebug() << QString("Recieved datagram from unknown host: %1 port: %2\nRaw datagram contents:%3\nDiscarding datagram.")
+ .arg(sender.toString())
+ .arg(senderPort)
+ .arg(tmp.constData());
+ continue;
+ }
+
+ if (m_compression && tmp.mid(0, 2) == "00")
+ {
+qDebug() << "COMPRESSED DATAGRAM = " << tmp;
+ tmp = qUncompress(tmp);
+ }
+
+ QString reply = QString::fromUtf8(tmp);
+
+ qDebug() << QString("Recieved datagram from [%1]:%2\nRaw datagram contents:%3")
+ .arg(m_host)
+ .arg(senderPort)
+ .arg(reply);
+
+ QByteArray commandId = tmp.mid(0, 5);
+
+ // Do not parse reply for commands not waiting for a reply.
+ if (!sentCommands.contains(commandId))
+ {
+qDebug() << QString("Command with id: %1 is not waiting for a reply, discarding").arg(commandId.constData());
+ continue;
+ }
+qDebug() << QString("Sending reply to command with id: %1").arg(commandId.constData());
+
+ // tag + space = 5 + 1
+ QByteArray replyCodeText = tmp.mid(6, 3);
+
+ bool ok;
+ int replyCodeInt = replyCodeText.toInt(&ok);
+ AbstractCommand::ReplyCode replyCode = AbstractCommand::UNKNOWN_REPLY;
+ if (ok)
+ {
+ replyCode = AbstractCommand::ReplyCode(replyCodeInt);
+ }
+
+ AbstractCommand *cmd = sentCommands.take(commandId);
+
+ // Requeue command and reauthenticate if not logged in.
+ if (replyCode == AbstractCommand::LOGIN_FIRST
+ || replyCode == AbstractCommand::INVALID_SESSION)
+ {
+qDebug() << "LOGIN FIRST required, authing";
+ enqueueCommand(cmd);
+ authenticate();
+ continue;
+ }
+ // tag + space + replyCode + space = 5 + 1 + 3 + 1
+ reply = reply.mid(10);
+
+ cmd->setRawReply(replyCode, reply, this);
+ }
+}
+
+void AniDBUdpClient::enterIdleState()
+{
+ if (m_idle)
+ return;
+qDebug() << "Entering idle state";
+ m_idle = true;
+
+ switch (m_idlePolicy)
+ {
+ case DoNothingIdlePolicy:
+ case KeepAliveIdlePolicy:
+ commandTimer->stop();
+ idleTimer->start();
+ break;
+ case LogoutIdlePolicy:
+ default:
+ idleTimeout();
+ break;
+ }
+
+}
+
+void AniDBUdpClient::leaveIdleState()
+{
+ // Don't do anything untill connected!
+ if (state() < ReconnectingState)
+ return;
+
+ if (!m_idle)
+ return;
+qDebug() << "Leaving idle state";
+ m_idle = false;
+
+ idleTimer->stop();
+
+ sendNextCommand();
+ commandTimer->start();
+}
+
+void AniDBUdpClient::idleTimeout()
+{
+ logout();
+}
+
+
+QByteArray AniDBUdpClient::buildCmd(const QString &cmd, const QVariantMap &args)
+{
+ QString result = cmd;
+ for (QVariantMap::const_iterator it = args.constBegin(); it != args.constEnd(); ++it)
+ {
+ if (!it.value().canConvert(QVariant::String))
+ {
+ qWarning("Passed value cannot be converted to string!");
+ continue;
+ }
+
+ // The string version of bool is "true" or "false", but hte API expects 1 or 0
+ QString value;
+ if (it.value().type() == QVariant::Bool)
+ {
+ value = it.value().toBool() ? "1" : "0";
+ }
+ else
+ {
+ value = it.value().toString();
+ }
+
+ if (it == args.constBegin())
+ result += QString(" %1=%2").arg(it.key(), value);
+ else
+ result += QString("&%1=%2").arg(it.key(), value);
+ }
+ 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:
+ // Do not wait for the timer floodInterval seconds for the first command.
+ 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";
+ static const int numChars = sizeof(chars) - 1;
+
+ QByteArray result(len, '-');
+ while (len--)
+ result[len] = chars[qrand() % numChars];
+
+qDebug() << QString("Generated id %1").arg(result.constData());
+ return result;
+}
--- /dev/null
+#ifndef ANIDBUDPCLIENT_H
+#define ANIDBUDPCLIENT_H
+
+#include "anidbudpclient_global.h"
+#include <QObject>
+#include <QQueue>
+#include <QTimer>
+#include <QHostAddress>
+#include <QHostInfo>
+#include <QVariantMap>
+
+#include "authcommand.h"
+
+class QUdpSocket;
+class QTimer;
+
+class AbstractCommand;
+class AuthCommand;
+
+
+#define CLIENT_NAME "anidbudpclient"
+#define CLIENT_VERSION 0x000001
+#define PROTOCOL_VERSION 3
+
+class ANIDBUDPCLIENTSHARED_EXPORT AniDBUdpClient : public QObject
+{
+ Q_OBJECT
+ Q_ENUMS(State Error IdlePolicy AbstractCommand::ReplyCode);
+
+ Q_PROPERTY(QString host READ host WRITE setHost);
+ Q_PROPERTY(quint16 hostPort READ hostPort WRITE setHostPort);
+ Q_PROPERTY(quint16 localPort READ localPort WRITE setLocalPort);
+
+ Q_PROPERTY(QString user READ user WRITE setUser);
+ Q_PROPERTY(QString pass READ pass WRITE setPass);
+
+ /*
+ 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);
+
+public:
+ static const QByteArray clientName;
+ static const int clientVersion;
+ static const int protocolVersion;
+
+ enum State
+ {
+ ErrorState = -1,
+ DisconnectedState,
+ ConnectingState,
+ ReconnectingState,
+ ConnectedState,
+ };
+
+ enum Error
+ {
+ NoError,
+ BindError,
+ HostLookupError,
+ HostUnreachableError,
+ AuthenticationError,
+ BannedError,
+ UnknownError,
+ };
+
+ enum IdlePolicy
+ {
+ DoNothingIdlePolicy,
+ LogoutIdlePolicy,
+ KeepAliveIdlePolicy,
+ };
+
+ AniDBUdpClient(QObject *parent = 0);
+ virtual ~AniDBUdpClient();
+
+ // ------------------ Properties ------------------
+ QString host() const;
+ void setHost(const QString &host, quint16 port = 0);
+ quint16 hostPort() const;
+ void setHostPort(quint16 port);
+ quint16 localPort() const;
+ void setLocalPort(quint16 port);
+
+ QString user() const;
+ void setUser(const QString &user);
+ QString pass() const;
+ void setPass(const QString &user);
+
+ bool compression() const;
+ void setCompression(bool compress);
+
+ int floodInterval() const;
+ void setFloodInterval(int interval);
+
+ IdlePolicy idlePolicy() const;
+ void setIdlePolicy(IdlePolicy policy);
+
+ State state() const;
+ Error error() const;
+ QString errorString() const;
+
+ bool isIdle();
+
+ // ---------------- END Properties ----------------
+
+ void clearCommandQueue();
+
+public slots:
+ void connect();
+
+ /*
+ Disconnect from host.
+ If \graceful is true send all enququed messages first.
+ */
+ void disconnect(bool graceful = false);
+
+ void authenticate();
+
+ void send(AbstractCommand *command);
+ void sendRaw(QByteArray command);
+
+signals:
+ void connected();
+ void disconnected();
+
+ void pong();
+ void uptime();
+
+ void stateChanged(State newState, State oldState);
+
+private slots:
+ void lookedUp(QHostInfo hostInfo);
+ void doConnect();
+ void doAuthenticate(bool success);
+
+ void logout();
+
+
+ void enqueueCommand(AbstractCommand *command, bool first = false);
+ void sendNextCommand();
+ void sendCommand(AbstractCommand *command);
+
+ void readReplies();
+
+ void enterIdleState();
+ void leaveIdleState();
+
+ void idleTimeout();
+
+private:
+ QByteArray buildCmd(const QString &cmd, const QVariantMap &args);
+
+ void changeState(State newState);
+ QByteArray nextCommandId(int len = 5);
+
+ QTimer *commandTimer;
+ QTimer *idleTimer;
+ QTimer *replyTimeoutTimer;
+
+ QQueue<AbstractCommand *> commandQueue;
+ QMap<QByteArray, AbstractCommand *> sentCommands;
+ QUdpSocket *socket;
+
+
+
+ // Connection params
+ QString m_host;
+ QHostAddress m_hostAddress;
+ quint16 m_hostPort;
+ quint16 m_localPort;
+
+ int m_floodInterval;
+
+ // Auth params
+ QString m_user;
+ QString m_pass;
+
+ bool m_compression;
+
+ QByteArray m_sessionId;
+
+ // Misc params
+ IdlePolicy m_idlePolicy;
+ State m_state;
+ Error m_error;
+ QString m_errorString;
+
+ bool disconnecting;
+ bool m_idle;
+
+ AuthCommand *authCommand;
+ bool authenticateOnConnect;
+
+
+ static const int UDP_DATAGRAM_MAXIMUM_SIZE = 1400;
+ static const int UDP_API_INACTIVITY_LOGOUT = 30 * 60;
+};
+
+#endif // ANIDBUDPCLIENT_H
--- /dev/null
+QT *= network
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+LIBS += -lanidbudpclient
+LIBS += -L$$DESTDIR
--- /dev/null
+# -------------------------------------------------
+# Project created by QtCreator 2009-03-22T14:53:52
+# -------------------------------------------------
+QT += network
+QT -= gui
+TEMPLATE = lib
+TARGET = anidbudpclient
+DESTDIR = ../../build
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+QT *= network
+DEFINES += ANIDBUDPCLIENT_LIBRARY
+SOURCES += anidbudpclient.cpp \
+ abstractcommand.cpp \
+ authcommand.cpp \
+ rawcommand.cpp \
+ mylistaddcommand.cpp
+HEADERS += anidbudpclient.h \
+ anidbudpclient_global.h \
+ abstractcommand.h \
+ authcommand.h \
+ rawcommand.h \
+ mylistaddcommand.h
--- /dev/null
+#ifndef ANIDBUDPCLIENT_GLOBAL_H
+#define ANIDBUDPCLIENT_GLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(ANIDBUDPCLIENT_LIBRARY)
+# define ANIDBUDPCLIENTSHARED_EXPORT Q_DECL_EXPORT
+#else
+# define ANIDBUDPCLIENTSHARED_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // ANIDBUDPCLIENT_GLOBAL_H
--- /dev/null
+#include "authcommand.h"
+
+#include "anidbudpclient.h"
+
+AuthCommand::AuthCommand(QObject *parent) : AbstractCommand(parent)
+{
+ m_compression = false;
+}
+
+AuthCommand::AuthCommand(QString user, QString pass, QObject *parent) : AbstractCommand(parent)
+{
+ m_user = user;
+ m_pass = pass;
+}
+
+void AuthCommand::setUser(const QString &user)
+{
+ m_user = user;
+}
+
+void AuthCommand::setPass(const QString &pass)
+{
+ m_pass = pass;
+}
+
+void AuthCommand::setCompression(bool compress)
+{
+ m_compression = compress;
+}
+
+bool AuthCommand::waitForResult() const
+{
+ return true;
+}
+
+Command AuthCommand::rawCommand() const
+{
+ Command command;
+
+ command.first = "AUTH";
+
+ command.second["user"] = m_user;
+ command.second["pass"] = m_pass;
+ command.second["protover"] = AniDBUdpClient::protocolVersion;
+ command.second["client"] = AniDBUdpClient::clientName.constData();
+ command.second["clientver"] = AniDBUdpClient::clientVersion;
+ command.second["enc"] = "UTF8";
+ command.second["comp"] = m_compression;
+ return command;
+}
+
+QString AuthCommand::sessionId() const
+{
+ return m_sessionId;
+}
+
+void AuthCommand::setRawReply(ReplyCode replyCode, const QString &reply, AniDBUdpClient *client)
+{
+qDebug() << replyCode;
+ AbstractCommand::setRawReply(replyCode, reply, client);
+
+ switch(replyCode)
+ {
+ case LOGIN_ACCEPTED:
+ case LOGIN_ACCEPTED_NEW_VER:
+ m_sessionId = m_rawReply.mid(0, m_rawReply.indexOf(" "));
+ emit replyReady(true);
+ break;
+ default:
+qDebug() << "ERROR CODE: " << replyCode;
+ emit replyReady(false);
+ }
+}
--- /dev/null
+#ifndef AUTHCOMMAND_H
+#define AUTHCOMMAND_H
+
+#include "abstractcommand.h"
+
+class AuthCommand : public AbstractCommand
+{
+ Q_OBJECT
+
+public:
+ AuthCommand(QObject *parent = 0);
+ AuthCommand(QString user, QString pass, QObject *parent = 0);
+
+ void setUser(const QString &user);
+ void setPass(const QString &pass);
+
+ void setCompression(bool compress);
+
+ bool waitForResult() const;
+
+ Command rawCommand() const;
+ QString sessionId() const;
+
+ void setRawReply(ReplyCode replyCode, const QString &reply, AniDBUdpClient *client);
+
+
+
+private:
+ QString m_user;
+ QString m_pass;
+ QString m_sessionId;
+
+ bool m_compression;
+};
+
+#endif // AUTHCOMMAND_H
--- /dev/null
+#include "mylistaddcommand.h"
+
+#include <QFileInfo>
+#include <QCryptographicHash>
+#include <QtConcurrentRun>
+#include <QStringList>
+
+#include "anidbudpclient.h"
+
+MylistAddCommand::MylistAddCommand(QString file, QObject *parent) : AbstractCommand(parent)
+{
+ m_file = file;
+ m_size = QFileInfo(file).size();
+ mylistId = 0;
+
+ connect(&futureWatcher, SIGNAL(finished()), this, SLOT(completeHash()));
+}
+
+QString MylistAddCommand::file() const
+{
+ return m_file;
+}
+
+QByteArray MylistAddCommand::ed2kHash() const
+{
+ return m_ed2k;
+}
+
+int MylistAddCommand::fileSize() const
+{
+ return m_size;
+}
+
+bool MylistAddCommand::markWatched() const
+{
+ return m_markWatched;
+}
+
+void MylistAddCommand::setMarkWatched(bool mark)
+{
+ m_markWatched = mark;
+}
+
+bool MylistAddCommand::waitForResult() const
+{
+ return true;
+}
+
+Command MylistAddCommand::rawCommand() const
+{
+ Command command;
+ switch (mylistId)
+ {
+ case 0:
+ command.first = "MYLIST";
+ command.second["size"] = m_size;
+ command.second["ed2k"] = m_ed2k.constData();
+ return command;
+ break;
+ case -1:
+ command.first = "MYLISTADD";
+ command.second["size"] = m_size;
+ command.second["ed2k"] = m_ed2k.constData();
+ command.second["state"] = 1;
+ command.second["viewed"] = m_markWatched;
+ return command;
+ break;
+ default:
+ command.first = "MYLISTADD";
+ command.second["lid"] = mylistId;
+ command.second["viewed"] = m_markWatched;
+ command.second["edit"] = 1;
+ return command;
+ break;
+ }
+}
+
+void MylistAddCommand::setRawReply(ReplyCode replyCode, const QString &reply, AniDBUdpClient *client)
+{
+ AbstractCommand::setRawReply(replyCode, reply, client);
+
+ switch (mylistId)
+ {
+ case 0:
+ switch(replyCode)
+ {
+ case MYLIST:
+ {
+ QString reply = m_rawReply.mid(m_rawReply.indexOf("\n"));
+ QStringList parts = reply.split('|', QString::KeepEmptyParts);
+qDebug() << "PARTS" << parts;
+ mylistId = parts[0].toInt();
+qDebug() << "Mylist ID: " << mylistId;
+ if (!mylistId)
+ {
+qDebug() << "FAILED to read Mylist ID";
+ emit replyReady(false);
+ }
+ client->send(this);
+ }
+ break;
+ default:
+ mylistId = -1;
+ client->send(this);
+ break;
+ }
+ break;
+ default:
+ switch(replyCode)
+ {
+ case MYLIST_ENTRY_ADDED:
+ case MYLIST_ENTRY_EDITED:
+ case FILE_ALREADY_IN_MYLIST:
+ emit replyReady(true);
+ break;
+ default:
+ emit replyReady(false);
+ break;
+ }
+ break;
+ }
+
+
+}
+
+void MylistAddCommand::hash()
+{
+ future = QtConcurrent::run(this, &MylistAddCommand::doHash, m_file);
+ futureWatcher.setFuture(future);
+}
+
+void MylistAddCommand::completeHash()
+{
+ if (!future.isFinished())
+ {
+qDebug() << "WTF?";
+ return;
+ }
+ m_ed2k = QByteArray(future);
+ emit hashComplete();
+}
+
+QByteArray MylistAddCommand::doHash(QString file)
+{
+qDebug() << "hash thread init";
+ QFile f(file);
+ if (!f.open(QIODevice::ReadOnly))
+ return QByteArray();
+
+ QCryptographicHash ed2k(QCryptographicHash::Md4);
+ char *data = new char[ED2K_PART_SIZE];
+ int size;
+ while (!f.atEnd())
+ {
+ size = f.read(data, ED2K_PART_SIZE);
+ ed2k.addData(QCryptographicHash::hash(QByteArray(data, size), QCryptographicHash::Md4));
+qDebug() << "hashing...";
+ }
+ f.close();
+ delete[] data;
+qDebug() << "hashing... complete!";
+ return ed2k.result().toHex();
+}
--- /dev/null
+#ifndef MYLISTADDCOMMAND_H
+#define MYLISTADDCOMMAND_H
+
+#include "anidbudpclient_global.h"
+#include "abstractcommand.h"
+
+#include <QFuture>
+#include <QFutureWatcher>
+
+
+class ANIDBUDPCLIENTSHARED_EXPORT MylistAddCommand : public AbstractCommand
+{
+ Q_OBJECT
+
+public:
+ MylistAddCommand(QString file, QObject *parent = 0);
+
+ QString file() const;
+ QByteArray ed2kHash() const;
+ int fileSize() const;
+
+ bool markWatched() const;
+ void setMarkWatched(bool mark);
+
+
+ bool waitForResult() const;
+
+ Command rawCommand() const;
+
+ void setRawReply(ReplyCode replyCode, const QString &reply, AniDBUdpClient *client);
+
+ void hash();
+
+signals:
+ void hashComplete();
+
+private slots:
+ void completeHash();
+
+private:
+ QByteArray doHash(QString file);
+ QFuture<QByteArray> future;
+ QFutureWatcher<QByteArray> futureWatcher;
+
+ bool m_markWatched;
+
+ int m_size;
+ QByteArray m_ed2k;
+ QString m_file;
+
+ int mylistId;
+
+ static const qint64 ED2K_PART_SIZE = 9728000;
+};
+
+#endif // MYLISTADDCOMMAND_H
--- /dev/null
+#include "rawcommand.h"
+
+RawCommand::RawCommand(const QString &command, QObject *parent) : AbstractCommand(parent)
+{
+ m_command = command;
+}
+
+Command RawCommand::rawCommand() const
+{
+ return Command(m_command, QVariantMap());
+}
--- /dev/null
+#ifndef RAWCOMMAND_H
+#define RAWCOMMAND_H
+
+#include "anidbudpclient_global.h"
+#include "abstractcommand.h"
+
+class ANIDBUDPCLIENTSHARED_EXPORT RawCommand : public AbstractCommand
+{
+ Q_OBJECT
+
+public:
+
+ RawCommand(const QString &command, QObject *parent = 0);
+
+ Command rawCommand() const;
+private:
+ QString m_command;
+};
+
+#endif // RAWCOMMAND_H