From: APTX Date: Wed, 25 Mar 2009 22:58:10 +0000 (+0100) Subject: - First commit! X-Git-Url: https://gitweb.aptx.org/?a=commitdiff_plain;h=2aed606137d649477c19588b37fd90f5d2af9eac;p=anidbudpclient.git - First commit! --- 2aed606137d649477c19588b37fd90f5d2af9eac diff --git a/abstractcommand.cpp b/abstractcommand.cpp new file mode 100644 index 0000000..a9cd0b5 --- /dev/null +++ b/abstractcommand.cpp @@ -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 index 0000000..7f7fa4b --- /dev/null +++ b/abstractcommand.h @@ -0,0 +1,178 @@ +#ifndef ABSTRACTCOMMAND_H +#define ABSTRACTCOMMAND_H + +#include "anidbudpclient_global.h" +#include +#include +#include + +class AniDBUdpClient; + +typedef QPair 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 index 0000000..3ab0057 --- /dev/null +++ b/anidbudpclient.cpp @@ -0,0 +1,555 @@ +#include "anidbudpclient.h" + +#include +#include + +#include + +#include + +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 index 0000000..28c681a --- /dev/null +++ b/anidbudpclient.h @@ -0,0 +1,205 @@ +#ifndef ANIDBUDPCLIENT_H +#define ANIDBUDPCLIENT_H + +#include "anidbudpclient_global.h" +#include +#include +#include +#include +#include +#include + +#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 commandQueue; + QMap 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 index 0000000..8af8ec6 --- /dev/null +++ b/anidbudpclient.pri @@ -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 index 0000000..75e6829 --- /dev/null +++ b/anidbudpclient.pro @@ -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 index 0000000..2b578d2 --- /dev/null +++ b/anidbudpclient_global.h @@ -0,0 +1,12 @@ +#ifndef ANIDBUDPCLIENT_GLOBAL_H +#define ANIDBUDPCLIENT_GLOBAL_H + +#include + +#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 index 0000000..871aba6 --- /dev/null +++ b/authcommand.cpp @@ -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 index 0000000..ab88016 --- /dev/null +++ b/authcommand.h @@ -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 index 0000000..e8fa659 --- /dev/null +++ b/mylistaddcommand.cpp @@ -0,0 +1,163 @@ +#include "mylistaddcommand.h" + +#include +#include +#include +#include + +#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 index 0000000..122921f --- /dev/null +++ b/mylistaddcommand.h @@ -0,0 +1,56 @@ +#ifndef MYLISTADDCOMMAND_H +#define MYLISTADDCOMMAND_H + +#include "anidbudpclient_global.h" +#include "abstractcommand.h" + +#include +#include + + +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 future; + QFutureWatcher 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 index 0000000..2b3ebf9 --- /dev/null +++ b/rawcommand.cpp @@ -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 index 0000000..990ffa4 --- /dev/null +++ b/rawcommand.h @@ -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