]> Some of my projects - anidbudpclient.git/commitdiff
- First commit!
authorAPTX <mail@example.com>
Wed, 25 Mar 2009 22:58:10 +0000 (23:58 +0100)
committerAPTX <mail@example.com>
Wed, 25 Mar 2009 22:58:10 +0000 (23:58 +0100)
13 files changed:
abstractcommand.cpp [new file with mode: 0644]
abstractcommand.h [new file with mode: 0644]
anidbudpclient.cpp [new file with mode: 0644]
anidbudpclient.h [new file with mode: 0644]
anidbudpclient.pri [new file with mode: 0644]
anidbudpclient.pro [new file with mode: 0644]
anidbudpclient_global.h [new file with mode: 0644]
authcommand.cpp [new file with mode: 0644]
authcommand.h [new file with mode: 0644]
mylistaddcommand.cpp [new file with mode: 0644]
mylistaddcommand.h [new file with mode: 0644]
rawcommand.cpp [new file with mode: 0644]
rawcommand.h [new file with mode: 0644]

diff --git a/abstractcommand.cpp b/abstractcommand.cpp
new file mode 100644 (file)
index 0000000..a9cd0b5
--- /dev/null
@@ -0,0 +1,38 @@
+#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;
+}
diff --git a/abstractcommand.h b/abstractcommand.h
new file mode 100644 (file)
index 0000000..7f7fa4b
--- /dev/null
@@ -0,0 +1,178 @@
+#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
diff --git a/anidbudpclient.cpp b/anidbudpclient.cpp
new file mode 100644 (file)
index 0000000..3ab0057
--- /dev/null
@@ -0,0 +1,555 @@
+#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;
+}
diff --git a/anidbudpclient.h b/anidbudpclient.h
new file mode 100644 (file)
index 0000000..28c681a
--- /dev/null
@@ -0,0 +1,205 @@
+#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
diff --git a/anidbudpclient.pri b/anidbudpclient.pri
new file mode 100644 (file)
index 0000000..8af8ec6
--- /dev/null
@@ -0,0 +1,5 @@
+QT *= network
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+LIBS += -lanidbudpclient
+LIBS += -L$$DESTDIR
diff --git a/anidbudpclient.pro b/anidbudpclient.pro
new file mode 100644 (file)
index 0000000..75e6829
--- /dev/null
@@ -0,0 +1,23 @@
+# -------------------------------------------------
+# 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
diff --git a/anidbudpclient_global.h b/anidbudpclient_global.h
new file mode 100644 (file)
index 0000000..2b578d2
--- /dev/null
@@ -0,0 +1,12 @@
+#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
diff --git a/authcommand.cpp b/authcommand.cpp
new file mode 100644 (file)
index 0000000..871aba6
--- /dev/null
@@ -0,0 +1,73 @@
+#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);
+       }
+}
diff --git a/authcommand.h b/authcommand.h
new file mode 100644 (file)
index 0000000..ab88016
--- /dev/null
@@ -0,0 +1,36 @@
+#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
diff --git a/mylistaddcommand.cpp b/mylistaddcommand.cpp
new file mode 100644 (file)
index 0000000..e8fa659
--- /dev/null
@@ -0,0 +1,163 @@
+#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();
+}
diff --git a/mylistaddcommand.h b/mylistaddcommand.h
new file mode 100644 (file)
index 0000000..122921f
--- /dev/null
@@ -0,0 +1,56 @@
+#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
diff --git a/rawcommand.cpp b/rawcommand.cpp
new file mode 100644 (file)
index 0000000..2b3ebf9
--- /dev/null
@@ -0,0 +1,11 @@
+#include "rawcommand.h"
+
+RawCommand::RawCommand(const QString &command, QObject *parent) : AbstractCommand(parent)
+{
+       m_command = command;
+}
+
+Command RawCommand::rawCommand() const
+{
+       return Command(m_command, QVariantMap());
+}
diff --git a/rawcommand.h b/rawcommand.h
new file mode 100644 (file)
index 0000000..990ffa4
--- /dev/null
@@ -0,0 +1,20 @@
+#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