--- /dev/null
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+.qmake.cache
+tags
+.DS_Store
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+
+# qtcreator generated files
+*.pro.user
+*.pro.user.*
+*.autosave
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.exp
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Directories to ignore
+# ---------------------
+
+debug
+release
+lib/qtsingleapplication/lib
+lib/qtsingleapplication/examples
+lib/qtsingleapplication/doc
+.tmp
+qtc-gdbmacros
+
+# Binaries
+# --------
+build/aniplayer
+build/*.dll
+build/*.lib
+build/*.exe
+build/*.so*
+
+
--- /dev/null
+QT += core
+QT -= gui
+
+TARGET = import-mylist
+DESTDIR = ../build
+CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+SOURCES += main.cpp
+
+include(../localmylist.pri)
--- /dev/null
+#include <QtCore/QCoreApplication>
+
+#include <QStringList>
+#include <QTextStream>
+
+#include "mylist.h"
+#include "abstracttask.h"
+
+using namespace LocalMyList;
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication a(argc, argv);
+ QTextStream cout(stdout);
+ if (a.arguments().count() < 2)
+ {
+ cout << "Usage: " << a.arguments()[0] << " FILE";
+ return 1;
+ }
+
+ QFile f(a.arguments()[1]);
+ if (!f.open(QIODevice::ReadOnly))
+ {
+ cout << "Failed open file for reading";
+ return 1;
+ }
+
+ AbstractTask *t = LocalMyList::instance()->importMyList(a.arguments()[1]);
+ QObject::connect(t, SIGNAL(finished()), &a, SLOT(quit()));
+
+ return a.exec();
+
+ return 0;
+}
--- /dev/null
+QT += core
+QT -= gui
+
+TARGET = import-titles
+DESTDIR = ../build
+CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+SOURCES += main.cpp
+
+include(../localmylist.pri)
--- /dev/null
+#include <QtCore/QCoreApplication>
+
+#include <QStringList>
+#include <QTextStream>
+#include "mylist.h"
+#include "abstracttask.h"
+
+using namespace LocalMyList;
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication a(argc, argv);
+ QTextStream cout(stdout);
+ if (a.arguments().count() < 2)
+ {
+ cout << "Usage: " << a.arguments()[0] << " FILE";
+ return 1;
+ }
+
+ AbstractTask *t = LocalMyList::instance()->importTitles(a.arguments()[1]);
+ QObject::connect(t, SIGNAL(finished()), &a, SLOT(quit()));
+
+ return a.exec();
+}
--- /dev/null
+QT *= sql
+
+INCLUDEPATH += $$PWD/localmylist/include
+INCLUDEPATH += $$PWD/localmylist
+DEPENDPATH += $$PWD/localmylist
+LIBS += -llocalmylist
+LIBS += -L$$PWD/build
--- /dev/null
+TEMPLATE = subdirs
+
+SUBDIRS += \
+ localmylist \
+ search-gui \
+ import-titles \
+ import-mylist \
+ management-gui \
+ play-next
--- /dev/null
+#include "abstracttask.h"
+
+namespace LocalMyList {
+
+AbstractTask::AbstractTask(Database *db, QObject *parent) :
+ QObject(parent)
+{
+ this->db = db;
+ connect(this, SIGNAL(nextWorkUnit()), this, SLOT(doNextWorkUnit()), Qt::QueuedConnection);
+}
+
+AbstractTask::~AbstractTask()
+{
+}
+
+QString AbstractTask::taskName() const
+{
+ return metaObject()->className();
+}
+
+QString AbstractTask::taskSubject() const
+{
+ return QString();
+}
+
+bool AbstractTask::canUseThreads() const
+{
+ return false;
+}
+
+void AbstractTask::setDatabase(Database *db)
+{
+ this->db = db;
+}
+
+void AbstractTask::start()
+{
+}
+
+
+void AbstractTask::doNextWorkUnit()
+{
+ workUnit();
+}
+
+
+void AbstractTask::workUnit()
+{
+}
+
+} // namespace LocalMyList
--- /dev/null
+#ifndef ABSTRACTTASK_H
+#define ABSTRACTTASK_H
+
+#include <QObject>
+
+namespace LocalMyList {
+
+class Database;
+
+class AbstractTask : public QObject
+{
+ Q_OBJECT
+public:
+ explicit AbstractTask(Database *db = 0, QObject *parent = 0);
+ virtual ~AbstractTask();
+
+ virtual QString taskName() const;
+ virtual QString taskSubject() const;
+ virtual bool canUseThreads() const;
+
+ void setDatabase(Database *db);
+
+public slots:
+ virtual void start() = 0;
+
+signals:
+ void nextWorkUnit();
+ void progress(int done, int currentTotal);
+ // TODO add some sort of error reporting
+ void finished();
+
+protected:
+ virtual void workUnit();
+
+private slots:
+ void doNextWorkUnit();
+
+protected:
+ Database *db;
+
+ static const int OPERATIONS_PER_UNIT = 100;
+};
+
+} // namespace LocalMyList
+
+#endif // ABSTRACTTASK_H
--- /dev/null
+#include "addfiletask.h"
+
+#include "database.h"
+#include "mylist.h"
+
+#include <AniDBUdpClient/Hash>
+#include <AniDBUdpClient/Client>
+#include <AniDBUdpClient/FileCommand>
+
+namespace LocalMyList {
+
+using namespace AniDBUdpClient;
+
+AddFileTask::AddFileTask(Database *db, QObject *parent) :
+ AbstractTask(db, parent), hashResult(0)
+{
+}
+
+AddFileTask::~AddFileTask()
+{
+ if (hashResult)
+ hashResult->deleteLater();
+}
+
+QFileInfo AddFileTask::file() const
+{
+ return m_file;
+}
+
+QString AddFileTask::taskSubject() const
+{
+ return m_file.fileName();
+}
+
+void AddFileTask::start()
+{
+ if (!m_file.exists())
+ {
+ emit finished();
+ return;
+ }
+
+ if (hashResult)
+ {
+ hashingFinished();
+ return;
+ }
+ hashResult = Hash::instance()->hashFile(HashRequest(m_file));
+ connect(hashResult, SIGNAL(resultReady()), this, SLOT(hashingFinished()));
+}
+
+void AddFileTask::setFile(const QFileInfo &file)
+{
+ m_file = file;
+}
+
+void AddFileTask::hashingFinished()
+{
+ int fid = db->isKnownFile(hashResult->hash(), m_file.size());
+ if (fid)
+ {
+ db->setFileLocation(fid, MyList::instance()->hostId(), m_file.canonicalFilePath());
+ emit finished();
+ return;
+ }
+
+ UnknownFile f;
+
+ f.ed2k = hashResult->hash();
+ f.size = m_file.size();
+ f.hostId = MyList::instance()->hostId();
+ f.path = m_file.canonicalFilePath();
+
+ db->addUnknownFile(f);
+
+ PendingRequest request;
+ request.ed2k = hashResult->hash();
+ request.size = m_file.size();
+
+ db->addRequest(request);
+
+ emit finished();
+
+/*
+ static const FileFlags fileFlags =
+ FileFlag::Aid | FileFlag::Eid | FileFlag::Gid
+ | FileFlag::LengthInSeconds
+ | FileFlag::FileType
+ | FileFlag::Crc32
+ | FileFlag::State
+ | FileFlag::Quality
+ | FileFlag::VideoResolution
+ | FileFlag::VideoCodec
+ | FileFlag::AudioCodec
+ | FileFlag::SubLanguage
+ | FileFlag::DubLanguage
+ | FileFlag::MyListViewDate;
+
+ static const FileAnimeFlags fileAnimeFlags =
+ FileAnimeFlag::GroupName
+ | FileAnimeFlag::GroupShortName;
+ if (fileReply)
+ fileReply->deleteLater();
+ fileReply = Client::instance()->send(FileCommand(
+ hashResult->hash(), m_file.size(),
+ fileFlags, fileAnimeFlags));
+ connect(fileReply, SIGNAL(replyReady(bool)), this, SLOT(fileDataRecieved(bool)));
+*/
+}
+/*
+void AddFileTask::fileDataRecieved(bool success)
+{
+ if (!success)
+ {
+ // Insert into unknown files
+ return;
+ }
+ File f;
+
+ f.fid = fileReply->fid();
+ f.aid = fileReply->value(FileFlag::Aid).toInt();
+ f.eid = fileReply->value(FileFlag::Eid).toInt();
+ f.gid = fileReply->value(FileFlag::Gid).toInt();
+ f.length = fileReply->value(FileFlag::LengthInSeconds).toInt();
+ f.extension = fileReply->value(FileFlag::FileType).toString();
+ f.crc = fileReply->value(FileFlag::Crc32).toString();
+ //f. = fileReply->value(FileFlag::State);
+ f.quality = fileReply->value(FileFlag::Quality).toString();
+ f.resolution = fileReply->value(FileFlag::VideoResolution).toString();
+ f.videoCodec = fileReply->value(FileFlag::VideoCodec).toString();
+ f.audioCodec = fileReply->value(FileFlag::AudioCodec).toString();
+ f.subtitleLanguage = fileReply->value(FileFlag::SubLanguage).toString();
+ f.audioLanguage = fileReply->value(FileFlag::DubLanguage).toString();
+ f.myWatched = QDateTime::fromTime_t(fileReply->value(FileFlag::MyListViewDate).toUInt());
+}
+*/
+} // namespace LocalMyList
--- /dev/null
+#ifndef ADDFILETASK_H
+#define ADDFILETASK_H
+
+#include "abstracttask.h"
+
+#include <QObject>
+#include <QFileInfo>
+
+namespace AniDBUdpClient {
+ class HashResult;
+ class FileReply;
+}
+
+namespace LocalMyList {
+
+class Database;
+
+// TODO change DB schema to allow for duplicate file locations
+// instead of only allowing one file location ever.
+class AddFileTask : public AbstractTask
+{
+ Q_OBJECT
+ Q_PROPERTY(QFileInfo file READ file WRITE setFile)
+
+public:
+ explicit AddFileTask(Database *db = 0, QObject *parent = 0);
+ ~AddFileTask();
+
+ QFileInfo file() const;
+
+ QString taskSubject() const;
+
+ void start();
+
+signals:
+
+public slots:
+ void setFile(const QFileInfo &file);
+
+private slots:
+ void hashingFinished();
+
+ // File data is handled via requests
+ // not just any task
+// void fileDataRecieved(bool success);
+
+
+private:
+ QFileInfo m_file;
+ AniDBUdpClient::HashResult *hashResult;
+};
+
+} // namespace LocalMyList
+
+#endif // ADDFILETASK_H
--- /dev/null
+#include "animetitleparsetask.h"
+
+#include <QStringList>
+#include "database.h"
+
+#include <QDebug>
+
+namespace LocalMyList {
+
+AnimeTitleParseTask::AnimeTitleParseTask(QObject *parent) :
+ AbstractTask(0, parent)
+{
+}
+
+QFileInfo AnimeTitleParseTask::file() const
+{
+ return m_file;
+}
+
+QString AnimeTitleParseTask::taskSubject() const
+{
+ return QString("Anime Title Parse (from file: %1)").arg(m_file.fileName());
+}
+
+bool AnimeTitleParseTask::canUseThreads() const
+{
+ return true;
+}
+
+void AnimeTitleParseTask::start()
+{
+ QFile f(m_file.absoluteFilePath());
+ if (!f.open(QIODevice::ReadOnly))
+ {
+ emit finished();
+ qWarning("AnimeTitleParseTask failed to open file");
+ return;
+ }
+
+ parse(&f);
+ emit finished();
+}
+
+void AnimeTitleParseTask::setFile(const QFileInfo &file)
+{
+ m_file = file;
+}
+
+void AnimeTitleParseTask::parse(QIODevice *device)
+{
+ db->transaction();
+ db->truncateTitleData();
+
+ int titles = 0;
+ QByteArray buf;
+ while (!device->atEnd())
+ {
+ buf = device->readLine();
+ QString s = QString::fromUtf8(buf);
+ s = s.trimmed();
+ if (s.startsWith(QChar('#')))
+ continue;
+
+ QStringList parts = s.split(QChar('|'));
+ if (parts.count() != 4)
+ continue;
+
+ AnimeTitle title;
+ bool ok;
+ title.aid = parts[0].toInt(&ok);
+ if (!ok)
+ continue;
+ title.type = AnimeTitle::TitleType(parts[1].toInt(&ok));
+ if (!ok)
+ continue;
+ title.language = parts[2];
+ title.title = parts[3];
+
+ db->addTitle(title);
+ ++titles;
+ if (titles % 100 != 0)
+ continue;
+ qDebug() << "Read" << titles << "titles";
+ emit progress(device->pos(), device->size());
+ }
+ qDebug() << "Done. Read" << titles << "titles";
+
+ db->commit();
+}
+
+} // namespace LocalMyList
--- /dev/null
+#ifndef ANIMETITLEPARSETASK_H
+#define ANIMETITLEPARSETASK_H
+
+#include "abstracttask.h"
+#include <QFileInfo>
+
+namespace LocalMyList {
+
+class AnimeTitleParseTask : public AbstractTask
+{
+ Q_OBJECT
+public:
+ explicit AnimeTitleParseTask(QObject *parent = 0);
+
+ QFileInfo file() const;
+ QString taskSubject() const;
+ bool canUseThreads() const;
+
+ void start();
+
+public slots:
+ void setFile(const QFileInfo &file);
+
+private:
+ void parse(QIODevice *device);
+
+ QFileInfo m_file;
+
+};
+
+} // namespace LocalMyList
+
+#endif // ANIMETITLEPARSETASK_H
--- /dev/null
+#include "database.h"
+
+#include <QSqlQuery>
+#include <QSqlError>
+#include <QSqlDriver>
+#include <QVariant>
+#include <QThread>
+#include <QDebug>
+
+namespace LocalMyList {
+
+AnimeTitle::AnimeTitle(int aid, TitleType type, const QString &language, const QString &title)
+{
+ this->aid = aid;
+ this->type = type;
+ this->language = language;
+ this->title = title;
+}
+
+Anime::Anime()
+{
+ aid = 0;
+ rating = 0;
+ votes = 0;
+ tempRating = 0;
+ tempVotes = 0;
+ myVote = 0;
+ myTempVote = 0;
+ description = "";
+}
+
+Episode::Episode()
+{
+ eid = 0;
+ aid = 0;
+ epno = 0;
+ length = 0;
+ state = 0;
+ special = false;
+ recap = false;
+ opening = false;
+ ending = false;
+ rating = 0;
+ votes = 0;
+ myVote = 0;
+}
+
+File::File()
+{
+ fid = 0;
+ eid = 0;
+ aid = 0;
+ gid = 0;
+ size = 0;
+ length = 0;
+ version = 1;
+ censored = false;
+ qualityId = 0;
+ myState = 0;
+ myFileState = 0;
+}
+
+FileEpisodeRel::FileEpisodeRel()
+{
+ fid = 0;
+ eid = 0;
+ startPercent = 0;
+ endPercent = 0;
+}
+
+UnknownFile::UnknownFile()
+{
+ size = 0;
+ hostId = 0;
+}
+
+
+PendingRequest::PendingRequest()
+{
+ aid = 0;
+ eid = 0;
+ fid = 0;
+ size = 0;
+}
+
+HostInfo::HostInfo()
+{
+ id = 0;
+ isUdpHost = false;
+}
+
+DatabaseConnectionSettings::DatabaseConnectionSettings()
+{
+ port = 0;
+}
+
+struct DatabaseInternal
+{
+ QSqlDatabase db;
+
+ QSqlQuery getHostInfoQuery;
+ QSqlQuery isKnownFileQuery;
+
+ QSqlQuery getSettingsQuery;
+ QSqlQuery updateSettingQuery;
+
+ QSqlQuery getAnimeQuery;
+ QSqlQuery getEpisodeQuery;
+ QSqlQuery getFileQuery;
+
+ QSqlQuery setAnimeQuery;
+ QSqlQuery setEpisodeQuery;
+ QSqlQuery setFileQuery;
+
+ QSqlQuery addLogQuery;
+
+ QSqlQuery addTitleQuery;
+ QSqlQuery addAnimeQuery;
+ QSqlQuery addEpisodeQuery;
+ QSqlQuery addFileQuery;
+ QSqlQuery addFileEpisodeRelQuery;
+
+ QSqlQuery addUnknownFileQuery;
+
+ QSqlQuery addPendingRequestQuery;
+ QSqlQuery getRequestBatchQuery;
+ QSqlQuery clearRequestQuery;
+
+ QThread *thread;
+};
+
+Database::Database(const QString &connectionName) : d(0)
+{
+ this->connectionName = connectionName;
+}
+
+Database::~Database()
+{
+ if (d)
+ {
+ delete d;
+ d = 0;
+ QSqlDatabase::removeDatabase(connectionName);
+ emit disconnected();
+ }
+}
+
+bool Database::isConnected() const
+{
+ return d && d->db.isOpen();
+}
+
+void Database::setConnectionSettings(const DatabaseConnectionSettings &dbs)
+{
+ m_connectionSettings = dbs;
+}
+
+DatabaseConnectionSettings Database::connectionSettings() const
+{
+ return m_connectionSettings;
+}
+
+bool Database::transaction()
+{
+ Q_ASSERT_X(d->thread == QThread::currentThread(), "threads", "DB used from different thread");
+
+ bool success = d->db.transaction();
+ if (success)
+ return true;
+ qDebug() << "Transaction Error:" << d->db.lastError();
+ return false;
+}
+
+bool Database::commit()
+{
+ Q_ASSERT_X(d->thread == QThread::currentThread(), "threads", "DB used from different thread");
+
+ bool success = d->db.commit();
+ if (success)
+ return true;
+ qDebug() << "Commit Error:" << d->db.lastError();
+ return false;
+}
+
+bool Database::rollback()
+{
+ Q_ASSERT_X(d->thread == QThread::currentThread(), "threads", "DB used from different thread");
+
+ bool success = d->db.rollback();
+ if (success)
+ return true;
+qDebug() << "Commit Error:" << d->db.lastError();
+ return false;
+}
+
+HostInfo Database::getHostInfo(const QString &hostName)
+{
+ d->getHostInfoQuery.bindValue(":name", hostName);
+
+ if (!exec(d->getHostInfoQuery))
+ return HostInfo();
+
+ HostInfo hostInfo;
+ if (d->getHostInfoQuery.next())
+ {
+ hostInfo.id = d->getHostInfoQuery.value(0).toInt();
+ hostInfo.name = d->getHostInfoQuery.value(1).toString();
+ hostInfo.isUdpHost = d->getHostInfoQuery.value(2).toBool();
+ }
+ d->getHostInfoQuery.finish();
+
+ return hostInfo;
+}
+
+QVariantMap Database::getConfig()
+{
+ if (!exec(d->getSettingsQuery))
+ return QVariantMap();
+
+ QVariantMap settings;
+ while (d->getSettingsQuery.next())
+ {
+ settings.insert(d->getSettingsQuery.value(0).toString(), d->getSettingsQuery.value(1));
+ }
+ d->getSettingsQuery.finish();
+ return settings;
+}
+
+bool Database::setConfig(const QString &key, const QVariant &value)
+{
+ d->updateSettingQuery.bindValue(":key", key);
+ d->updateSettingQuery.bindValue(":value", value);
+
+ return exec(d->updateSettingQuery);
+}
+
+int Database::isKnownFile(const QByteArray &ed2k, qint64 size)
+{
+ d->isKnownFileQuery.bindValue(":ed2k", ed2k);
+ d->isKnownFileQuery.bindValue(":size", size);
+
+ if (!exec(d->isKnownFileQuery))
+ return 0;
+
+ int fid = 0;
+ if (d->isKnownFileQuery.next())
+ fid = d->isKnownFileQuery.value(0).toInt();
+ d->isKnownFileQuery.finish();
+
+ return fid;
+}
+
+bool Database::setFileLocation(int fid, int hostId, const QString &location)
+{
+ QSqlQuery q(d->db);
+ q.prepare("INSERT INTO file_location VALUES(:fid, :hostId, :path)");
+ q.bindValue(":fid", fid);
+ q.bindValue(":hostId", hostId);
+ q.bindValue(":path", location);
+
+ return exec(q);
+}
+
+Anime Database::getAnime(int aid)
+{
+ Anime a;
+
+ d->getAnimeQuery.bindValue(":aid", aid);
+
+ if (!exec(d->getAnimeQuery))
+ return a;
+
+ if (!d->getAnimeQuery.next())
+ {
+ d->getAnimeQuery.finish();
+ return a;
+ }
+
+ a.aid = d->getAnimeQuery.value(0).toInt();
+ a.anidbUpdate = d->getAnimeQuery.value(1).toDateTime();
+ a.entryUpdate = d->getAnimeQuery.value(2).toDateTime();
+ a.myUpdate = d->getAnimeQuery.value(3).toDateTime();
+ a.titleEnglish = d->getAnimeQuery.value(4).toString();
+ a.titleRomaji = d->getAnimeQuery.value(5).toString();
+ a.titleKanji = d->getAnimeQuery.value(6).toString();
+ a.description = d->getAnimeQuery.value(7).toString();
+ a.year = d->getAnimeQuery.value(8).toString();
+ a.startDate = d->getAnimeQuery.value(9).toDateTime();
+ a.endDate = d->getAnimeQuery.value(10).toDateTime();
+ a.type = d->getAnimeQuery.value(11).toString();
+ a.rating = d->getAnimeQuery.value(12).toDouble();
+ a.votes = d->getAnimeQuery.value(13).toInt();
+ a.tempRating = d->getAnimeQuery.value(14).toDouble();
+ a.tempVotes = d->getAnimeQuery.value(15).toInt();
+ a.myVote = d->getAnimeQuery.value(16).toDouble();
+ a.myVoteDate = d->getAnimeQuery.value(17).toDateTime();
+ a.myTempVote = d->getAnimeQuery.value(18).toDouble();
+ a.myTempVoteDate = d->getAnimeQuery.value(19).toDateTime();
+
+ d->getAnimeQuery.finish();
+
+ return a;
+}
+
+Episode Database::getEpisode(int eid)
+{
+ Episode e;
+
+ d->getEpisodeQuery.bindValue(":eid", eid);
+
+ if (!exec(d->getEpisodeQuery))
+ return e;
+
+ if (!d->getEpisodeQuery.next())
+ {
+ d->getEpisodeQuery.finish();
+ return e;
+ }
+
+ e.eid = d->getEpisodeQuery.value(0).toInt();
+ e.aid = d->getEpisodeQuery.value(1).toInt();
+ e.anidbUpdate = d->getEpisodeQuery.value(2).toDateTime();
+ e.entryUpdate = d->getEpisodeQuery.value(3).toDateTime();
+ e.myUpdate = d->getEpisodeQuery.value(4).toDateTime();
+ e.epno = d->getEpisodeQuery.value(5).toInt();
+ e.titleEnglish = d->getEpisodeQuery.value(6).toString();
+ e.titleRomaji = d->getEpisodeQuery.value(7).toString();
+ e.titleKanji = d->getEpisodeQuery.value(8).toString();
+ e.length = d->getEpisodeQuery.value(9).toInt();
+ e.airdate = d->getEpisodeQuery.value(10).toDateTime();
+ e.state = d->getEpisodeQuery.value(11).toInt();
+ e.special = d->getEpisodeQuery.value(12).toBool();
+ e.recap = d->getEpisodeQuery.value(13).toBool();
+ e.opening = d->getEpisodeQuery.value(14).toBool();
+ e.ending = d->getEpisodeQuery.value(15).toBool();
+ e.rating = d->getEpisodeQuery.value(16).toDouble();
+ e.votes = d->getEpisodeQuery.value(17).toInt();
+ e.myVote = d->getEpisodeQuery.value(18).toDouble();
+ e.myVoteDate = d->getEpisodeQuery.value(19).toDateTime();
+
+ d->getEpisodeQuery.finish();
+
+ return e;
+}
+
+File Database::getFile(int fid)
+{
+ File f;
+
+ d->getFileQuery.bindValue(":fid", fid);
+
+ if (!exec(d->getFileQuery))
+ return f;
+
+ if (!d->getFileQuery.next())
+ {
+ d->getFileQuery.finish();
+ return f;
+ }
+
+ f.fid = d->getFileQuery.value(0).toInt();
+ f.eid = d->getFileQuery.value(1).toInt();
+ f.aid = d->getFileQuery.value(2).toInt();
+ f.gid = d->getFileQuery.value(3).toInt();
+ f.anidbUpdate = d->getFileQuery.value(4).toDateTime();
+ f.entryUpdate = d->getFileQuery.value(5).toDateTime();
+ f.myUpdate = d->getFileQuery.value(6).toDateTime();
+ f.ed2k = d->getFileQuery.value(7).toByteArray();
+ f.size = d->getFileQuery.value(8).toLongLong();
+ f.length = d->getFileQuery.value(9).toInt();
+ f.extension = d->getFileQuery.value(10).toString();
+ f.groupName = d->getFileQuery.value(11).toString();
+ f.groupNameShort = d->getFileQuery.value(12).toString();
+ f.crc = d->getFileQuery.value(13).toString();
+ f.releaseDate = d->getFileQuery.value(14).toDateTime();
+ f.version = d->getFileQuery.value(15).toInt();
+ f.censored = d->getFileQuery.value(16).toBool();
+ f.type = d->getFileQuery.value(17).toString();
+ f.qualityId = d->getFileQuery.value(18).toInt();
+ f.quality = d->getFileQuery.value(19).toString();
+ f.resolution = d->getFileQuery.value(20).toString();
+ f.videoCodec = d->getFileQuery.value(21).toString();
+ f.audioCodec = d->getFileQuery.value(22).toString();
+ f.audioLanguage = d->getFileQuery.value(23).toString();
+ f.subtitleLanguage = d->getFileQuery.value(24).toString();
+ f.aspectRatio = d->getFileQuery.value(25).toString();
+ f.myWatched = d->getFileQuery.value(26).toDateTime();
+ f.myState = d->getFileQuery.value(27).toInt();
+ f.myFileState = d->getFileQuery.value(28).toInt();
+ f.myStorage = d->getFileQuery.value(29).toString();
+ f.mySource = d->getFileQuery.value(30).toString();
+ f.myOther = d->getFileQuery.value(31).toString();
+
+ d->getFileQuery.finish();
+
+ return f;
+}
+
+bool Database::setAnime(const Anime &anime)
+{
+ d->setAnimeQuery.bindValue(":aid", anime.aid);
+ d->setAnimeQuery.bindValue(":anidbUpdate", anime.anidbUpdate);
+ d->setAnimeQuery.bindValue(":entryUpdate", anime.entryUpdate);
+ d->setAnimeQuery.bindValue(":myUpdate", anime.myUpdate);
+ d->setAnimeQuery.bindValue(":titleEnglish", anime.titleEnglish);
+ d->setAnimeQuery.bindValue(":titleRomaji", anime.titleRomaji);
+ d->setAnimeQuery.bindValue(":titleKanji", anime.titleKanji);
+ d->setAnimeQuery.bindValue(":description", anime.description);
+ d->setAnimeQuery.bindValue(":year", anime.year);
+ d->setAnimeQuery.bindValue(":startDate", anime.startDate);
+ d->setAnimeQuery.bindValue(":endDate", anime.endDate);
+ d->setAnimeQuery.bindValue(":type", anime.type);
+ d->setAnimeQuery.bindValue(":rating", anime.rating);
+ d->setAnimeQuery.bindValue(":votes", anime.votes);
+ d->setAnimeQuery.bindValue(":tempRating", anime.tempRating);
+ d->setAnimeQuery.bindValue(":tempVotes", anime.tempVotes);
+ d->setAnimeQuery.bindValue(":myVote", anime.myVote);
+ d->setAnimeQuery.bindValue(":myVoteDate", anime.myVoteDate);
+ d->setAnimeQuery.bindValue(":myTempVote", anime.myTempVote);
+ d->setAnimeQuery.bindValue(":myTempVoteDate", anime.myTempVoteDate);
+
+ return exec(d->setAnimeQuery);
+}
+
+bool Database::setEpisode(const Episode &episode)
+{
+ d->setEpisodeQuery.bindValue(":eid", episode.eid);
+ d->setEpisodeQuery.bindValue(":aid", episode.aid);
+ d->setEpisodeQuery.bindValue(":anidbUpdate", episode.anidbUpdate);
+ d->setEpisodeQuery.bindValue(":entryUpdate", episode.entryUpdate);
+ d->setEpisodeQuery.bindValue(":myUpdate", episode.myUpdate);
+ d->setEpisodeQuery.bindValue(":epno", episode.epno);
+ d->setEpisodeQuery.bindValue(":titleEnglish", episode.titleEnglish);
+ d->setEpisodeQuery.bindValue(":titleRomaji", episode.titleRomaji);
+ d->setEpisodeQuery.bindValue(":titleKanji", episode.titleKanji);
+ d->setEpisodeQuery.bindValue(":length", episode.length);
+ d->setEpisodeQuery.bindValue(":airdate", episode.airdate);
+ d->setEpisodeQuery.bindValue(":state", episode.state);
+ d->setEpisodeQuery.bindValue(":special", episode.special);
+ d->setEpisodeQuery.bindValue(":recap", episode.recap);
+ d->setEpisodeQuery.bindValue(":opening", episode.opening);
+ d->setEpisodeQuery.bindValue(":ending", episode.ending);
+ d->setEpisodeQuery.bindValue(":rating", episode.rating);
+ d->setEpisodeQuery.bindValue(":votes", episode.votes);
+ d->setEpisodeQuery.bindValue(":myVote", episode.myVote);
+ d->setEpisodeQuery.bindValue(":myVoteDate", episode.myVoteDate);
+
+ return exec(d->setEpisodeQuery);
+}
+
+bool Database::setFile(const File &file)
+{
+ d->setFileQuery.bindValue(":fid", file.fid);
+ d->setFileQuery.bindValue(":eid", file.eid);
+ d->setFileQuery.bindValue(":aid", file.aid);
+ d->setFileQuery.bindValue(":gid", file.gid);
+ d->setFileQuery.bindValue(":anidbUpdate", file.anidbUpdate);
+ d->setFileQuery.bindValue(":entryUpdate", file.entryUpdate);
+ d->setFileQuery.bindValue(":myUpdate", file.myUpdate);
+ d->setFileQuery.bindValue(":ed2k", file.ed2k);
+ d->setFileQuery.bindValue(":size", file.size);
+ d->setFileQuery.bindValue(":length", file.length);
+ d->setFileQuery.bindValue(":extension", file.extension);
+ d->setFileQuery.bindValue(":groupName", file.groupName);
+ d->setFileQuery.bindValue(":groupNameShort", file.groupNameShort);
+ d->setFileQuery.bindValue(":crc", file.crc);
+ d->setFileQuery.bindValue(":releaseDate", file.releaseDate);
+ d->setFileQuery.bindValue(":version", file.version);
+ d->setFileQuery.bindValue(":censored", file.censored);
+ d->setFileQuery.bindValue(":type", file.type);
+ d->setFileQuery.bindValue(":qualityId", file.qualityId);
+ d->setFileQuery.bindValue(":quality", file.quality);
+ d->setFileQuery.bindValue(":resolution", file.resolution);
+ d->setFileQuery.bindValue(":videoCodec", file.videoCodec);
+ d->setFileQuery.bindValue(":audioCodec", file.audioCodec);
+ d->setFileQuery.bindValue(":audioLanguage", file.audioLanguage);
+ d->setFileQuery.bindValue(":subtitleLanguage", file.subtitleLanguage);
+ d->setFileQuery.bindValue(":aspectRatio", file.aspectRatio);
+ d->setFileQuery.bindValue(":myWatched", file.myWatched);
+ d->setFileQuery.bindValue(":myState", file.myState);
+ d->setFileQuery.bindValue(":myFileState", file.myFileState);
+ d->setFileQuery.bindValue(":myStorage", file.myStorage);
+ d->setFileQuery.bindValue(":mySource", file.mySource);
+ d->setFileQuery.bindValue(":myOther", file.myOther);
+
+ return exec(d->setFileQuery);
+}
+
+bool Database::addTitle(const AnimeTitle &title)
+{
+ d->addTitleQuery.bindValue(":aid", title.aid);
+ d->addTitleQuery.bindValue(":type", int(title.type));
+ d->addTitleQuery.bindValue(":language", title.language);
+ d->addTitleQuery.bindValue(":title", title.title);
+
+ return exec(d->addTitleQuery);
+}
+
+bool Database::addAnime(const Anime &anime)
+{
+ d->addAnimeQuery.bindValue(":aid", anime.aid);
+ d->addAnimeQuery.bindValue(":anidbUpdate", anime.anidbUpdate);
+ d->addAnimeQuery.bindValue(":entryUpdate", anime.entryUpdate);
+ d->addAnimeQuery.bindValue(":myUpdate", anime.myUpdate);
+ d->addAnimeQuery.bindValue(":titleEnglish", anime.titleEnglish);
+ d->addAnimeQuery.bindValue(":titleRomaji", anime.titleRomaji);
+ d->addAnimeQuery.bindValue(":titleKanji", anime.titleKanji);
+ d->addAnimeQuery.bindValue(":description", anime.description);
+ d->addAnimeQuery.bindValue(":year", anime.year);
+ d->addAnimeQuery.bindValue(":startDate", anime.startDate);
+ d->addAnimeQuery.bindValue(":endDate", anime.endDate);
+ d->addAnimeQuery.bindValue(":type", anime.type);
+ d->addAnimeQuery.bindValue(":rating", anime.rating);
+ d->addAnimeQuery.bindValue(":votes", anime.votes);
+ d->addAnimeQuery.bindValue(":tempRating", anime.tempRating);
+ d->addAnimeQuery.bindValue(":tempVotes", anime.tempVotes);
+ d->addAnimeQuery.bindValue(":myVote", anime.myVote);
+ d->addAnimeQuery.bindValue(":myVoteDate", anime.myVoteDate);
+ d->addAnimeQuery.bindValue(":myTempVote", anime.myTempVote);
+ d->addAnimeQuery.bindValue(":myTempVoteDate", anime.myTempVoteDate);
+
+ return exec(d->addAnimeQuery);
+}
+
+bool Database::addEpisode(const Episode &episode)
+{
+ d->addEpisodeQuery.bindValue(":eid", episode.eid);
+ d->addEpisodeQuery.bindValue(":aid", episode.aid);
+ d->addEpisodeQuery.bindValue(":anidbUpdate", episode.anidbUpdate);
+ d->addEpisodeQuery.bindValue(":entryUpdate", episode.entryUpdate);
+ d->addEpisodeQuery.bindValue(":myUpdate", episode.myUpdate);
+ d->addEpisodeQuery.bindValue(":epno", episode.epno);
+ d->addEpisodeQuery.bindValue(":titleEnglish", episode.titleEnglish);
+ d->addEpisodeQuery.bindValue(":titleRomaji", episode.titleRomaji);
+ d->addEpisodeQuery.bindValue(":titleKanji", episode.titleKanji);
+ d->addEpisodeQuery.bindValue(":length", episode.length);
+ d->addEpisodeQuery.bindValue(":airdate", episode.airdate);
+ d->addEpisodeQuery.bindValue(":state", episode.state);
+ d->addEpisodeQuery.bindValue(":special", episode.special);
+ d->addEpisodeQuery.bindValue(":recap", episode.recap);
+ d->addEpisodeQuery.bindValue(":opening", episode.opening);
+ d->addEpisodeQuery.bindValue(":ending", episode.ending);
+ d->addEpisodeQuery.bindValue(":rating", episode.rating);
+ d->addEpisodeQuery.bindValue(":votes", episode.votes);
+ d->addEpisodeQuery.bindValue(":myVote", episode.myVote);
+ d->addEpisodeQuery.bindValue(":myVoteDate", episode.myVoteDate);
+
+ return exec(d->addEpisodeQuery);
+}
+
+bool Database::addFile(const File &file)
+{
+ d->addFileQuery.bindValue(":fid", file.fid);
+ d->addFileQuery.bindValue(":eid", file.eid);
+ d->addFileQuery.bindValue(":aid", file.aid);
+ d->addFileQuery.bindValue(":gid", file.gid);
+ d->addFileQuery.bindValue(":anidbUpdate", file.anidbUpdate);
+ d->addFileQuery.bindValue(":entryUpdate", file.entryUpdate);
+ d->addFileQuery.bindValue(":myUpdate", file.myUpdate);
+ d->addFileQuery.bindValue(":ed2k", file.ed2k);
+ d->addFileQuery.bindValue(":size", file.size);
+ d->addFileQuery.bindValue(":length", file.length);
+ d->addFileQuery.bindValue(":extension", file.extension);
+ d->addFileQuery.bindValue(":groupName", file.groupName);
+ d->addFileQuery.bindValue(":groupNameShort", file.groupNameShort);
+ d->addFileQuery.bindValue(":crc", file.crc);
+ d->addFileQuery.bindValue(":releaseDate", file.releaseDate);
+ d->addFileQuery.bindValue(":version", file.version);
+ d->addFileQuery.bindValue(":censored", file.censored);
+ d->addFileQuery.bindValue(":type", file.type);
+ d->addFileQuery.bindValue(":qualityId", file.qualityId);
+ d->addFileQuery.bindValue(":quality", file.quality);
+ d->addFileQuery.bindValue(":resolution", file.resolution);
+ d->addFileQuery.bindValue(":videoCodec", file.videoCodec);
+ d->addFileQuery.bindValue(":audioCodec", file.audioCodec);
+ d->addFileQuery.bindValue(":audioLanguage", file.audioLanguage);
+ d->addFileQuery.bindValue(":subtitleLanguage", file.subtitleLanguage);
+ d->addFileQuery.bindValue(":aspectRatio", file.aspectRatio);
+ d->addFileQuery.bindValue(":myWatched", file.myWatched);
+ d->addFileQuery.bindValue(":myState", file.myState);
+ d->addFileQuery.bindValue(":myFileState", file.myFileState);
+ d->addFileQuery.bindValue(":myStorage", file.myStorage);
+ d->addFileQuery.bindValue(":mySource", file.mySource);
+ d->addFileQuery.bindValue(":myOther", file.myOther);
+
+ return exec(d->addFileQuery);
+}
+
+bool Database::addFileEpisodeRel(const FileEpisodeRel &fileEpisodeRel)
+{
+ d->addFileEpisodeRelQuery.bindValue(":fid", fileEpisodeRel.fid);
+ d->addFileEpisodeRelQuery.bindValue(":eid", fileEpisodeRel.eid);
+ d->addFileEpisodeRelQuery.bindValue(":startPercent", fileEpisodeRel.startPercent);
+ d->addFileEpisodeRelQuery.bindValue(":endPercent", fileEpisodeRel.endPercent);
+
+ return exec(d->addFileEpisodeRelQuery);
+}
+
+bool Database::addUnknownFile(const UnknownFile &file)
+{
+ d->addUnknownFileQuery.bindValue(":ed2k", file.ed2k);
+ d->addUnknownFileQuery.bindValue(":size", file.size);
+ d->addUnknownFileQuery.bindValue(":hostId", file.hostId);
+ d->addUnknownFileQuery.bindValue(":path", file.path);
+
+ return exec(d->addUnknownFileQuery);
+}
+
+bool Database::addRequest(const PendingRequest &request)
+{
+ d->addPendingRequestQuery.bindValue(":aid", request.aid);
+ d->addPendingRequestQuery.bindValue(":eid", request.eid);
+ d->addPendingRequestQuery.bindValue(":fid", request.fid);
+ d->addPendingRequestQuery.bindValue(":ed2k", request.ed2k.isNull() ? QByteArray("") : request.ed2k);
+ d->addPendingRequestQuery.bindValue(":size", request.size);
+
+ return exec(d->addPendingRequestQuery);
+}
+
+QList<PendingRequest> Database::getRequestBatch(int limit)
+{
+ d->getRequestBatchQuery.bindValue(":limit", limit);
+
+ QList<PendingRequest> ret;
+
+ if (!exec(d->getRequestBatchQuery))
+ return ret;
+
+ while (d->getRequestBatchQuery.next())
+ {
+ PendingRequest request;
+ request.aid = d->getRequestBatchQuery.value(0).toInt();
+ request.eid = d->getRequestBatchQuery.value(1).toInt();
+ request.fid = d->getRequestBatchQuery.value(2).toInt();
+ request.ed2k = d->getRequestBatchQuery.value(3).toByteArray();
+ request.size = d->getRequestBatchQuery.value(4).toInt();
+ ret << request;
+ }
+
+ d->getRequestBatchQuery.finish();
+
+ return ret;
+}
+
+bool Database::clearRequest(const PendingRequest &request)
+{
+ d->clearRequestQuery.bindValue(":aid", request.aid);
+ d->clearRequestQuery.bindValue(":eid", request.eid);
+ d->clearRequestQuery.bindValue(":fid", request.fid);
+ d->clearRequestQuery.bindValue(":ed2k", request.ed2k.isNull() ? QByteArray("") : request.ed2k);
+ d->clearRequestQuery.bindValue(":size", request.size);
+
+ bool ret = exec(d->clearRequestQuery);
+ qDebug() << "AFFECTED" << d->clearRequestQuery.numRowsAffected();
+
+ return ret;
+}
+
+bool Database::truncateTitleData()
+{
+ return exec("TRUNCATE TABLE anime_title");
+}
+
+bool Database::truncateMyListData()
+{
+ return exec("TRUNCATE TABLE anime, episode, file, "
+ "file_episode_rel");
+}
+
+bool Database::truncateDatabase()
+{
+ return exec("TRUNCATE TABLE anime, anime_title, episode, file, "
+ "file_episode_rel, file_location, unknown_file");
+}
+
+bool Database::log(const QString &message, int type)
+{
+ d->addLogQuery.bindValue(":type", type);
+ d->addLogQuery.bindValue(":log", message);
+
+ qDebug() << "LOG:" << message << "; TYPE:" << type;
+
+ return exec(d->addLogQuery);
+}
+
+QSqlDatabase Database::connection() const
+{
+ return d->db;
+}
+
+bool Database::connect()
+{
+ if (!d)
+ {
+ d = new DatabaseInternal();
+ d->db = QSqlDatabase::addDatabase("QPSQL", connectionName);
+ }
+ else if (d->db.isOpen())
+ {
+ qDebug() << "Already Connected";
+ return true;
+ }
+
+ d->db.setHostName(m_connectionSettings.host);
+ if (m_connectionSettings.port)
+ d->db.setPort(m_connectionSettings.port);
+ d->db.setUserName(m_connectionSettings.user);
+ d->db.setPassword(m_connectionSettings.pass);
+ d->db.setDatabaseName(m_connectionSettings.database);
+ d->thread = QThread::currentThread();
+
+ bool success = d->db.open();
+ if (!success)
+ {
+ qWarning() << "Failed opening database connection." << d->db.lastError();
+ return success;
+ }
+
+ QObject::connect(d->db.driver(), SIGNAL(notification(QString)), this, SLOT(handleNotification(QString)));
+ prepareQueries();
+ emit connected();
+
+ return success;
+}
+
+void Database::disconnect()
+{
+ if (!d)
+ return;
+
+ if (!d->db.isOpen())
+ {
+ qDebug() << "Already Connected";
+ return;
+ }
+ d->db.close();
+ emit disconnected();
+}
+
+void Database::prepareQueries()
+{
+ d->getHostInfoQuery = QSqlQuery(d->db);
+ d->getHostInfoQuery.prepare("SELECT host_id, name, is_udp_host FROM host"
+ "WHERE name = :name");
+ d->isKnownFileQuery = QSqlQuery(d->db);
+ d->isKnownFileQuery.prepare("SELECT fid FROM file WHERE ed2k = :ed2k AND size = :size");
+
+ d->getSettingsQuery = QSqlQuery(d->db);
+ d->getSettingsQuery.prepare("SELECT key, value FROM config");
+
+ d->updateSettingQuery = QSqlQuery(d->db);
+ d->updateSettingQuery.prepare("UPDATE config SET value = :value WHERE key = :key");
+
+ d->getAnimeQuery = QSqlQuery(d->db);
+ d->getAnimeQuery.prepare("SELECT aid, anidb_update, entry_update, my_update, title_english, "
+ "title_romaji, title_kanji, description, year, start_date, end_date, "
+ "type, rating, votes, temp_rating, temp_votes, my_vote, my_vote_date, "
+ "my_temp_vote, my_temp_vote_date "
+ "FROM anime "
+ "WHERE aid = :aid");
+
+ d->getEpisodeQuery = QSqlQuery(d->db);
+ d->getEpisodeQuery.prepare("SELECT eid, aid, anidb_update, entry_update, my_update, epno, "
+ "title_english, title_romaji, title_kanji, length, airdate, state, "
+ "special, recap, opening, ending, rating, votes, my_vote, my_vote_date "
+ "FROM episode "
+ "WHERE eid = :eid");
+
+ d->getFileQuery = QSqlQuery(d->db);
+ d->getFileQuery.prepare("SELECT fid, eid, aid, gid, anidb_update, entry_update, my_update, "
+ "ed2k, size, length, extension, group_name, group_name_short, crc, "
+ "release_date, version, censored, type, quality_id, quality, resolution, "
+ "video_codec, audio_codec, audio_language, subtitle_language, aspect_ratio, "
+ "my_watched, my_state, my_file_state, my_storage, my_source, my_other "
+ "FROM file "
+ "WHERE fid = :fid");
+
+ d->setAnimeQuery = QSqlQuery(d->db);
+ d->setAnimeQuery.prepare("UPDATE anime SET "
+ "anidb_update = :anidbUpdate, entry_update = :entryUpdate, "
+ "my_update = :myUpdate, title_english = :titleEnglish, "
+ "title_romaji = :titleRomaji, title_kanji = :titleKanji, "
+ "description = :description, year = :year, start_date = :startDate, "
+ "end_date = :endDate, type = :type, rating = :rating, votes = :votes, "
+ "temp_rating = :tempRating, temp_votes = :tempVotes, my_vote = :myVote, "
+ "my_vote_date = :myVoteDate, my_temp_vote = :myTempVote, "
+ "my_temp_vote_date = :myTempVoteDate "
+ "WHERE aid = :aid");
+
+ d->setEpisodeQuery = QSqlQuery(d->db);
+ d->setEpisodeQuery.prepare("UPDATE episode SET "
+ "aid = :aid, anidb_update = :anidbUpdate, entry_update = :entryUpdate, "
+ "my_update = :myUpdate, epno = :epno, title_english = :titleEnglish, "
+ "title_romaji = :titleRomaji, title_kanji = :titleKanji, length = :length, "
+ "airdate = :airdate, state = :state, special = :special, recap = :recap, "
+ "opening = :opening, ending = :ending, rating = :rating, votes = :votes, "
+ "my_vote = :myVote, my_vote_date = :myVoteDate "
+ "WHERE eid = :eid");
+
+ d->setFileQuery = QSqlQuery(d->db);
+ d->setFileQuery.prepare("UPDATE file SET "
+ "eid = :eid, aid = :aid, gid = :gid, anidb_update = :anidbUpdate, "
+ "entry_update = :entryUpdate, my_update = :myUpdate, "
+ "ed2k = :ed2k, size = :size, length = :length, extension = :extension, "
+ "group_name = :groupName, group_name_short = :groupNameShort, crc = :crc, "
+ "release_date = :releaseDate, version = :version, censored = :censored, "
+ "type = :type, quality_id = :qualityId, quality = :quality, "
+ "resolution = :resolution, video_codec = :videoCodec, "
+ "audio_codec = :audioCodec, audio_language = :audioLanguage, "
+ "subtitle_language = :subtitleLanguage, aspect_ratio = :aspectRatio, "
+ "my_watched = :myWatched, my_state = :myState, my_file_state = :myFileState, "
+ "my_storage = :myStorage, my_source = :mySource, my_other = :myOther "
+ "WHERE fid = :fid");
+
+ d->addLogQuery = QSqlQuery(d->db);
+ d->addLogQuery.prepare("INSERT INTO log (type, log) VALUES (:type, :log)");
+
+ d->addTitleQuery = QSqlQuery(d->db);
+ d->addTitleQuery.prepare("INSERT INTO anime_title VALUES(:aid, :type, :language, :title)");
+
+ d->addAnimeQuery = QSqlQuery(d->db);
+ d->addAnimeQuery.prepare("INSERT INTO anime VALUES(:aid, :anidbUpdate, :entryUpdate, :myUpdate, :titleEnglish, "
+ ":titleRomaji, :titleKanji, :description, :year, :startDate, :endDate, :type, "
+ ":rating, :votes, :tempRating, :tempVotes, :myVote, :myVoteDate, "
+ ":myTempVote, :myTempVoteDate)");
+ d->addEpisodeQuery = QSqlQuery(d->db);
+ d->addEpisodeQuery.prepare("INSERT INTO episode VALUES(:eid, :aid, :anidbUpdate, :entryUpdate, :myUpdate, :epno, "
+ ":titleEnglish, :titleRomaji, :titleKanji, :length, :airdate, "
+ ":state, :special, :recap, :openineg, :ending, :rating, "
+ ":votes, :myVote, :myVoteDate)");
+ d->addFileQuery = QSqlQuery(d->db);
+ d->addFileQuery.prepare("INSERT INTO file VALUES(:fid, :eid, :aid, :gid, :anidbUpdate, :entryUpdate, :myUpdate, "
+ ":ed2k, :size, :length, :extension, :groupName, :groupNameShort, "
+ ":crc, :releaseDate, :version, :censored, :type, :qualityId, "
+ ":quality, :resolution, :vidoeCodec, :audioCodec, :audioLanguage, "
+ ":subtitleLanguage, :aspectRatio, :myWatched, :myState, "
+ ":myFileState, :myStorage, :mySource, :myOther)");
+ d->addFileEpisodeRelQuery = QSqlQuery(d->db);
+ d->addFileEpisodeRelQuery.prepare("INSERT INTO file_episode_rel VALUES(:fid, :eid, "
+ ":startPercentage, :endPercentage)");
+
+ d->addUnknownFileQuery = QSqlQuery(d->db);
+ d->addUnknownFileQuery.prepare("INSERT INTO unknown_file VALUES(:ed2k, :size, :hostId, :path)");
+
+ d->addPendingRequestQuery = QSqlQuery(d->db);
+ d->addPendingRequestQuery.prepare("INSERT INTO pending_request VALUES(:aid, :eid, :fid, :ed2k, :size, DEFAULT, DEFAULT, DEFAULT)");
+
+ d->getRequestBatchQuery = QSqlQuery(d->db);
+ d->getRequestBatchQuery.prepare("UPDATE pending_request SET start = NOW() "
+ "WHERE (aid, eid, fid, ed2k, size) IN (SELECT aid, eid, fid, ed2k, size FROM pending_request "
+ "WHERE start IS NULL "
+ "ORDER BY priority DESC, added ASC "
+ "LIMIT :limit) "
+ "RETURNING aid, eid, fid, ed2k, size");
+
+ d->clearRequestQuery = QSqlQuery(d->db);
+ d->clearRequestQuery.prepare("DELETE FROM pending_request WHERE aid = :aid AND eid = :eid AND fid = :fid AND ed2k = :ed2k AND size = :size");
+
+ d->db.driver()->subscribeToNotification("new_pending_request");
+}
+
+bool Database::exec(QSqlQuery &query)
+{
+ Q_ASSERT_X(d->thread == QThread::currentThread(), "threads", "DB used from different thread");
+ bool result = query.exec();
+ if (result)
+ return result;
+
+ qDebug() << "SQL error: " << query.lastError().type();
+ qDebug() << "Message: " << query.lastError().text();
+ qDebug() << "DB Message:" << query.lastError().databaseText();
+ qDebug() << "Query: " << query.executedQuery();
+
+ if (query.lastError().type() == QSqlError::ConnectionError)
+ disconnect();
+ return result;
+}
+
+bool Database::exec(const QString &sql)
+{
+ Q_ASSERT_X(d->thread == QThread::currentThread(), "threads", "DB used from different thread");
+ bool result = false;
+ bool connectionError = false;
+
+ {
+ QSqlQuery query(d->db);
+ result = query.exec(sql);
+
+ if (result)
+ return result;
+
+ qDebug() << "SQL error: " << query.lastError().type();
+ qDebug() << "Message: " << query.lastError().text();
+ qDebug() << "DB Message:" << query.lastError().databaseText();
+ qDebug() << "Query: " << query.executedQuery();
+
+ connectionError = query.lastError().type() == QSqlError::ConnectionError;
+ }
+
+ if (connectionError)
+ disconnect();
+
+ return result;
+}
+
+void Database::handleNotification(const QString ¬ification)
+{
+ qDebug() << "Recieved notification" << notification;
+ if (notification == "new_pending_request")
+ {
+ emit newPendingRequest();
+ }
+}
+
+} // namespace LocalMyList
--- /dev/null
+#ifndef DATABASE_H
+#define DATABASE_H
+
+#include "localmylist_global.h"
+#include <QVariant>
+#include <QSqlDatabase>
+#include <QSqlQuery>
+#include <QDateTime>
+
+namespace LocalMyList {
+
+struct LOCALMYLISTSHARED_EXPORT AnimeTitle
+{
+ enum TitleType {
+ PrimaryTitle = 1,
+ Synonym = 2,
+ ShortTitle = 3,
+ OfficialTitle = 4
+ };
+
+ int aid;
+ TitleType type;
+ QString language;
+ QString title;
+
+ AnimeTitle(int aid = 0, TitleType type = PrimaryTitle, const QString &language = QString(), const QString &title = QString());
+};
+
+struct LOCALMYLISTSHARED_EXPORT Anime
+{
+ int aid;
+ QDateTime anidbUpdate;
+ QDateTime entryUpdate;
+ QDateTime myUpdate;
+ QString titleEnglish;
+ QString titleRomaji;
+ QString titleKanji;
+ QString description;
+ QString year;
+ QDateTime startDate;
+ QDateTime endDate;
+ QString type;
+ double rating;
+ int votes;
+ double tempRating;
+ int tempVotes;
+ double myVote;
+ QDateTime myVoteDate;
+ double myTempVote;
+ QDateTime myTempVoteDate;
+
+ Anime();
+};
+
+struct LOCALMYLISTSHARED_EXPORT Episode
+{
+ int eid;
+ int aid;
+ QDateTime anidbUpdate;
+ QDateTime entryUpdate;
+ QDateTime myUpdate;
+ int epno;
+ QString titleEnglish;
+ QString titleRomaji;
+ QString titleKanji;
+ int length;
+ QDateTime airdate;
+ int state;
+ bool special;
+ bool recap;
+ bool opening;
+ bool ending;
+ double rating;
+ int votes;
+ double myVote;
+ QDateTime myVoteDate;
+
+ Episode();
+};
+
+struct LOCALMYLISTSHARED_EXPORT File
+{
+ int fid;
+ int eid;
+ int aid;
+ int gid;
+ QDateTime anidbUpdate;
+ QDateTime entryUpdate;
+ QDateTime myUpdate;
+ QByteArray ed2k;
+ qint64 size;
+ int length;
+ QString extension;
+ QString groupName;
+ QString groupNameShort;
+ QString crc;
+ QDateTime releaseDate;
+ int version;
+ bool censored;
+ QString type;
+ int qualityId;
+ QString quality;
+ QString resolution;
+ QString videoCodec;
+ QString audioCodec;
+ QString audioLanguage;
+ QString subtitleLanguage;
+ QString aspectRatio;
+ QDateTime myWatched;
+ int myState;
+ int myFileState;
+ QString myStorage;
+ QString mySource;
+ QString myOther;
+
+ File();
+};
+
+struct LOCALMYLISTSHARED_EXPORT UnknownFile
+{
+ QByteArray ed2k;
+ qint64 size;
+ int hostId;
+ QString path;
+
+ UnknownFile();
+};
+
+struct LOCALMYLISTSHARED_EXPORT FileEpisodeRel
+{
+ int fid;
+ int eid;
+ int startPercent;
+ int endPercent;
+
+ FileEpisodeRel();
+};
+
+struct LOCALMYLISTSHARED_EXPORT PendingRequest
+{
+ int aid;
+ int eid;
+ int fid;
+ QByteArray ed2k;
+ qint64 size;
+
+ PendingRequest();
+};
+
+struct LOCALMYLISTSHARED_EXPORT HostInfo
+{
+ int id;
+ QString name;
+ bool isUdpHost;
+
+ HostInfo();
+};
+
+struct LOCALMYLISTSHARED_EXPORT DatabaseConnectionSettings
+{
+ QString host;
+ quint16 port;
+ QString user;
+ QString pass;
+ QString database;
+
+ DatabaseConnectionSettings();
+};
+
+struct DatabaseInternal;
+
+class LOCALMYLISTSHARED_EXPORT Database : public QObject
+{
+ Q_OBJECT
+public:
+ Database(const QString &connectionName = "default");
+ ~Database();
+
+ bool isConnected() const;
+
+ void setConnectionSettings(const DatabaseConnectionSettings &dbs);
+ DatabaseConnectionSettings connectionSettings() const;
+
+ bool transaction();
+ bool commit();
+ bool rollback();
+
+ HostInfo getHostInfo(const QString &hostName);
+
+ QVariantMap getConfig();
+ bool setConfig(const QString &key, const QVariant &value);
+
+ int isKnownFile(const QByteArray &ed2k, qint64 size);
+ bool setFileLocation(int fid, int hostId, const QString &location);
+
+ Anime getAnime(int aid);
+ Episode getEpisode(int eid);
+ File getFile(int fid);
+
+ bool setAnime(const Anime &anime);
+ bool setEpisode(const Episode &episode);
+ bool setFile(const File &file);
+
+ bool addTitle(const AnimeTitle &title);
+ bool addAnime(const Anime &anime);
+ bool addEpisode(const Episode &episode);
+ bool addFile(const File &file);
+ bool addFileEpisodeRel(const FileEpisodeRel &fileEpisodeRel);
+ bool addUnknownFile(const UnknownFile &file);
+
+ bool addRequest(const PendingRequest &request);
+ QList<PendingRequest> getRequestBatch(int limit = 10);
+ bool clearRequest(const PendingRequest &request);
+
+ bool truncateTitleData();
+ bool truncateMyListData();
+ bool truncateDatabase();
+
+ bool log(const QString &message, int type = 1);
+
+ QSqlDatabase connection() const;
+
+public slots:
+ bool connect();
+ void disconnect();
+
+signals:
+ void connected();
+ void disconnected();
+
+ void newPendingRequest();
+
+private slots:
+ void handleNotification(const QString ¬ification);
+
+private:
+ bool exec(QSqlQuery &query);
+ bool exec(const QString &sql);
+ void prepareQueries();
+
+ DatabaseInternal *d;
+ DatabaseConnectionSettings m_connectionSettings;
+
+ QString connectionName;
+};
+
+} // namespace LocalMyList
+
+#endif // DATABASE_H
--- /dev/null
+#include "directoryscantask.h"
+
+#include "mylist.h"
+#include "addfiletask.h"
+
+#include <QDebug>
+
+namespace LocalMyList {
+
+DirectoryScanTask::DirectoryScanTask(Database *db, QObject *parent) :
+ AbstractTask(db, parent)
+{
+}
+
+QDir DirectoryScanTask::directory() const
+{
+ return m_directory;
+}
+
+QString DirectoryScanTask::taskSubject() const
+{
+ return m_directory.canonicalPath();
+}
+
+bool DirectoryScanTask::canUseThreads() const
+{
+ return true;
+}
+
+
+void DirectoryScanTask::setDirectory(QDir directory)
+{
+ m_directory = directory;
+}
+
+void DirectoryScanTask::start()
+{
+ stack.push(qMakePair(m_directory, 0));
+ emit nextWorkUnit();
+}
+
+void DirectoryScanTask::workUnit()
+{
+ qDebug() << "Starting work unit";
+ int operations = 0;
+
+ while (!stack.isEmpty() && operations < OPERATIONS_PER_UNIT)
+ {
+ QDir currentDir = stack.top().first;
+ int startIndex = stack.top().second;
+ stack.pop();
+
+ QFileInfoList entries = currentDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files);
+ qDebug() << entries.count() << "entries in" << currentDir.absolutePath();
+ int i;
+ for (i = startIndex; i < entries.count(); ++i)
+ {
+ if (operations >= OPERATIONS_PER_UNIT)
+ {
+ stack.push(qMakePair(currentDir, i));
+ break;
+ }
+ ++operations;
+
+ const QFileInfo &entry = entries[i];
+
+ if (entry.isDir())
+ {
+ qDebug() << "Queueing DIR " << entry.absoluteFilePath();
+ stack.push(qMakePair(QDir(entry.absoluteFilePath()), 0));
+ }
+ else if (entry.isFile())
+ {
+// qDebug() << "Queueing FILE " << entry.fileName();
+ QMetaObject::invokeMethod(MyList::instance(), "addFile", Qt::QueuedConnection, Q_ARG(QFileInfo, entry));
+ }
+ }
+
+ if (i < entries.count())
+ {
+ qDebug() << "ReQueueing DIR " << currentDir.absolutePath() << "i = " << i;
+ qMakePair(currentDir, i);
+ }
+ }
+
+ if (stack.isEmpty())
+ emit finished();
+ else
+ emit nextWorkUnit();
+}
+
+} // namespace LocalMyList
--- /dev/null
+#ifndef DIRECTORYSCANTASK_H
+#define DIRECTORYSCANTASK_H
+
+#include "abstracttask.h"
+
+#include <QDir>
+#include <QStack>
+#include <QPair>
+
+namespace LocalMyList {
+
+class DirectoryScanTask : public AbstractTask
+{
+ Q_OBJECT
+ Q_PROPERTY(QDir directory READ directory WRITE setDirectory)
+
+public:
+ explicit DirectoryScanTask(Database *db = 0, QObject *parent = 0);
+
+ QDir directory() const;
+
+ QString taskSubject() const;
+ bool canUseThreads() const;
+
+public slots:
+ void start();
+
+ void setDirectory(QDir directory);
+
+protected:
+ void workUnit();
+
+ QDir m_directory;
+
+ QStack<QPair<QDir, int > > stack;
+};
+
+} // namespace LocalMyList
+
+#endif // DIRECTORYSCANTASK_H
--- /dev/null
+#include ""
\ No newline at end of file
--- /dev/null
+#include ""
\ No newline at end of file
--- /dev/null
+QT += network sql xml xmlpatterns
+QT -= gui
+
+TARGET = localmylist
+DESTDIR = ../build
+TEMPLATE = lib
+
+DEFINES += LOCALMYLIST_LIBRARY
+
+SOURCES += \
+ parser/animetitledatparser.cpp \
+ database.cpp \
+ parser/mylistexportxmlparser.cpp \
+ workthread.cpp \
+ addfiletask.cpp \
+ abstracttask.cpp \
+ directoryscantask.cpp \
+ requesthandler.cpp \
+ mylist.cpp \
+ animetitleparsetask.cpp \
+ mylistexportparsetask.cpp \
+ settings.cpp
+
+HEADERS +=\
+ localmylist_global.h \
+ parser/animetitledatparser.h \
+ database.h \
+ parser/mylistexportxmlparser.h \
+ workthread.h \
+ addfiletask.h \
+ abstracttask.h \
+ directoryscantask.h \
+ requesthandler.h \
+ mylist.h \
+ animetitleparsetask.h \
+ mylistexportparsetask.h \
+ settings.h
+
+unix {
+ target.path = /usr/lib
+ INSTALLS += target
+}
+
+LIBS += -lanidbudpclient
--- /dev/null
+#ifndef LOCALMYLIST_GLOBAL_H
+#define LOCALMYLIST_GLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(LOCALMYLIST_LIBRARY)
+# define LOCALMYLISTSHARED_EXPORT Q_DECL_EXPORT
+#else
+# define LOCALMYLISTSHARED_EXPORT Q_DECL_IMPORT
+#endif
+
+static const char *organizationName = "APTX";
+static const char *libraryName = "localmylist";
+
+#endif // LOCALMYLIST_GLOBAL_H
--- /dev/null
+#include "mylist.h"
+
+#include <QMetaType>
+#include <QSettings>
+#include <QCoreApplication>
+
+#include "database.h"
+#include "abstracttask.h"
+#include "directoryscantask.h"
+#include "addfiletask.h"
+#include "animetitleparsetask.h"
+#include "mylistexportparsetask.h"
+#include "workthread.h"
+#include "requesthandler.h"
+#include <AniDBUdpClient/Client>
+
+namespace LocalMyList {
+
+MyList::MyList()
+{
+ init();
+ loadLocalSettings();
+
+ m_requestHandler = 0;
+
+ db = new Database("main");
+ db->setConnectionSettings(dbs);
+ db->connect();
+ m_settings = new Settings(db, this);
+ workThread = new WorkThread("workThread", dbs, this);
+ workThread->start();
+
+ setupUdpClient();
+ setupRequestHandler();
+}
+
+MyList::~MyList()
+{
+ delete db;
+ delete workThread;
+}
+
+QString MyList::hostName() const
+{
+ return hostInfo.name;
+}
+
+int MyList::hostId() const
+{
+ return hostInfo.id;
+}
+
+bool MyList::isUdpHost() const
+{
+ return hostInfo.isUdpHost;
+}
+
+void MyList::setHostName(QString name)
+{
+ hostInfo.name = name;
+}
+
+Database *MyList::database() const
+{
+ return db;
+}
+
+Settings *MyList::settings() const
+{
+ return m_settings;
+}
+
+
+// -------
+
+void MyList::setupUdpClient()
+{
+ using namespace ::AniDBUdpClient;
+ Client::instance()->setHost(m_settings->get("udpClientHost").toString());
+ Client::instance()->setHostPort(m_settings->get("udpClientHostPort").toUInt());
+ Client::instance()->setLocalPort(m_settings->get("udpClientLocalPort").toUInt());
+ Client::instance()->setUser(m_settings->get("udpClientUser").toString());
+ Client::instance()->setPass(m_settings->get("udpClientPass").toString());
+ Client::instance()->setEncryptionEnabled(m_settings->get("udpClientEncryptionEnabled").toBool());
+ Client::instance()->setApiKey(m_settings->get("udpClientApiKey").toString());
+}
+
+void MyList::setupRequestHandler()
+{
+ if(m_requestHandler)
+ return;
+
+ m_requestHandler = new RequestHandler(db, this);
+ connect(db, SIGNAL(newPendingRequest()), m_requestHandler, SLOT(handleRequests()));
+}
+
+void MyList::loadLocalSettings()
+{
+ QSettings s(QSettings::IniFormat, QSettings::UserScope, organizationName, libraryName);
+ s.beginGroup("general");
+ hostInfo.name = s.value("hostName", QHostInfo::localHostName().toLower()).toString();
+ s.endGroup();
+ s.beginGroup("database");
+ dbs.host = s.value("host", "localhost").toString();
+ dbs.port = quint16(s.value("port", 0).toUInt());
+ dbs.user = s.value("user", "localmylist").toString();
+ dbs.pass = s.value("pass", "localmylist").toString();
+ dbs.database = s.value("database", "localmylist").toString();
+ s.endGroup();
+}
+
+void MyList::saveLocalSettings()
+{
+ QSettings s(QSettings::IniFormat, QSettings::UserScope, organizationName, libraryName);
+ s.beginGroup("general");
+ s.setValue("hostName", hostInfo.name);
+ s.endGroup();
+ s.beginGroup("database");
+ s.setValue("host", dbs.host);
+ s.setValue("port", dbs.port);
+ s.setValue("user", dbs.user);
+ s.setValue("pass", dbs.pass);
+ s.setValue("database", dbs.database);
+ s.endGroup();
+}
+
+AbstractTask *MyList::addFile(const QFileInfo &file)
+{
+ AddFileTask *task = new AddFileTask();
+ task->setFile(file);
+ executeTask(task);
+ return task;
+}
+
+AbstractTask *MyList::addDirectory(const QDir &directory)
+{
+ DirectoryScanTask *task = new DirectoryScanTask();
+ task->setDirectory(directory);
+ executeTask(task);
+ return task;
+}
+
+AbstractTask *MyList::importTitles(const QFileInfo &file)
+{
+ AnimeTitleParseTask *task = new AnimeTitleParseTask();
+ task->setFile(file);
+ executeTask(task);
+ return task;
+}
+
+AbstractTask *MyList::importMyList(const QFileInfo &file)
+{
+ MyListExportParseTask *task = new MyListExportParseTask();
+ task->setFile(file);
+ executeTask(task);
+ return task;
+}
+
+void MyList::executeTask(AbstractTask *task)
+{
+ if (task->canUseThreads())
+ {
+ task->setDatabase(workThread->database());
+ task->moveToThread(workThread);
+ }
+ else
+ {
+ if (task->thread() != thread())
+ task->moveToThread(thread());
+
+ task->setDatabase(database());
+ }
+
+ connect(task, SIGNAL(finished()), this, SLOT(taskFinished()), Qt::QueuedConnection);
+ tasks.insert(task);
+ db->log(tr("Starting task %1 on %2").arg(task->taskName(), task->taskSubject()));
+
+ QMetaObject::invokeMethod(task, "start", Qt::QueuedConnection);
+}
+
+void MyList::taskFinished()
+{
+ AbstractTask *task = qobject_cast<AbstractTask *>(sender());
+ Q_ASSERT(task);
+ tasks.remove(task);
+ db->log(tr("Task %1 on %2 finished").arg(task->taskName(), task->taskSubject()));
+ task->deleteLater();
+}
+
+MyList *MyList::instance()
+{
+ if (!m_instance)
+ m_instance = new MyList;
+ return m_instance;
+}
+
+void MyList::init()
+{
+ static bool init = false;
+ if (init) return;
+
+ if (!REGISTER_QT_TYPES) return;
+// qRegisterMetaType<AbstractTaskPtr>("AbstractTaskPtr");
+ qRegisterMetaType<QFileInfo>("QFileInfo");
+}
+
+bool MyList::REGISTER_QT_TYPES = true;
+MyList *MyList::m_instance = 0;
+
+MyList *instance()
+{
+ return MyList::instance();
+}
+
+} // namespace LocalMyList
--- /dev/null
+#ifndef LOCALMYLIST_H
+#define LOCALMYLIST_H
+
+#include "localmylist_global.h"
+#include "database.h"
+#include "settings.h"
+#include <QObject>
+
+#include <QSet>
+#include <QDir>
+#include <QFileInfo>
+
+namespace LocalMyList {
+
+class AbstractTask;
+class WorkThread;
+class RequestHandler;
+
+class LOCALMYLISTSHARED_EXPORT MyList : public QObject {
+ Q_OBJECT
+ Q_PROPERTY(QString hostName READ hostName WRITE setHostName)
+ Q_PROPERTY(int hostId READ hostId)
+
+public:
+ MyList();
+ ~MyList();
+
+ Database *database() const;
+ Settings *settings() const;
+
+ QString hostName() const;
+ int hostId() const;
+ bool isUdpHost() const;
+
+public slots:
+ void setHostName(QString name);
+
+
+ AbstractTask *addFile(const QFileInfo &file);
+ AbstractTask *addDirectory(const QDir &directory);
+ AbstractTask *importTitles(const QFileInfo &file);
+ AbstractTask *importMyList(const QFileInfo &file);
+ void executeTask(AbstractTask *task);
+
+ void setupUdpClient();
+ void setupRequestHandler();
+
+ void loadLocalSettings();
+ void saveLocalSettings();
+
+private slots:
+ void taskFinished();
+
+
+signals:
+ void requestAddDirectory(const QDir &directory);
+
+private:
+ DatabaseConnectionSettings dbs;
+ Database *db;
+ WorkThread *workThread;
+ RequestHandler *m_requestHandler;
+ Settings *m_settings;
+
+ HostInfo hostInfo;
+
+ QSet<AbstractTask *> tasks;
+
+public:
+ static MyList *instance();
+ static bool REGISTER_QT_TYPES;
+
+private:
+ static void init();
+ static MyList *m_instance;
+};
+
+LOCALMYLISTSHARED_EXPORT MyList *instance();
+
+} // namespace LocalMyList
+
+#endif // LOCALMYLIST_H
--- /dev/null
+#include "mylistexportparsetask.h"
+
+#include "database.h"
+
+#include <QDebug>
+
+namespace LocalMyList {
+
+MyListExportParseTask::MyListExportParseTask(QObject *parent) :
+ AbstractTask(0, parent), QXmlStreamReader()
+{
+ dateTimeFormat = "dd.MM.yyyy hh:mm";
+ dateFormat = "dd.MM.yyyy";
+}
+
+QFileInfo MyListExportParseTask::file() const
+{
+ return m_file;
+}
+
+QString MyListExportParseTask::taskSubject() const
+{
+ return QString("Anime Title Parse (from file: %1)").arg(m_file.fileName());
+}
+
+bool MyListExportParseTask::canUseThreads() const
+{
+ return true;
+}
+
+void MyListExportParseTask::start()
+{
+ qDebug() << "Started";
+ QFile f(m_file.absoluteFilePath());
+ if (!f.open(QIODevice::ReadOnly))
+ {
+ emit finished();
+ qWarning("MyListExportParseTask failed to open file");
+ return;
+ }
+
+ parse(&f);
+ emit finished();
+}
+
+void MyListExportParseTask::setFile(const QFileInfo &file)
+{
+ m_file = file;
+}
+
+bool MyListExportParseTask::parse(QIODevice *device)
+{
+ setDevice(device);
+ animeCount = episodeCount = fileCount = 0;
+ animeRead = episodesRead = filesRead = 0;
+
+ while (!atEnd())
+ {
+ readNext();
+ if (isStartElement())
+ {
+ if (name() == "my_anime_list")
+ {
+ readMyList();
+ }
+ else
+ {
+ raiseError(QObject::tr("The file is not a \"mylist-xml-new\" file."));
+ }
+ }
+ }
+
+ return !error();
+}
+
+void MyListExportParseTask::readUnknownElement()
+{
+ Q_ASSERT(isStartElement());
+
+ int i = 1;
+ while (!atEnd())
+ {
+ readNext();
+ if (isStartElement())
+ ++i;
+
+ if (isEndElement())
+ --i;
+
+ if (!i)
+ break;
+ }
+}
+
+void MyListExportParseTask::readMyList()
+{
+ Q_ASSERT(isStartElement());
+
+ while (!atEnd()) {
+ readNext();
+ if (isEndElement())
+ break;
+
+ if (isStartElement()) {
+ if (name() == "anime") {
+ readAnime();
+ reportProgress();
+ }
+ else if (name() == "episode") {
+ readEpisode();
+ reportProgress();
+ }
+ else if (name() == "file") {
+ readFile();
+ reportProgress();
+ }
+ else if (name() == "file_episode") {
+ readFileEpisodeRel();
+ reportProgress();
+ }
+ else if (name() == "user_info") {
+ readUserInfo();
+ qDebug() << "Entry count" << (animeCount + fileCount + episodeCount);
+ } else {
+ readUnknownElement();
+ }
+ }
+ }
+}
+
+void MyListExportParseTask::readUserInfo()
+{
+ while (!atEnd()) {
+ readNext();
+ if (isEndElement())
+ break;
+
+ if (isStartElement()) {
+ if (name() == "ExportDate") {
+ exportDate = QDateTime::fromString(readElementText(), dateTimeFormat);
+ }
+ else if (name() == "MyAnimeCount") {
+ animeCount = readElementText().toInt();
+ }
+ else if (name() == "MyEpisodeCount") {
+ episodeCount = readElementText().toInt();
+ }
+ else if (name() == "MyFileCount") {
+ fileCount = readElementText().toInt();
+ } else {
+ readUnknownElement();
+ }
+ }
+ }
+qDebug() << "Exported on" << exportDate;
+}
+
+void MyListExportParseTask::readAnime()
+{
+ Q_ASSERT(isStartElement());
+
+ Anime a;
+
+ while (!atEnd()) {
+ readNext();
+ if (isEndElement())
+ break;
+
+ if (isStartElement()) {
+ if (name() == "AnimeID") {
+ a.aid = readElementText().toInt();
+ }
+ else if (name() == "Update") {
+ a.anidbUpdate = QDateTime::fromTime_t(readElementText().toInt());
+ }
+ else if (name() == "Name") {
+ a.titleRomaji = readElementText();
+ }
+ else if (name() == "NameKanji") {
+ a.titleKanji = readElementText();
+ }
+ else if (name() == "NameEnglish") {
+ a.titleEnglish = readElementText();
+ }
+ else if (name() == "AnimeDescription") {
+ a.description = readElementText();
+ }
+ else if (name() == "Year") {
+ a.year = readElementText();
+ }
+ else if (name() == "StartDate") {
+ a.startDate = QDateTime::fromString(readElementText(), dateFormat);
+ }
+ else if (name() == "EndDate") {
+ a.endDate = QDateTime::fromString(readElementText(), dateFormat);
+ }
+ else if (name() == "Rating") {
+ a.rating = readElementText().toDouble();
+ }
+ else if (name() == "Votes") {
+ a.votes = readElementText().toInt();
+ }
+ else if (name() == "TempRating") {
+ a.tempRating = readElementText().toDouble();
+ }
+ else if (name() == "TempVotes") {
+ a.tempVotes = readElementText().toInt();
+ }
+ else if (name() == "MyVote") {
+ a.myVote = readElementText().toDouble();
+ }
+ else if (name() == "MyVoteDate") {
+ a.myVoteDate = QDateTime::fromString(readElementText(), dateTimeFormat);
+ }
+ else if (name() == "MyTempVote") {
+ a.myTempVote = readElementText().toDouble();
+ }
+ else if (name() == "MyTempVoteDate") {
+ a.myTempVoteDate = QDateTime::fromString(readElementText(), dateTimeFormat);
+ }
+ else if (name() == "TypeName") {
+ a.type = readElementText();
+ } else {
+ readUnknownElement();
+ }
+ }
+ }
+
+ ++animeRead;
+
+ db->transaction();
+ Anime current = db->getAnime(a.aid);
+
+ if (!current.aid)
+ {
+qDebug() << "Adding new Anime";
+ a.entryUpdate = QDateTime::currentDateTime();
+ a.myUpdate = exportDate;
+ db->addAnime(a);
+ db->commit();
+ return;
+ }
+
+ if (current.anidbUpdate < a.anidbUpdate)
+ {
+qDebug() << "Updating Anime" << current.aid;
+ if (a.anidbUpdate.isValid())
+ current.anidbUpdate = a.anidbUpdate;
+ if (!a.titleRomaji.isEmpty())
+ current.titleRomaji = a.titleRomaji;
+ if (!a.titleKanji.isEmpty())
+ current.titleKanji = a.titleKanji;
+ if (!a.titleEnglish.isEmpty())
+ current.titleEnglish = a.titleEnglish;
+ if (!a.description.isEmpty())
+ current.description = a.description;
+ if (a.year.isEmpty())
+ current.year = a.year;
+ if (a.startDate.isValid())
+ current.startDate = a.startDate;
+ if (a.endDate.isValid())
+ current.endDate = a.endDate;
+ if (!a.type.isEmpty())
+ current.type = a.type;
+ }else{qDebug() << "No update required" << current.aid;}
+
+ current.entryUpdate = QDateTime::currentDateTime();
+ if (a.rating)
+ current.rating = a.rating;
+ if (a.votes)
+ current.votes = a.votes;
+ if (a.tempRating)
+ current.tempRating = a.tempRating;
+ if (a.tempVotes)
+ current.tempVotes = a.tempVotes;
+
+ if (current.myUpdate < exportDate)
+ {
+ qDebug() << "My var update" << current.aid;
+ current.myUpdate = exportDate;
+ if (a.myVote)
+ current.myVote = a.myVote;
+ if (a.myVoteDate.isValid())
+ current.myVoteDate = a.myVoteDate;
+ if (a.myTempVote)
+ current.myTempVote = a.myTempVote;
+ if (a.myTempVoteDate.isValid())
+ current.myTempVoteDate = a.myTempVoteDate;
+ }
+
+ db->setAnime(current);
+ db->commit();
+}
+
+void MyListExportParseTask::readEpisode()
+{
+ Q_ASSERT(isStartElement());
+
+ Episode e;
+
+ while (!atEnd()) {
+ readNext();
+ if (isEndElement())
+ break;
+
+ if (isStartElement()) {
+ if (name() == "EpID") {
+ e.eid = readElementText().toInt();
+ }
+ else if (name() == "AnimeID") {
+ e.aid = readElementText().toInt();
+ }
+ else if (name() == "EpUpdate") {
+ e.anidbUpdate = QDateTime::fromString(readElementText(), dateTimeFormat);
+ }
+ else if (name() == "EpNo") {
+ e.epno = readElementText().toInt();
+ }
+ else if (name() == "EpNameRomaji") {
+ e.titleRomaji = readElementText();
+ }
+ else if (name() == "EpNameKanji") {
+ e.titleKanji = readElementText();
+ }
+ else if (name() == "EpName") {
+ e.titleEnglish = readElementText();
+ }
+ else if (name() == "EpLength") {
+ e.length = readElementText().toInt();
+ }
+ else if (name() == "EpAired") {
+ e.airdate = QDateTime::fromString(readElementText(), dateTimeFormat);
+ }
+ else if (name() == "EpState") {
+ e.state = readElementText().toInt();
+ }
+ else if (name() == "EpStateSpecial") {
+ e.special = bool(readElementText().toInt());
+ }
+ else if (name() == "EpStateRecap") {
+ e.recap = bool(readElementText().toInt());
+ }
+ else if (name() == "EpStateOp") {
+ e.opening = bool(readElementText().toInt());
+ }
+ else if (name() == "EpStateEnd") {
+ e.ending = bool(readElementText().toInt());
+ }
+ else if (name() == "EpRating") {
+ e.rating = readElementText().toDouble();
+ }
+ else if (name() == "EpVotes") {
+ e.votes = readElementText().toInt();
+ }
+ else if (name() == "EpMyVote") {
+ e.myVote = readElementText().toDouble();
+ }
+ else if (name() == "EpMyVoteDate") {
+ e.myVoteDate = QDateTime::fromString(readElementText(), dateTimeFormat);
+ } else {
+ readUnknownElement();
+ }
+ }
+ }
+
+
+ ++episodesRead;
+
+ db->transaction();
+ Episode current = db->getEpisode(e.eid);
+
+ if (!current.eid)
+ {
+qDebug() << "Adding new Episode";
+ e.entryUpdate = QDateTime::currentDateTime();
+ e.myUpdate = exportDate;
+ db->addEpisode(e);
+ db->commit();
+ return;
+ }
+
+ if (current.anidbUpdate < e.anidbUpdate)
+ {
+qDebug() << "Updating Episode" << current.eid;
+ if (e.aid)
+ current.aid = e.aid;
+ if (e.anidbUpdate.isValid())
+ current.anidbUpdate = e.anidbUpdate;
+ if (e.epno)
+ current.epno = e.epno;
+ if (!e.titleRomaji.isEmpty())
+ current.titleRomaji = e.titleRomaji;
+ if (!e.titleKanji.isEmpty())
+ current.titleKanji = e.titleKanji;
+ if (!e.titleEnglish.isEmpty())
+ current.titleEnglish = e.titleEnglish;
+ if (e.length)
+ current.length = e.length;
+ if (e.airdate.isValid())
+ current.airdate = e.airdate;
+ current.state = e.state;
+ current.special = e.special;
+ current.recap = e.recap;
+ current.opening = e.opening;
+ current.ending = e.ending;
+ }else{qDebug() << "No update required" << current.eid;}
+
+ current.entryUpdate = QDateTime::currentDateTime();
+ if (e.rating)
+ current.rating = e.rating;
+ if (e.votes)
+ current.votes = e.votes;
+
+ if (current.myUpdate < exportDate)
+ {
+qDebug() << "My var update" << current.eid;
+ current.myUpdate = exportDate;
+ if (e.myVote)
+ current.myVote = e.myVote;
+ if (e.myVoteDate.isValid())
+ current.myVoteDate = e.myVoteDate;
+ }
+ db->setEpisode(current);
+ db->commit();
+}
+
+void MyListExportParseTask::readFile()
+{
+ Q_ASSERT(isStartElement());
+
+ File f;
+ bool generic;
+
+ while (!atEnd())
+ {
+ readNext();
+ if (isEndElement())
+ break;
+
+ if (isStartElement()) {
+ if (name() == "FID") {
+ f.fid = readElementText().toInt();
+ }
+ else if (name() == "EpID") {
+ f.eid = readElementText().toInt();
+ }
+ else if (name() == "AnimeID") {
+ f.aid = readElementText().toInt();
+ }
+ else if (name() == "GID") {
+ f.gid = readElementText().toInt();
+ }
+ else if (name() == "Update") {
+ f.anidbUpdate = QDateTime::fromString(readElementText(), dateTimeFormat);
+ }
+ else if (name() == "ed2kHash") {
+ f.ed2k = readElementText().toAscii();
+ }
+ else if (name() == "Size") {
+ f.size = readElementText().toLongLong();
+ }
+ else if (name() == "Length") {
+ f.length = readElementText().toInt();
+ }
+ else if (name() == "FileType") {
+ QString text = readElementText();
+ bool ok;
+ text.toInt(&ok);
+ if (!ok)
+ f.extension = text;
+ }
+ else if (name() == "GName") {
+ f.groupName = readElementText();
+ }
+ else if (name() == "GShortName") {
+ f.groupNameShort = readElementText();
+ }
+ else if (name() == "CRC") {
+ f.crc = readElementText();
+ }
+ else if (name() == "ReleaseDate") {
+ f.releaseDate = QDateTime::fromString(readElementText(), dateTimeFormat);
+ }
+ else if (name() == "VersionName") {
+ f.version = readElementText().mid(1).toInt();
+ }
+ // f.censored;
+ else if (name() == "TypeName") {
+ f.type = readElementText();
+ }
+ else if (name() == "QualityID") {
+ f.qualityId = readElementText().toInt();
+ }
+ else if (name() == "QualityName") {
+ f.quality = readElementText();
+ }
+ else if (name() == "ResName") {
+ f.resolution = readElementText();
+ }
+ else if (name() == "VCodecName") {
+ f.videoCodec = readElementText();
+ }
+ else if (name() == "ACodecName") {
+ f.audioCodec = readElementText();
+ }
+ else if (name() == "ALangName") {
+ f.audioLanguage = readElementText();
+ }
+ else if (name() == "SubName") {
+ f.subtitleLanguage = readElementText();
+ }
+ else if (name() == "VAspectRatioName") {
+ f.aspectRatio = readElementText();
+ }
+ else if (name() == "ViewDate") {
+ f.myWatched = QDateTime::fromString(readElementText(), dateTimeFormat);
+ }
+ else if (name() == "MyState") {
+ f.myState = readElementText().toInt();
+ }
+ else if (name() == "MyFileState") {
+ f.myFileState = readElementText().toInt();
+ }
+/*
+ else if (name() == "Storage") {
+ f.myStorage = readElementText();
+ }
+ else if (name() == "Source") {
+ f.mySource = readElementText();
+ }
+ else if (name() == "Other") {
+ f.myOther = readElementText();
+ }
+*/
+ else if (name() == "Generic") {
+ generic = bool(readElementText().toInt());
+ } else {
+ readUnknownElement();
+ }
+ }
+ }
+
+ ++filesRead;
+
+ if (generic)
+ return;
+
+ db->transaction();
+ File current = db->getFile(f.fid);
+
+ if (!current.fid)
+ {
+qDebug() << "Adding new File";
+ f.entryUpdate = QDateTime::currentDateTime();
+ f.myUpdate = exportDate;
+ db->addFile(f);
+ db->commit();
+ return;
+ }
+
+ if (current.anidbUpdate < f.anidbUpdate)
+ {
+qDebug() << "Updating File" << current.fid;
+ if (f.eid)
+ current.eid = f.eid;
+ if (f.aid)
+ current.aid = f.aid;
+ if (f.gid)
+ current.gid = f.gid;
+ if (f.anidbUpdate.isValid())
+ current.anidbUpdate = f.anidbUpdate;
+ if (!f.ed2k.isEmpty())
+ current.ed2k = f.ed2k;
+ if (f.size)
+ current.size = f.size;
+ if (f.length)
+ current.length = f.length;
+ if (!f.extension.isEmpty())
+ current.extension = f.extension;
+ if (!f.groupName.isEmpty())
+ current.groupName = f.groupName;
+ if (!f.groupNameShort.isEmpty())
+ current.groupNameShort = f.groupNameShort;
+ if (!f.crc.isEmpty())
+ current.crc = f.crc;
+ if (f.releaseDate.isValid())
+ current.releaseDate = f.releaseDate;
+ if (f.version)
+ current.version = f.version;
+ if (f.censored)
+ current.censored = f.censored;
+ if (!f.type.isEmpty())
+ current.type = f.type;
+ if (f.qualityId)
+ current.qualityId = f.qualityId;
+ if (!f.quality.isEmpty())
+ current.quality = f.quality;
+ if (!f.resolution.isEmpty())
+ current.resolution = f.resolution;
+ if (!f.videoCodec.isEmpty())
+ current.videoCodec = f.videoCodec;
+ if (!f.audioCodec.isEmpty())
+ current.audioCodec = f.audioCodec;
+ if (!f.audioLanguage.isEmpty())
+ current.audioLanguage = f.audioLanguage;
+ if (!f.subtitleLanguage.isEmpty())
+ current.subtitleLanguage = f.subtitleLanguage;
+ if (!f.aspectRatio.isEmpty())
+ current.aspectRatio = f.aspectRatio;
+ }else{qDebug() << "No update required" << current.fid;}
+
+ current.entryUpdate = QDateTime::currentDateTime();
+
+ if (current.myUpdate < exportDate)
+ {
+ qDebug() << "My var update" << current.fid;
+ current.myUpdate = exportDate;
+ if (f.myWatched.isValid())
+ current.myWatched = f.myWatched;
+ if (f.myState)
+ current.myState = f.myState;
+ if (f.myFileState)
+ current.myFileState = f.myFileState;
+ if (!f.myStorage.isEmpty())
+ current.myStorage = f.myStorage;
+ if (!f.mySource.isEmpty())
+ current.mySource = f.mySource;
+ if (!f.myOther.isEmpty())
+ current.myOther = f.myOther;
+ }
+ db->setFile(current);
+ db->commit();
+}
+
+void MyListExportParseTask::readFileEpisodeRel()
+{
+ Q_ASSERT(isStartElement());
+
+ FileEpisodeRel fe;
+
+ while (!atEnd())
+ {
+ readNext();
+ if (isEndElement())
+ break;
+
+ if (isStartElement()) {
+ if (name() == "FID") {
+ fe.fid = readElementText().toInt();
+ }
+ else if (name() == "EpID") {
+ fe.eid = readElementText().toInt();
+ }
+ else if (name() == "StartPercent") {
+ fe.startPercent = readElementText().toInt();
+ }
+ else if (name() == "EndPercent") {
+ fe.endPercent = readElementText().toInt();
+ } else {
+ readUnknownElement();
+ }
+ }
+ }
+
+ db->addFileEpisodeRel(fe);
+}
+
+void MyListExportParseTask::reportProgress()
+{
+ int read = animeRead + filesRead + episodesRead;
+ int total = animeCount + fileCount + episodeCount;
+ if (read % 200 != 0)
+ return;
+ emit progress(read, total);
+ qDebug() << "Read" << read << "entries";
+
+}
+
+} // namespace LocalMyList
--- /dev/null
+#ifndef MYLISTEXPORTPARSETASK_H
+#define MYLISTEXPORTPARSETASK_H
+
+#include "abstracttask.h"
+#include <QXmlStreamReader>
+#include <QFileInfo>
+#include <QDateTime>
+
+namespace LocalMyList {
+
+class MyListExportParseTask : public AbstractTask, public QXmlStreamReader
+{
+ Q_OBJECT
+public:
+ explicit MyListExportParseTask(QObject *parent = 0);
+
+ QFileInfo file() const;
+ QString taskSubject() const;
+ bool canUseThreads() const;
+
+ void start();
+
+public slots:
+ void setFile(const QFileInfo &file);
+
+private:
+ bool parse(QIODevice *device);
+
+ void readUnknownElement();
+
+ void readMyList();
+ void readUserInfo();
+ void readAnime();
+ void readEpisode();
+ void readFile();
+ void readFileEpisodeRel();
+
+ void reportProgress();
+
+ int animeCount;
+ int episodeCount;
+ int fileCount;
+
+ int animeRead;
+ int episodesRead;
+ int filesRead;
+
+ QDateTime exportDate;
+
+ QString dateTimeFormat;
+ QString dateFormat;
+
+ QFileInfo m_file;
+};
+
+} // namespace LocalMyList
+
+#endif // MYLISTEXPORTPARSETASK_H
--- /dev/null
+#include "requesthandler.h"
+
+#include "database.h"
+
+#include <AniDBUdpClient/Client>
+#include <AniDBUdpClient/AnimeCommand>
+#include <AniDBUdpClient/EpisodeCommand>
+#include <AniDBUdpClient/FileCommand>
+#include <AniDBUdpClient/VoteCommand>
+#include <AniDBUdpClient/MyListAddCommand>
+
+#include <QDebug>
+
+namespace LocalMyList {
+
+RequestHandler::RequestHandler(Database *db, QObject *parent) :
+ QObject(parent)
+{
+ this->db = db;
+ connect(this, SIGNAL(batchFinished()), this, SLOT(handleRequests()), Qt::QueuedConnection);
+}
+
+void RequestHandler::handleRequests()
+{
+ using namespace ::AniDBUdpClient;
+
+ qDebug() << "handleRequests";
+
+ static const AnimeFlags animeFlags =
+ AnimeFlag::Aid
+ | AnimeFlag::DateRecordUpdated
+ | AnimeFlag::EnglishName
+ | AnimeFlag::KanjiName
+ | AnimeFlag::RomajiName
+ | AnimeFlag::Year
+ | AnimeFlag::Type
+ | AnimeFlag::Rating
+ | AnimeFlag::VoteCount
+ | AnimeFlag::TempRating
+ | AnimeFlag::TempVoteCount;
+
+ static const FileFlags fileFlags =
+ FileFlag::Aid | FileFlag::Eid | FileFlag::Gid | FileFlag::Lid
+ | FileFlag::Ed2k
+ | FileFlag::Size
+ | FileFlag::LengthInSeconds
+ | FileFlag::FileType
+ | FileFlag::Crc32
+ | FileFlag::State
+ | FileFlag::Quality
+ | FileFlag::VideoResolution
+ | FileFlag::VideoCodec
+ | FileFlag::AudioCodec
+ | FileFlag::SubLanguage
+ | FileFlag::DubLanguage
+ | FileFlag::MyListViewDate;
+
+ static const FileAnimeFlags fileAnimeFlags =
+ FileAnimeFlag::GroupName
+ | FileAnimeFlag::GroupShortName;
+
+ db->transaction();
+ QList<PendingRequest> requests = db->getRequestBatch();
+
+ qDebug() << "Got" << requests.count() << "requests";
+
+ if (!requests.count())
+ {
+ db->commit();
+ return;
+ }
+
+ foreach (const PendingRequest &request, requests)
+ {
+ if (request.aid)
+ {
+ qDebug() << " `-> aid" << request.aid;
+ AnimeReply *reply = Client::instance()->send(AnimeCommand(request.aid, animeFlags));
+ connect(reply, SIGNAL(replyReady(bool)), this, SLOT(animeRequestComplete(bool)));
+ }
+ else if (request.eid)
+ {
+ qDebug() << " `-> eid" << request.eid;
+ EpisodeReply *reply = Client::instance()->send(EpisodeCommand(request.eid));
+ connect(reply, SIGNAL(replyReady(bool)), this, SLOT(episodeRequestComplete(bool)));
+
+ // The vote command doesn't allow voting for an eid, but requires the aid and episode number
+ // This has to be done after recieving a reply.
+ }
+ else if (request.fid)
+ {
+ qDebug() << " `-> fid" << request.fid;
+ FileReply *reply = Client::instance()->send(FileCommand(request.fid, fileFlags, fileAnimeFlags));
+ connect(reply, SIGNAL(replyReady(bool)), this, SLOT(fileRequestComplete(bool)));
+ }
+ else if (!request.ed2k.isEmpty() && request.size)
+ {
+ qDebug() << " `-> ed2k&size" << request.ed2k << request.size;
+ FileReply *reply = Client::instance()->send(FileCommand(request.ed2k, request.size, fileFlags, fileAnimeFlags));
+ connect(reply, SIGNAL(replyReady(bool)), this, SLOT(fileRequestComplete(bool)));
+ }
+ else
+ {
+ Q_ASSERT_X(false, "requestHandler", "Unknown request");
+ }
+ }
+ db->commit();
+
+ emit batchFinished();
+}
+
+void RequestHandler::animeRequestComplete(bool success)
+{
+ using namespace ::AniDBUdpClient;
+
+ qDebug() << "animeRequestComplete";
+
+ AnimeReply *reply = qobject_cast<AnimeReply *>(sender());
+
+ Q_ASSERT(reply);
+ reply->deleteLater();
+
+ if (!success)
+ return;
+
+ // If entry exists we get to update fields we know.
+ // Entry might exist just with my values and aid from vote command
+ Anime next = db->getAnime(reply->command().aid());
+
+ bool isNew = !next.aid;
+
+ next.aid = reply->aid();
+ next.anidbUpdate = reply->dateRecordUpdated();
+ next.entryUpdate = QDateTime::currentDateTime();
+ // date goes here
+ next.titleEnglish = reply->englishName();
+ next.titleRomaji = reply->romajiName();
+ next.titleKanji = reply->kanjiName();
+ // next.description This is obtained with a different command
+ next.year = reply->year(); // Format like in file command?
+ qDebug() << reply->year();
+ // next.startDate
+ // next.endDate
+ next.type = reply->type();
+ next.rating = reply->rating();
+ next.votes = reply->voteCount();
+ next.tempRating = reply->tempRating();
+ next.tempVotes = reply->tempVoteCount();
+
+ if (isNew)
+ db->addAnime(next);
+ else
+ db->setAnime(next);
+
+ // my values obtained with VoteCommand
+
+ VoteReply *voteReply = Client::instance()->send(VoteCommand(VoteCommand::AnimeVote, next.aid));
+ connect(voteReply, SIGNAL(replyReady(bool)), this, SLOT(voteRequestComplete(bool)));
+ idMap.insert(voteReply, next.aid);
+}
+
+void RequestHandler::episodeRequestComplete(bool success)
+{
+ using namespace ::AniDBUdpClient;
+
+ qDebug() << "episodeRequestComplete";
+
+ EpisodeReply *reply = qobject_cast<EpisodeReply *>(sender());
+
+ Q_ASSERT(reply);
+ reply->deleteLater();
+
+ if (!success)
+ return;
+
+ Episode next = db->getEpisode(reply->command().eid());
+
+ bool isNew = !next.eid;
+
+ next.eid = reply->eid();
+ next.aid = reply->aid();
+// next.anidbUpdate = reply->
+ next.entryUpdate = QDateTime::currentDateTime();
+// next.myUpdate = reply->
+ next.epno = reply->epnoAsInt();
+ next.titleEnglish = reply->titleEnglish();
+ next.titleRomaji = reply->titleRomaji();
+ next.titleKanji = reply->titleKanji();
+ next.length = reply->length();
+ next.airdate = reply->airDate();
+ // next.state - State is a bitfield and will be split into parts
+ next.special = reply->type() == 'S';
+ // I see no way of getting these via UDP api.
+ // next.recap
+ // next.opening
+ // next.ending
+ next.rating = reply->rating();
+ next.votes = reply->votes();
+
+ if (isNew)
+ db->addEpisode(next);
+ else
+ db->setEpisode(next);
+
+ // Obtain my values
+ VoteReply *voteReply = Client::instance()->send(VoteCommand(VoteCommand::AnimeVote, next.aid, VoteCommand::Retrieve, reply->epnoAsInt()));
+ connect(voteReply, SIGNAL(replyReady(bool)), this, SLOT(voteRequestComplete(bool)));
+ idMap.insert(voteReply, next.eid);
+}
+
+void RequestHandler::fileRequestComplete(bool success)
+{
+ using namespace ::AniDBUdpClient;
+
+ qDebug() << "fileRequestComplete";
+
+ FileReply *reply = qobject_cast<FileReply *>(sender());
+
+ Q_ASSERT(reply);
+ reply->deleteLater();
+
+ if (!success)
+ return;
+
+ // Fid might not be known in command
+ File next = db->getFile(reply->fid());
+
+ bool isNew = !next.fid;
+
+ next.fid = reply->fid();
+ next.aid = reply->aid();
+ next.eid = reply->eid();
+ next.gid = reply->gid();
+ // next.anidbUpdate
+ next.entryUpdate = QDateTime::currentDateTime();
+ // next.myUpdate
+ next.ed2k = reply->ed2k();
+ next.size = reply->size();
+ next.length = reply->lengthInSeconds();
+ next.extension = reply->fileType();
+ next.groupName = reply->groupName();
+ next.groupNameShort = reply->groupShortName();
+ next.crc = reply->crc32(); qDebug() << "crc" << reply->crc32();
+ //next.releaseDate
+ next.version = reply->version();
+ next.censored = reply->isCensored();
+ next.type = reply->type();
+ // next.qualityId - can map quality to qualityId
+ next.quality = reply->quality();
+ next.resolution = reply->videoResolution();
+ next.videoCodec = reply->videoCodec();
+ next.audioCodec = reply->audioCodec();
+ next.subtitleLanguage = reply->subLanguage();
+ next.audioLanguage = reply->audioCodec();
+ next.subtitleLanguage = reply->subLanguage();
+ // next.aspectRatio
+ next.myWatched = reply->myListViewDate();
+ next.myState = reply->myListState();
+ next.myFileState = reply->myListFileState();
+ next.myStorage = reply->myListStorage();
+ next.mySource = reply->myListSource();
+ next.myOther = reply->myListOther();
+
+ if (isNew)
+ db->addFile(next);
+ else
+ db->setFile(next);
+
+ {
+ Episode ep = db->getEpisode(next.eid);
+ if (!ep.eid)
+ {
+ PendingRequest request;
+ request.eid = next.eid;
+
+ db->addRequest(request);
+ }
+ }
+
+ {
+ Anime anime = db->getAnime(next.aid);
+ if (!anime.aid)
+ {
+ PendingRequest request;
+ request.aid = next.aid;
+
+ db->addRequest(request);
+ }
+ }
+
+ // File is not in mylist
+ if (!reply->lid())
+ {
+ MyListAddCommand cmd(next.fid);
+ cmd.setEd2k(reply->ed2k());
+ cmd.setSize(reply->size());
+ MyListAddReply *addReply = Client::instance()->send(cmd);
+ connect(addReply, SIGNAL(replyReady(bool)), this, SLOT(myListAddReplyRecieved(bool)));
+ return;
+ }
+
+ qDebug() << "Clearing fid/ed2k&size" << next.fid << "/" << reply->command().ed2k() << "&" << reply->command().size();
+ PendingRequest request;
+ request.fid = next.fid;
+ qDebug() << "Clearing fid" << request.fid;
+ db->clearRequest(request);
+
+ PendingRequest ed2kRequest;
+ ed2kRequest.ed2k = reply->ed2k();
+ ed2kRequest.size = reply->size();
+ db->clearRequest(ed2kRequest);
+}
+
+void RequestHandler::voteRequestComplete(bool)
+{
+ using namespace ::AniDBUdpClient;
+
+ qDebug() << "voteRequestComplete";
+
+ VoteReply *reply = qobject_cast<VoteReply *>(sender());
+
+ Q_ASSERT(reply);
+ Q_ASSERT(idMap.contains(reply));
+
+ int id = idMap.take(reply);
+ reply->deleteLater();
+
+
+
+ // Anime vote
+ if (reply->command().voteType() == VoteCommand::AnimeVote && reply->command().epno() == 0)
+ {
+ qDebug() << " `-> anime vote";
+ Anime anime = db->getAnime(id);
+
+ if (!anime.aid)
+ return;
+
+ anime.myUpdate = QDateTime::currentDateTime();
+ anime.myVote = reply->vote();
+ // vote dates not available via udp api?
+
+ db->setAnime(anime);
+
+ VoteReply *tmpVoteReply = Client::instance()->send(VoteCommand(VoteCommand::AnimeTempVote, id));
+ idMap.insert(tmpVoteReply, id);
+ connect(tmpVoteReply, SIGNAL(replyReady(bool)), this, SLOT(voteRequestComplete(bool)));
+ }
+ // Episode vote
+ if (reply->command().voteType() == VoteCommand::AnimeVote && reply->command().epno() != 0)
+ {
+ qDebug() << " `-> episode vote";
+ Episode ep = db->getEpisode(id);
+
+ if (!ep.eid)
+ return;
+
+ ep.myUpdate = QDateTime::currentDateTime();
+ ep.myVote = reply->vote();
+
+ db->setEpisode(ep);
+
+ qDebug() << "Clearing eid" << id;
+ PendingRequest request;
+ request.eid = id;
+ db->clearRequest(request);
+ }
+ if (reply->command().voteType() == VoteCommand::AnimeTempVote)
+ {
+ qDebug() << " `-> anime temp vote";
+ Anime anime = db->getAnime(id);
+
+ if (!anime.aid)
+ return;
+
+ anime.myUpdate = QDateTime::currentDateTime();
+ anime.myTempVote = reply->vote();
+
+ db->setAnime(anime);
+
+ qDebug() << "Clearing aid" << id;
+ PendingRequest request;
+ request.aid = id;
+ db->clearRequest(request);
+ }
+}
+
+void RequestHandler::myListAddReplyRecieved(bool success)
+{
+ using namespace ::AniDBUdpClient;
+
+ qDebug() << "myListAddReplyRecieved";
+
+ MyListAddReply *reply = qobject_cast<MyListAddReply *>(sender());
+
+ Q_ASSERT(reply);
+ reply->deleteLater();
+
+ if (!success)
+ return;
+
+ qDebug() << "Clearing fid/ed2k&size" << reply->command().fid() << "/" << reply->command().ed2k() << "&" << reply->command().size();
+ PendingRequest request;
+ request.fid = reply->command().fid();
+ db->clearRequest(request);
+
+ PendingRequest ed2kRequest;
+ ed2kRequest.ed2k = reply->command().ed2k();
+ ed2kRequest.size = reply->command().size();
+ db->clearRequest(ed2kRequest);
+
+
+}
+
+} // namespace LocalMyList
--- /dev/null
+#ifndef REQUESTHANDLER_H
+#define REQUESTHANDLER_H
+
+#include <QObject>
+#include <QMap>
+
+namespace AniDBUdpClient
+{
+ class AnimeReply;
+ class EpisodeReply;
+ class FileReply;
+ class VoteReply;
+ class MyListAddReply;
+}
+
+namespace LocalMyList {
+
+class Database;
+
+class RequestHandler : public QObject
+{
+ Q_OBJECT
+public:
+ explicit RequestHandler(Database *db, QObject *parent = 0);
+
+signals:
+ void batchFinished();
+
+public slots:
+ void handleRequests();
+
+ void animeRequestComplete(bool success);
+ void episodeRequestComplete(bool success);
+ void fileRequestComplete(bool success);
+ void voteRequestComplete(bool);
+ void myListAddReplyRecieved(bool success);
+
+private:
+ Database *db;
+
+ QMap<::AniDBUdpClient::VoteReply *, int> idMap;
+};
+
+} // namespace LocalMyList
+
+#endif // REQUESTHANDLER_H
--- /dev/null
+#include "settings.h"
+#include "database.h"
+
+namespace LocalMyList {
+
+Settings::Settings(Database *db, QObject *parent) :
+ QObject(parent)
+{
+ this->db = db;
+}
+
+Database *Settings::database() const
+{
+ return db;
+}
+
+void Settings::setDatabase(Database *db)
+{
+ this->db = db;
+}
+
+QVariant Settings::get(const QString &key) const
+{
+ if (!settings.contains(key)) readSettings();
+ return settings.value(key);
+}
+
+void Settings::set(const QString &key, const QVariant &value)
+{
+ settings[key] = value;
+ db->setConfig(key, value);
+}
+
+void Settings::settingsChangedInDatabase()
+{
+ settings.clear();
+}
+
+void Settings::readSettings() const
+{
+ settings = db->getConfig();
+}
+
+} // namespace LocalMyList
--- /dev/null
+#ifndef SETTINGS_H
+#define SETTINGS_H
+
+#include <QObject>
+#include <QVariant>
+
+namespace LocalMyList {
+
+class Database;
+
+class Settings : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Settings(Database *db = 0, QObject *parent = 0);
+ Database *database() const;
+ void setDatabase(Database *db);
+
+ template<typename T> void get(const QString &key, T& value) const { value = get(key).value<T>();}
+
+signals:
+ void settingsChanged();
+
+public slots:
+ QVariant get(const QString &key) const;
+ void set(const QString &key, const QVariant &value);
+
+ void settingsChangedInDatabase();
+
+private:
+ void readSettings() const;
+
+ Database *db;
+ mutable QVariantMap settings;
+};
+
+} // namespace LocalMyList
+
+#endif // SETTINGS_H
--- /dev/null
+#include "workthread.h"
+#include "database.h"
+
+namespace LocalMyList {
+
+WorkThread::WorkThread(const QString &threadName, const DatabaseConnectionSettings &dbs, QObject *parent) :
+ QThread(parent), m_threadName(threadName), db(0)
+{
+ db = new Database(m_threadName);
+ db->setConnectionSettings(dbs);
+ db->moveToThread(this);
+}
+
+WorkThread::~WorkThread()
+{
+ if (db)
+ delete db;
+}
+
+QString WorkThread::threadName() const
+{
+ return m_threadName;
+}
+
+Database *WorkThread::database() const
+{
+ return db;
+}
+
+void WorkThread::run()
+{
+ db->connect();
+ exec();
+}
+
+} // namespace LocalMyList
--- /dev/null
+#ifndef WORKTHREAD_H
+#define WORKTHREAD_H
+
+#include <QThread>
+#include "abstracttask.h"
+#include "database.h"
+
+namespace LocalMyList {
+
+class WorkThread : public QThread
+{
+ Q_OBJECT
+public:
+ explicit WorkThread(const QString &threadName, const DatabaseConnectionSettings &dbs, QObject *parent = 0);
+ ~WorkThread();
+
+ QString threadName() const;
+ Database *database() const;
+
+protected:
+ void run();
+
+private:
+ QString m_threadName;
+ Database *db;
+};
+
+} // namespace LocalMyList
+
+#endif // WORKTHREAD_H
--- /dev/null
+#include "databaseconnectiondialog.h"
+#include "ui_databaseconnectiondialog.h"
+
+DatabaseConnectionDialog::DatabaseConnectionDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::DatabaseConnectionDialog)
+{
+ ui->setupUi(this);
+}
+
+DatabaseConnectionDialog::~DatabaseConnectionDialog()
+{
+ delete ui;
+}
--- /dev/null
+#ifndef DATABASECONNECTIONDIALOG_H
+#define DATABASECONNECTIONDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+class DatabaseConnectionDialog;
+}
+
+class DatabaseConnectionDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit DatabaseConnectionDialog(QWidget *parent = 0);
+ ~DatabaseConnectionDialog();
+
+private:
+ Ui::DatabaseConnectionDialog *ui;
+};
+
+#endif // DATABASECONNECTIONDIALOG_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DatabaseConnectionDialog</class>
+ <widget class="QDialog" name="DatabaseConnectionDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Connection Details</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Host:</string>
+ </property>
+ <property name="buddy">
+ <cstring>host</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="host"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Port:</string>
+ </property>
+ <property name="buddy">
+ <cstring>port</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="port"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>User:</string>
+ </property>
+ <property name="buddy">
+ <cstring>user</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="user"/>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Pass:</string>
+ </property>
+ <property name="buddy">
+ <cstring>pass</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="pass"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Client Details</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Name:</string>
+ </property>
+ <property name="buddy">
+ <cstring>clientName</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="clientName"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>host</tabstop>
+ <tabstop>port</tabstop>
+ <tabstop>user</tabstop>
+ <tabstop>pass</tabstop>
+ <tabstop>clientName</tabstop>
+ <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DatabaseConnectionDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>227</x>
+ <y>282</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DatabaseConnectionDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>295</x>
+ <y>288</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
--- /dev/null
+#include <QApplication>
+#include "mainwindow.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ MainWindow w;
+ w.show();
+
+ return a.exec();
+}
--- /dev/null
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+
+#include <QLabel>
+#include <QSqlTableModel>
+#include <QFileDialog>
+
+#include "mylist.h"
+#include "database.h"
+
+#include <QDebug>
+
+using namespace LocalMyList;
+
+MainWindow::MainWindow(QWidget *parent) :
+ QMainWindow(parent),
+ ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+
+
+ connect(MyList::instance()->database(), SIGNAL(connected()), this, SLOT(dbConnected()));
+ connect(MyList::instance()->database(), SIGNAL(disconnected()), this, SLOT(dbDisconnected()));
+ connect(MyList::instance()->database(), SIGNAL(newPendingRequest()), this, SLOT(handleNotification()));
+
+ databaseConnectionStatusIndicator = new QLabel(MyList::instance()->database()->isConnected() ? "Connected" : "Disconnected");
+ ui->statusBar->addPermanentWidget(databaseConnectionStatusIndicator);
+
+ animeModel = 0;
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
+
+void MainWindow::on_actionConnect_triggered()
+{
+ MyList::instance()->database()->connect();
+}
+
+void MainWindow::on_actionDisconnect_triggered()
+{
+ MyList::instance()->database()->disconnect();
+}
+
+
+void MainWindow::dbConnected()
+{
+ databaseConnectionStatusIndicator->setText("Connected");
+// animeModel = new QSqlTableModel(this, LocalMyList::instance()->database()->connection());
+// animeModel->setTable("anime");
+// animeModel->select();
+// ui->tableView->setModel(animeModel);
+}
+
+void MainWindow::dbDisconnected()
+{
+ databaseConnectionStatusIndicator->setText("Disconnected");
+// ui->tableView->setModel(0);
+// delete animeModel;
+ // animeModel = 0;
+}
+
+void MainWindow::handleNotification()
+{
+ ui->statusBar->showMessage("New Pending Request added!");
+}
+
+void MainWindow::on_actionScanDirectory_triggered()
+{
+ QString dir = QFileDialog::getExistingDirectory(this, tr("Scan Directory"));
+ if (dir.isEmpty())
+ return;
+ MyList::instance()->addDirectory(QDir(dir));
+}
+
+void MainWindow::on_actionImportMyList_triggered()
+{
+ QString file = QFileDialog::getOpenFileName(this, tr("Import MyList"));
+ if (file.isEmpty())
+ return;
+ MyList::instance()->importMyList(file);
+}
+
+void MainWindow::on_actionImportTitles_triggered()
+{
+ QString file = QFileDialog::getOpenFileName(this, tr("Import Titles"));
+ if (file.isEmpty())
+ return;
+ MyList::instance()->importTitles(file);
+}
+
+void MainWindow::on_actionClearDatabase_triggered()
+{
+ MyList::instance()->database()->truncateDatabase();
+}
+
+void MainWindow::on_actionClearMyListData_triggered()
+{
+ MyList::instance()->database()->truncateMyListData();
+}
+
+void MainWindow::on_actionClearAnimeTitleData_triggered()
+{
+ MyList::instance()->database()->truncateTitleData();
+}
+
+void MainWindow::on_actionHandlePendingRequests_triggered()
+{
+
+}
--- /dev/null
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+namespace Ui {
+class MainWindow;
+}
+
+class QSqlTableModel;
+class QLabel;
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+private slots:
+ void on_actionConnect_triggered();
+
+ void dbConnected();
+ void dbDisconnected();
+
+ void handleNotification();
+
+ void on_actionDisconnect_triggered();
+ void on_actionScanDirectory_triggered();
+ void on_actionImportTitles_triggered();
+ void on_actionClearDatabase_triggered();
+ void on_actionClearMyListData_triggered();
+ void on_actionClearAnimeTitleData_triggered();
+
+ void on_actionImportMyList_triggered();
+
+ void on_actionHandlePendingRequests_triggered();
+
+private:
+ Ui::MainWindow *ui;
+
+ QLabel *databaseConnectionStatusIndicator;
+
+ QSqlTableModel *animeModel;
+};
+
+#endif // MAINWINDOW_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralWidget">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QTableView" name="tableView"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menuBar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menuConnection">
+ <property name="title">
+ <string>Connection</string>
+ </property>
+ <addaction name="actionConnect"/>
+ <addaction name="actionDisconnect"/>
+ <addaction name="separator"/>
+ <addaction name="actionQuit"/>
+ </widget>
+ <widget class="QMenu" name="menuSettings">
+ <property name="title">
+ <string>Settings</string>
+ </property>
+ <addaction name="actionConnection"/>
+ </widget>
+ <widget class="QMenu" name="menu_Actions">
+ <property name="title">
+ <string>&Actions</string>
+ </property>
+ <addaction name="actionScanDirectory"/>
+ <addaction name="actionImportMyList"/>
+ <addaction name="actionImportTitles"/>
+ <addaction name="separator"/>
+ <addaction name="actionResetPendingRequests"/>
+ <addaction name="separator"/>
+ <addaction name="actionClearDatabase"/>
+ <addaction name="actionClearMyListData"/>
+ <addaction name="actionClearAnimeTitleData"/>
+ <addaction name="separator"/>
+ </widget>
+ <addaction name="menuConnection"/>
+ <addaction name="menu_Actions"/>
+ <addaction name="menuSettings"/>
+ </widget>
+ <widget class="QToolBar" name="mainToolBar">
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <widget class="QStatusBar" name="statusBar"/>
+ <action name="actionConnect">
+ <property name="text">
+ <string>Connect</string>
+ </property>
+ </action>
+ <action name="actionDisconnect">
+ <property name="text">
+ <string>Disconnect</string>
+ </property>
+ </action>
+ <action name="actionQuit">
+ <property name="text">
+ <string>Quit</string>
+ </property>
+ </action>
+ <action name="actionConnection">
+ <property name="text">
+ <string>Connection...</string>
+ </property>
+ </action>
+ <action name="actionScanDirectory">
+ <property name="text">
+ <string>Scan Directory...</string>
+ </property>
+ </action>
+ <action name="actionClearDatabase">
+ <property name="text">
+ <string>Clear Database</string>
+ </property>
+ </action>
+ <action name="actionClearMyListData">
+ <property name="text">
+ <string>Clear MyList Data</string>
+ </property>
+ </action>
+ <action name="actionClearAnimeTitleData">
+ <property name="text">
+ <string>Clear Anime Title Data</string>
+ </property>
+ </action>
+ <action name="actionImportTitles">
+ <property name="text">
+ <string>Import Titles...</string>
+ </property>
+ </action>
+ <action name="actionImportMyList">
+ <property name="text">
+ <string>Import MyList...</string>
+ </property>
+ </action>
+ <action name="actionResetPendingRequests">
+ <property name="text">
+ <string>Reset Pending Requests</string>
+ </property>
+ </action>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null
+#-------------------------------------------------
+#
+# Project created by QtCreator 2012-02-16T17:36:59
+#
+#-------------------------------------------------
+
+QT += core gui
+
+TARGET = management-gui
+DESTDIR = ../build
+TEMPLATE = app
+
+
+SOURCES += main.cpp\
+ mainwindow.cpp \
+ databaseconnectiondialog.cpp
+
+HEADERS += mainwindow.h \
+ databaseconnectiondialog.h
+
+FORMS += mainwindow.ui \
+ databaseconnectiondialog.ui
+
+include(../localmylist.pri)
--- /dev/null
+#include <QtCore/QCoreApplication>
+
+#include <QFile>
+#include <QStringList>
+#include <QTextStream>
+#include <QProcess>
+
+#include "mylist.h"
+#include "database.h"
+#include <QSqlError>
+#include <QDebug>
+
+using namespace LocalMyList;
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication a(argc, argv);
+ QTextStream cout(stdout);
+ if (a.arguments().count() < 2)
+ {
+ cout << "Usage: " << a.arguments()[0] << " TITLE";
+ return 1;
+ }
+
+ QStringList args = a.arguments();
+ args.removeFirst();
+
+ QString title = args.join(QChar(' '));
+
+ QString atitle, etitle;
+ QString path;
+ int epno;
+ {
+ QSqlQuery q(MyList::instance()->database()->connection());
+ q.prepare(
+ "SELECT fl.path, e.epno, a.title_romaji, e.title FROM file f "
+ " LEFT JOIN anime a ON f.aid = a.aid "
+ " LEFT JOIN anime_title at ON f.aid = at.aid "
+ " LEFT JOIN episode e ON f.eid = e.eid "
+ " LEFT JOIN file_location fl ON fl.fid = f.fid "
+ " WHERE f.my_watched IS NULL "
+ " AND lower(at.title) = :title "
+ " AND fl.path IS NOT NULL "
+ "UNION "
+ "SELECT fl.path, e.epno, a.title_romaji, e.title FROM file f "
+ " LEFT JOIN anime a ON f.aid = a.aid "
+ " LEFT JOIN anime_title at ON f.aid = at.aid "
+ " LEFT JOIN episode e ON f.eid = e.eid "
+ " LEFT JOIN file_location fl ON fl.fid = f.fid "
+ " WHERE f.my_watched IS NULL "
+ " AND at.title ILIKE :title "
+ " AND fl.path IS NOT NULL "
+ "GROUP BY fl.path, a.title_romaji, e.epno, e.title "
+ "ORDER BY title_romaji ASC, epno ASC ");
+ q.bindValue(":title", title);
+
+ if (!q.exec())
+ {
+ qDebug() << q.lastError();
+ return 1;
+ }
+
+ if (!q.next())
+ {
+ cout << "No file to watch.";
+ return 1;
+ }
+ path = q.value(0).toString();
+ epno = q.value(1).toInt();
+ atitle = q.value(2).toString();
+ etitle = q.value(3).toString();
+ }
+
+ cout << "ANIME " << atitle << endl;
+ cout << "EPISODE " << epno << " - " << etitle << endl;
+ cout << "Starting player..." << endl;
+ QProcess::startDetached("D:/_C/aniplayer/build/aniplayer", QStringList() << path);
+ return 0;
+
+
+}
--- /dev/null
+#-------------------------------------------------
+#
+# Project created by QtCreator 2012-02-26T21:42:52
+#
+#-------------------------------------------------
+
+QT += core
+
+QT -= gui
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+TARGET = play-next
+DESTDIR = ../build
+CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+
+SOURCES += main.cpp
+
+include(../localmylist.pri)
--- /dev/null
+#include <QApplication>
+#include "mainwindow.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ MainWindow w;
+ w.show();
+
+ return a.exec();
+}
--- /dev/null
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+
+#include <QSqlQuery>
+#include <QSqlError>
+#include "database.h"
+#include "mylist.h"
+
+#include <QDebug>
+
+using namespace LocalMyList;
+
+MainWindow::MainWindow(QWidget *parent) :
+ QMainWindow(parent),
+ ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+ MyList::instance();
+
+ model = new QSqlQueryModel(this);
+
+ ui->resultView->setModel(model);
+ on_search_textChanged("");
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
+
+void MainWindow::on_search_textChanged(const QString &text)
+{
+
+ QSqlQuery q(
+ "SELECT a.aid, b.title AS MainTitle, a.title, a.language, a.type FROM anime_title a"
+ " LEFT JOIN anime_title b on b.aid = a.aid"
+ " WHERE lower(a.title) LIKE '%" + text + "%'"
+ " AND b.type = 1"
+ " ORDER BY a.title ASC, a.aid ASC", MyList::instance()->database()->connection());
+qDebug() << q.lastError();
+ model->setQuery(q);
+
+}
--- /dev/null
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <QSqlQueryModel>
+
+namespace Ui {
+ class MainWindow;
+}
+
+namespace LocalMyList {
+ class Database;
+}
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+private slots:
+ void on_search_textChanged(const QString &arg1);
+
+private:
+ Ui::MainWindow *ui;
+ QSqlQueryModel *model;
+ LocalMyList::Database *db;
+};
+
+#endif // MAINWINDOW_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralWidget">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLineEdit" name="search"/>
+ </item>
+ <item>
+ <widget class="QTableView" name="resultView"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menuBar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>21</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QToolBar" name="mainToolBar">
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <widget class="QStatusBar" name="statusBar"/>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null
+#-------------------------------------------------
+#
+# Project created by QtCreator 2012-02-13T02:45:41
+#
+#-------------------------------------------------
+
+QT += core gui
+
+TARGET = search-gui
+DESTDIR = ../build
+TEMPLATE = app
+
+
+SOURCES += main.cpp\
+ mainwindow.cpp
+
+HEADERS += mainwindow.h
+
+FORMS += mainwindow.ui
+
+include(../localmylist.pri)