]> Some of my projects - localmylist.git/commitdiff
LocalMyList
authorAPTX <marek321@gmail.com>
Sat, 26 May 2012 01:51:36 +0000 (03:51 +0200)
committerAPTX <marek321@gmail.com>
Sat, 26 May 2012 01:51:36 +0000 (03:51 +0200)
46 files changed:
.gitignore [new file with mode: 0644]
import-mylist/import-mylist.pro [new file with mode: 0644]
import-mylist/main.cpp [new file with mode: 0644]
import-titles/import-titles.pro [new file with mode: 0644]
import-titles/main.cpp [new file with mode: 0644]
localmylist.pri [new file with mode: 0644]
localmylist.pro [new file with mode: 0644]
localmylist/abstracttask.cpp [new file with mode: 0644]
localmylist/abstracttask.h [new file with mode: 0644]
localmylist/addfiletask.cpp [new file with mode: 0644]
localmylist/addfiletask.h [new file with mode: 0644]
localmylist/animetitleparsetask.cpp [new file with mode: 0644]
localmylist/animetitleparsetask.h [new file with mode: 0644]
localmylist/database.cpp [new file with mode: 0644]
localmylist/database.h [new file with mode: 0644]
localmylist/directoryscantask.cpp [new file with mode: 0644]
localmylist/directoryscantask.h [new file with mode: 0644]
localmylist/include/LocalMyList/Database [new file with mode: 0644]
localmylist/include/LocalMyList/LocalMyList [new file with mode: 0644]
localmylist/localmylist.pro [new file with mode: 0644]
localmylist/localmylist_global.h [new file with mode: 0644]
localmylist/mylist.cpp [new file with mode: 0644]
localmylist/mylist.h [new file with mode: 0644]
localmylist/mylistexportparsetask.cpp [new file with mode: 0644]
localmylist/mylistexportparsetask.h [new file with mode: 0644]
localmylist/requesthandler.cpp [new file with mode: 0644]
localmylist/requesthandler.h [new file with mode: 0644]
localmylist/settings.cpp [new file with mode: 0644]
localmylist/settings.h [new file with mode: 0644]
localmylist/workthread.cpp [new file with mode: 0644]
localmylist/workthread.h [new file with mode: 0644]
management-gui/databaseconnectiondialog.cpp [new file with mode: 0644]
management-gui/databaseconnectiondialog.h [new file with mode: 0644]
management-gui/databaseconnectiondialog.ui [new file with mode: 0644]
management-gui/main.cpp [new file with mode: 0644]
management-gui/mainwindow.cpp [new file with mode: 0644]
management-gui/mainwindow.h [new file with mode: 0644]
management-gui/mainwindow.ui [new file with mode: 0644]
management-gui/management-gui.pro [new file with mode: 0644]
play-next/main.cpp [new file with mode: 0644]
play-next/play-next.pro [new file with mode: 0644]
search-gui/main.cpp [new file with mode: 0644]
search-gui/mainwindow.cpp [new file with mode: 0644]
search-gui/mainwindow.h [new file with mode: 0644]
search-gui/mainwindow.ui [new file with mode: 0644]
search-gui/search-gui.pro [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..3dfe734
--- /dev/null
@@ -0,0 +1,76 @@
+# 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*
+
+
diff --git a/import-mylist/import-mylist.pro b/import-mylist/import-mylist.pro
new file mode 100644 (file)
index 0000000..8b68571
--- /dev/null
@@ -0,0 +1,13 @@
+QT += core
+QT -= gui
+
+TARGET = import-mylist
+DESTDIR = ../build
+CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+SOURCES += main.cpp
+
+include(../localmylist.pri)
diff --git a/import-mylist/main.cpp b/import-mylist/main.cpp
new file mode 100644 (file)
index 0000000..5485df9
--- /dev/null
@@ -0,0 +1,34 @@
+#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;
+}
diff --git a/import-titles/import-titles.pro b/import-titles/import-titles.pro
new file mode 100644 (file)
index 0000000..2ab825d
--- /dev/null
@@ -0,0 +1,13 @@
+QT += core
+QT -= gui
+
+TARGET = import-titles
+DESTDIR = ../build
+CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+SOURCES += main.cpp
+
+include(../localmylist.pri)
diff --git a/import-titles/main.cpp b/import-titles/main.cpp
new file mode 100644 (file)
index 0000000..2316911
--- /dev/null
@@ -0,0 +1,24 @@
+#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();
+}
diff --git a/localmylist.pri b/localmylist.pri
new file mode 100644 (file)
index 0000000..86b931b
--- /dev/null
@@ -0,0 +1,7 @@
+QT *= sql
+
+INCLUDEPATH += $$PWD/localmylist/include
+INCLUDEPATH += $$PWD/localmylist
+DEPENDPATH += $$PWD/localmylist
+LIBS += -llocalmylist
+LIBS += -L$$PWD/build
diff --git a/localmylist.pro b/localmylist.pro
new file mode 100644 (file)
index 0000000..208cee3
--- /dev/null
@@ -0,0 +1,9 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+    localmylist \
+    search-gui \
+    import-titles \
+    import-mylist \
+    management-gui \
+    play-next
diff --git a/localmylist/abstracttask.cpp b/localmylist/abstracttask.cpp
new file mode 100644 (file)
index 0000000..aa351bd
--- /dev/null
@@ -0,0 +1,51 @@
+#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
diff --git a/localmylist/abstracttask.h b/localmylist/abstracttask.h
new file mode 100644 (file)
index 0000000..4945917
--- /dev/null
@@ -0,0 +1,46 @@
+#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
diff --git a/localmylist/addfiletask.cpp b/localmylist/addfiletask.cpp
new file mode 100644 (file)
index 0000000..6880bd0
--- /dev/null
@@ -0,0 +1,137 @@
+#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
diff --git a/localmylist/addfiletask.h b/localmylist/addfiletask.h
new file mode 100644 (file)
index 0000000..5cc86f1
--- /dev/null
@@ -0,0 +1,55 @@
+#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
diff --git a/localmylist/animetitleparsetask.cpp b/localmylist/animetitleparsetask.cpp
new file mode 100644 (file)
index 0000000..3c86d8e
--- /dev/null
@@ -0,0 +1,91 @@
+#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
diff --git a/localmylist/animetitleparsetask.h b/localmylist/animetitleparsetask.h
new file mode 100644 (file)
index 0000000..7dbe08b
--- /dev/null
@@ -0,0 +1,33 @@
+#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
diff --git a/localmylist/database.cpp b/localmylist/database.cpp
new file mode 100644 (file)
index 0000000..a21e1e6
--- /dev/null
@@ -0,0 +1,915 @@
+#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 &notification)
+{
+       qDebug() << "Recieved notification" << notification;
+       if (notification == "new_pending_request")
+       {
+               emit newPendingRequest();
+       }
+}
+
+} // namespace LocalMyList
diff --git a/localmylist/database.h b/localmylist/database.h
new file mode 100644 (file)
index 0000000..015d81f
--- /dev/null
@@ -0,0 +1,249 @@
+#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 &notification);
+
+private:
+       bool exec(QSqlQuery &query);
+       bool exec(const QString &sql);
+       void prepareQueries();
+
+       DatabaseInternal *d;
+       DatabaseConnectionSettings m_connectionSettings;
+
+       QString connectionName;
+};
+
+} // namespace LocalMyList
+
+#endif // DATABASE_H
diff --git a/localmylist/directoryscantask.cpp b/localmylist/directoryscantask.cpp
new file mode 100644 (file)
index 0000000..c9be688
--- /dev/null
@@ -0,0 +1,92 @@
+#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
diff --git a/localmylist/directoryscantask.h b/localmylist/directoryscantask.h
new file mode 100644 (file)
index 0000000..fe9040e
--- /dev/null
@@ -0,0 +1,40 @@
+#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
diff --git a/localmylist/include/LocalMyList/Database b/localmylist/include/LocalMyList/Database
new file mode 100644 (file)
index 0000000..ff7ec1b
--- /dev/null
@@ -0,0 +1 @@
+#include ""
\ No newline at end of file
diff --git a/localmylist/include/LocalMyList/LocalMyList b/localmylist/include/LocalMyList/LocalMyList
new file mode 100644 (file)
index 0000000..ff7ec1b
--- /dev/null
@@ -0,0 +1 @@
+#include ""
\ No newline at end of file
diff --git a/localmylist/localmylist.pro b/localmylist/localmylist.pro
new file mode 100644 (file)
index 0000000..c4369fe
--- /dev/null
@@ -0,0 +1,44 @@
+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
diff --git a/localmylist/localmylist_global.h b/localmylist/localmylist_global.h
new file mode 100644 (file)
index 0000000..4f5754a
--- /dev/null
@@ -0,0 +1,15 @@
+#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
diff --git a/localmylist/mylist.cpp b/localmylist/mylist.cpp
new file mode 100644 (file)
index 0000000..27fb181
--- /dev/null
@@ -0,0 +1,215 @@
+#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
diff --git a/localmylist/mylist.h b/localmylist/mylist.h
new file mode 100644 (file)
index 0000000..bff9fc5
--- /dev/null
@@ -0,0 +1,82 @@
+#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
diff --git a/localmylist/mylistexportparsetask.cpp b/localmylist/mylistexportparsetask.cpp
new file mode 100644 (file)
index 0000000..28103b6
--- /dev/null
@@ -0,0 +1,679 @@
+#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
diff --git a/localmylist/mylistexportparsetask.h b/localmylist/mylistexportparsetask.h
new file mode 100644 (file)
index 0000000..e1d94d8
--- /dev/null
@@ -0,0 +1,58 @@
+#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
diff --git a/localmylist/requesthandler.cpp b/localmylist/requesthandler.cpp
new file mode 100644 (file)
index 0000000..9b1ba1a
--- /dev/null
@@ -0,0 +1,415 @@
+#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
diff --git a/localmylist/requesthandler.h b/localmylist/requesthandler.h
new file mode 100644 (file)
index 0000000..a846591
--- /dev/null
@@ -0,0 +1,46 @@
+#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
diff --git a/localmylist/settings.cpp b/localmylist/settings.cpp
new file mode 100644 (file)
index 0000000..74cd26c
--- /dev/null
@@ -0,0 +1,44 @@
+#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
diff --git a/localmylist/settings.h b/localmylist/settings.h
new file mode 100644 (file)
index 0000000..01bd3c0
--- /dev/null
@@ -0,0 +1,39 @@
+#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
diff --git a/localmylist/workthread.cpp b/localmylist/workthread.cpp
new file mode 100644 (file)
index 0000000..58d882c
--- /dev/null
@@ -0,0 +1,36 @@
+#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
diff --git a/localmylist/workthread.h b/localmylist/workthread.h
new file mode 100644 (file)
index 0000000..f00aeef
--- /dev/null
@@ -0,0 +1,30 @@
+#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
diff --git a/management-gui/databaseconnectiondialog.cpp b/management-gui/databaseconnectiondialog.cpp
new file mode 100644 (file)
index 0000000..efffcc8
--- /dev/null
@@ -0,0 +1,14 @@
+#include "databaseconnectiondialog.h"
+#include "ui_databaseconnectiondialog.h"
+
+DatabaseConnectionDialog::DatabaseConnectionDialog(QWidget *parent) :
+    QDialog(parent),
+    ui(new Ui::DatabaseConnectionDialog)
+{
+       ui->setupUi(this);
+}
+
+DatabaseConnectionDialog::~DatabaseConnectionDialog()
+{
+       delete ui;
+}
diff --git a/management-gui/databaseconnectiondialog.h b/management-gui/databaseconnectiondialog.h
new file mode 100644 (file)
index 0000000..b5a25f6
--- /dev/null
@@ -0,0 +1,22 @@
+#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
diff --git a/management-gui/databaseconnectiondialog.ui b/management-gui/databaseconnectiondialog.ui
new file mode 100644 (file)
index 0000000..f02d333
--- /dev/null
@@ -0,0 +1,168 @@
+<?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>
diff --git a/management-gui/main.cpp b/management-gui/main.cpp
new file mode 100644 (file)
index 0000000..24a397b
--- /dev/null
@@ -0,0 +1,11 @@
+#include <QApplication>
+#include "mainwindow.h"
+
+int main(int argc, char *argv[])
+{
+       QApplication a(argc, argv);
+       MainWindow w;
+       w.show();
+       
+       return a.exec();
+}
diff --git a/management-gui/mainwindow.cpp b/management-gui/mainwindow.cpp
new file mode 100644 (file)
index 0000000..18d928c
--- /dev/null
@@ -0,0 +1,112 @@
+#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()
+{
+
+}
diff --git a/management-gui/mainwindow.h b/management-gui/mainwindow.h
new file mode 100644 (file)
index 0000000..782694b
--- /dev/null
@@ -0,0 +1,48 @@
+#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
diff --git a/management-gui/mainwindow.ui b/management-gui/mainwindow.ui
new file mode 100644 (file)
index 0000000..782796a
--- /dev/null
@@ -0,0 +1,134 @@
+<?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>&amp;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>
diff --git a/management-gui/management-gui.pro b/management-gui/management-gui.pro
new file mode 100644 (file)
index 0000000..402d907
--- /dev/null
@@ -0,0 +1,24 @@
+#-------------------------------------------------
+#
+# 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)
diff --git a/play-next/main.cpp b/play-next/main.cpp
new file mode 100644 (file)
index 0000000..ea73e1f
--- /dev/null
@@ -0,0 +1,81 @@
+#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;
+
+
+}
diff --git a/play-next/play-next.pro b/play-next/play-next.pro
new file mode 100644 (file)
index 0000000..d2e8808
--- /dev/null
@@ -0,0 +1,23 @@
+#-------------------------------------------------
+#
+# 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)
diff --git a/search-gui/main.cpp b/search-gui/main.cpp
new file mode 100644 (file)
index 0000000..24a397b
--- /dev/null
@@ -0,0 +1,11 @@
+#include <QApplication>
+#include "mainwindow.h"
+
+int main(int argc, char *argv[])
+{
+       QApplication a(argc, argv);
+       MainWindow w;
+       w.show();
+       
+       return a.exec();
+}
diff --git a/search-gui/mainwindow.cpp b/search-gui/mainwindow.cpp
new file mode 100644 (file)
index 0000000..25d8740
--- /dev/null
@@ -0,0 +1,43 @@
+#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);
+
+}
diff --git a/search-gui/mainwindow.h b/search-gui/mainwindow.h
new file mode 100644 (file)
index 0000000..53b9ccd
--- /dev/null
@@ -0,0 +1,32 @@
+#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
diff --git a/search-gui/mainwindow.ui b/search-gui/mainwindow.ui
new file mode 100644 (file)
index 0000000..b878805
--- /dev/null
@@ -0,0 +1,49 @@
+<?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>
diff --git a/search-gui/search-gui.pro b/search-gui/search-gui.pro
new file mode 100644 (file)
index 0000000..ed0ba85
--- /dev/null
@@ -0,0 +1,21 @@
+#-------------------------------------------------
+#
+# 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)