From 9c94e877de387f0a31558a4e5532b196e38ba197 Mon Sep 17 00:00:00 2001 From: APTX Date: Sat, 3 Aug 2013 19:04:35 +0200 Subject: [PATCH] More complete dynamic model. --- .../tabs/dynamicmodeltab.cpp | 8 + localmylist/database.cpp | 14 +- localmylist/database.h | 14 +- localmylist/dynamicmodel/data.cpp | 135 ++++++++++ localmylist/dynamicmodel/data.h | 37 +++ localmylist/dynamicmodel/datamodel.cpp | 5 +- localmylist/dynamicmodel/datatype.cpp | 36 ++- localmylist/dynamicmodel/datatype.h | 20 +- .../dynamicmodel/dynamicmodel_global.h | 7 + localmylist/dynamicmodel/model.cpp | 24 +- localmylist/dynamicmodel/model.h | 9 + localmylist/dynamicmodel/node.cpp | 43 +++- localmylist/dynamicmodel/node.h | 5 + localmylist/dynamicmodel/typerelation.cpp | 243 +++++++++++++++--- localmylist/dynamicmodel/typerelation.h | 74 +++++- localmylist/dynamicmodel/types.cpp | 226 +++++++++++----- localmylist/dynamicmodel/types.h | 49 +++- 17 files changed, 806 insertions(+), 143 deletions(-) diff --git a/localmylist-management/tabs/dynamicmodeltab.cpp b/localmylist-management/tabs/dynamicmodeltab.cpp index 40a9026..b18cba9 100644 --- a/localmylist-management/tabs/dynamicmodeltab.cpp +++ b/localmylist-management/tabs/dynamicmodeltab.cpp @@ -10,6 +10,7 @@ #include "dynamicmodel/model.h" #include "dynamicmodel/datamodel.h" #include "dynamicmodel/types.h" +#include "dynamicmodel/typerelation.h" using namespace LocalMyList::DynamicModel; @@ -40,6 +41,12 @@ void DynamicModelTab::init() { dataModel = new DataModel(this); dataModel->registerDataType(new AnimeType); + dataModel->registerDataType(new EpisodeType); + dataModel->registerDataType(new FileType); + dataModel->registerTypeRelation(new RootAnimeRelation(this)); + dataModel->registerTypeRelation(new RootEpisodeRelation(this)); + dataModel->registerTypeRelation(new AnimeEpisodeRelation(this)); + dataModel->registerTypeRelation(new EpisodeFileRelation(this)); model = new Model(this); model->setDataModel(dataModel); @@ -66,6 +73,7 @@ void DynamicModelTab::init() connect(ui->myListView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(currentSelectionChanged(QModelIndex,QModelIndex))); connect(ui->filterInput, SIGNAL(textChanged(QString)), this, SLOT(currentSelectionChanged())); + model->setQuery("anime|episode|file"); } void DynamicModelTab::activate() diff --git a/localmylist/database.cpp b/localmylist/database.cpp index ee587c4..f832e5b 100644 --- a/localmylist/database.cpp +++ b/localmylist/database.cpp @@ -1652,7 +1652,7 @@ void Database::disconnect() emit disconnected(); } -void Database::readAnimeData(SqlResultIteratorInterface &result, Anime &data, int offset) +void Database::readAnimeData(const SqlResultIteratorInterface &result, Anime &data, int offset) { data.aid = result.value(offset++).toInt(); data.entryAdded = result.value(offset++).toDateTime(); @@ -1679,7 +1679,7 @@ void Database::readAnimeData(SqlResultIteratorInterface &result, Anime &data, in data.myTempVoteDate = result.value(offset++).toDateTime(); } -void Database::readEpisodeData(SqlResultIteratorInterface &result, Episode &data, int offset) +void Database::readEpisodeData(const SqlResultIteratorInterface &result, Episode &data, int offset) { data.eid = result.value(offset++).toInt(); data.aid = result.value(offset++).toInt(); @@ -1702,7 +1702,7 @@ void Database::readEpisodeData(SqlResultIteratorInterface &result, Episode &data data.myVoteDate = result.value(offset++).toDateTime(); } -void Database::readFileData(SqlResultIteratorInterface &result, File &data, int offset) +void Database::readFileData(const SqlResultIteratorInterface &result, File &data, int offset) { data.fid = result.value(offset++).toInt(); data.eid = result.value(offset++).toInt(); @@ -1740,7 +1740,7 @@ void Database::readFileData(SqlResultIteratorInterface &result, File &data, int data.myOther = result.value(offset++).toString(); } -void Database::readFileLocationData(SqlResultIteratorInterface &result, FileLocation &data, int offset) +void Database::readFileLocationData(const SqlResultIteratorInterface &result, FileLocation &data, int offset) { data.locationId = result.value(offset++).toInt(); data.fid = result.value(offset++).toInt(); @@ -1750,7 +1750,7 @@ void Database::readFileLocationData(SqlResultIteratorInterface &result, FileLoca data.failedRename = result.value(offset++).toBool(); } -void Database::readOpenFileData(SqlResultIteratorInterface &result, OpenFileData &data, int offset) +void Database::readOpenFileData(const SqlResultIteratorInterface &result, OpenFileData &data, int offset) { data.fid = result.value(offset++).toInt(); data.animeTitle = result.value(offset++).toString(); @@ -1759,7 +1759,7 @@ void Database::readOpenFileData(SqlResultIteratorInterface &result, OpenFileData data.path = result.value(offset++).toString(); } -void Database::readUnknownFileData(SqlResultIteratorInterface &result, UnknownFile &data, int offset) +void Database::readUnknownFileData(const SqlResultIteratorInterface &result, UnknownFile &data, int offset) { data.ed2k = result.value(offset++).toByteArray(); data.size = result.value(offset++).toLongLong(); @@ -1767,7 +1767,7 @@ void Database::readUnknownFileData(SqlResultIteratorInterface &result, UnknownFi data.path = result.value(offset++).toString(); } -void Database::readPendingMyListUpdateData(SqlResultIteratorInterface &query, PendingMyListUpdate &data, int offset) +void Database::readPendingMyListUpdateData(const SqlResultIteratorInterface &query, PendingMyListUpdate &data, int offset) { data.updateId = query.value(offset++).toLongLong(); data.fid = query.value(offset++).toInt(); diff --git a/localmylist/database.h b/localmylist/database.h index fb3f0c4..85ba817 100644 --- a/localmylist/database.h +++ b/localmylist/database.h @@ -150,13 +150,13 @@ public slots: bool connect(); void disconnect(); - static void readAnimeData(SqlResultIteratorInterface &result, Anime &data, int offset = 0); - static void readEpisodeData(SqlResultIteratorInterface &result, Episode &data, int offset = 0); - static void readFileData(SqlResultIteratorInterface &result, File &data, int offset = 0); - static void readFileLocationData(SqlResultIteratorInterface &result, FileLocation &data, int offset = 0); - static void readOpenFileData(SqlResultIteratorInterface &result, OpenFileData &data, int offset = 0); - static void readUnknownFileData(SqlResultIteratorInterface &result, UnknownFile &data, int offset = 0); - static void readPendingMyListUpdateData(SqlResultIteratorInterface &result, PendingMyListUpdate &data, int offset = 0); + static void readAnimeData(const SqlResultIteratorInterface &result, Anime &data, int offset = 0); + static void readEpisodeData(const SqlResultIteratorInterface &result, Episode &data, int offset = 0); + static void readFileData(const SqlResultIteratorInterface &result, File &data, int offset = 0); + static void readFileLocationData(const SqlResultIteratorInterface &result, FileLocation &data, int offset = 0); + static void readOpenFileData(const SqlResultIteratorInterface &result, OpenFileData &data, int offset = 0); + static void readUnknownFileData(const SqlResultIteratorInterface &result, UnknownFile &data, int offset = 0); + static void readPendingMyListUpdateData(const SqlResultIteratorInterface &result, PendingMyListUpdate &data, int offset = 0); static QString animeFields(); static QString episodeFields(); diff --git a/localmylist/dynamicmodel/data.cpp b/localmylist/dynamicmodel/data.cpp index 47c1bc8..32e46b1 100644 --- a/localmylist/dynamicmodel/data.cpp +++ b/localmylist/dynamicmodel/data.cpp @@ -135,5 +135,140 @@ QVariant AnimeData::data(int column, int role) const return QVariant(); } +// ========================================================== + +EpisodeData::EpisodeData(DataType *dataType) : Data(dataType) +{ + +} + +int EpisodeData::id() const +{ + return episodeData.eid; +} + +QVariant EpisodeData::data(int column, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + switch (column) + { + case 0: + return episodeData.titleEnglish; + case 1: + return episodeData.type + QString::number(episodeData.epno); + case 2: + if (episodeData.rating < 1) + return "n/a"; + return QString::number(episodeData.rating, 'f', 2); + case 3: + if (episodeData.myVote < 1) + return "n/a"; + return QString::number(episodeData.myVote, 'f', 2); + case 4: + if (!watchedDate.isValid()) + return QObject::tr("No"); + return QObject::tr("Yes, on %1").arg(watchedDate.toString()); + case 5: + return stateIdToState(myStates); + } + break; + case Qt::ToolTipRole: + switch (column) + { + case 0: + if (!episodeData.titleEnglish.isEmpty() && !episodeData.titleKanji.isEmpty()) + return QString("%1 -- %2").arg(episodeData.titleKanji) + .arg(episodeData.titleRomaji); + if (!episodeData.titleEnglish.isEmpty()) + return episodeData.titleEnglish; + if (!episodeData.titleKanji.isEmpty()) + return episodeData.titleKanji; + } + break; + case Qt::EditRole: + switch (column) + { + case 3: + return episodeData.myVote; + } + break; + } + + return QVariant(); +} + +FileData::FileData(DataType *dataType) : Data(dataType) +{ +} + +int FileData::id() const +{ + return fileData.fid; +} + +QVariant FileData::data(int column, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + switch (column) + { + case 0: + if (!fileData.gid) + return QObject::tr("Unknown"); + return fileData.groupName; + case 1: + return "v" + QString::number(fileData.version); + case 2: + return fileData.quality; + case 3: + return ""; + case 4: + if (!fileData.myWatched.isValid()) + return QObject::tr("No"); + return QObject::tr("Yes, on %1").arg(fileData.myWatched.toString()); + case 5: + return stateIdToState(fileData.myState); + } + return QVariant(); +} + +FileLocationData::FileLocationData(DataType *dataType) : Data(dataType) +{ +} + +int FileLocationData::id() const +{ + return fileLocationData.locationId; +} + +QVariant FileLocationData::data(int column, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + switch (column) + { + case 0: + return fileLocationData.path; + case 1: + return QString("%1 (%2)").arg(hostName) + .arg(fileLocationData.hostId); + case 2: + return ""; + case 3: + return ""; + case 4: + if (!fileLocationData.renamed.isValid()) + return QObject::tr("No"); + if (fileLocationData.failedRename) + return QObject::tr("Rename failed"); + return QObject::tr("Yes, on %1").arg(fileLocationData.renamed.toString()); + } + return QVariant(); +} + } // namespace DynamicModel } // namespace LocalMyList diff --git a/localmylist/dynamicmodel/data.h b/localmylist/dynamicmodel/data.h index 54692b4..430a9b2 100644 --- a/localmylist/dynamicmodel/data.h +++ b/localmylist/dynamicmodel/data.h @@ -50,6 +50,43 @@ public: int myState; }; +class LOCALMYLISTSHARED_EXPORT EpisodeData : public Data +{ +public: + EpisodeData(DataType *dataType); + + int id() const; + QVariant data(int column, int role) const; + + Episode episodeData; + QDateTime watchedDate; + int episodeTypeOrdering; + int myStates; +}; + +class LOCALMYLISTSHARED_EXPORT FileData : public Data +{ +public: + FileData(DataType *dataType); + + int id() const; + QVariant data(int column, int role) const; + + File fileData; +}; + +class LOCALMYLISTSHARED_EXPORT FileLocationData : public Data +{ +public: + FileLocationData(DataType *dataType); + + int id() const; + QVariant data(int column, int role) const; + + FileLocation fileLocationData; + QString hostName; +}; + } // namespace DynamicModel } // namespace LocalMyList diff --git a/localmylist/dynamicmodel/datamodel.cpp b/localmylist/dynamicmodel/datamodel.cpp index 67e8b52..a713a67 100644 --- a/localmylist/dynamicmodel/datamodel.cpp +++ b/localmylist/dynamicmodel/datamodel.cpp @@ -25,6 +25,7 @@ bool DataModel::registerDataType(DataType *dataType) dataTypes.insert(dataType); dataTypeNames.insert(dataType->name(), dataType); + dataType->m_model = this; return true; } @@ -35,7 +36,8 @@ bool DataModel::registerTypeRelation(TypeRelation *typeRelation) if (!typeRelation->sourceType().isEmpty() && !dataTypeNames.contains(typeRelation->sourceType())) return false; - if (!dataTypeNames.contains(typeRelation->destinationType())) + const auto destinationTypeIt = dataTypeNames.find(typeRelation->destinationType()); + if (destinationTypeIt == dataTypeNames.constEnd()) return false; auto it = typeRelations.find(typeRelation->sourceType()); @@ -48,6 +50,7 @@ bool DataModel::registerTypeRelation(TypeRelation *typeRelation) return false; it.value().insert(typeRelation->destinationType(), typeRelation); + typeRelation->m_dataType = destinationTypeIt.value(); return true; } diff --git a/localmylist/dynamicmodel/datatype.cpp b/localmylist/dynamicmodel/datatype.cpp index 19966a6..300665e 100644 --- a/localmylist/dynamicmodel/datatype.cpp +++ b/localmylist/dynamicmodel/datatype.cpp @@ -1,13 +1,15 @@ #include "datatype.h" #include - +#include +#include "../database.h" +#include "../mylist.h" #include "data.h" namespace LocalMyList { namespace DynamicModel { -DataType::DataType(QObject *parent) : QObject(parent), m_size(0) +DataType::DataType(QObject *parent) : QObject(parent), m_size(0), m_model(0) { query = new SqlAsyncQuery(this); } @@ -17,6 +19,11 @@ DataType::~DataType() } +DataModel *DataType::model() const +{ + return m_model; +} + QStringList DataType::availableChildRelations() const { return QStringList(); @@ -27,12 +34,6 @@ Data *DataType::data(int key) const return m_dataStore.value(key, 0); } -bool DataType::canGetChildren(const QString &childTypeName) const -{ - Q_UNUSED(childTypeName); - return false; -} - void DataType::update(Data *data) { Q_UNUSED(data); @@ -57,5 +58,24 @@ void DataType::released(Data *data) delete data; } +int DataType::sizeHelper(const QString &tableName, const QString &keyName) const +{ + if (m_size) + return m_size; + + QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString( + "SELECT count(%1) FROM %2") + .arg(keyName, tableName)); + + if (!MyList::instance()->database()->exec(q)) + return 0; + + if (!q.next()) + return 0; + + m_size = q.value(0).toInt(); + q.finish(); +} + } // namespace DynamicModel } // namespace LocalMyList diff --git a/localmylist/dynamicmodel/datatype.h b/localmylist/dynamicmodel/datatype.h index 575cd41..c81fa95 100644 --- a/localmylist/dynamicmodel/datatype.h +++ b/localmylist/dynamicmodel/datatype.h @@ -8,23 +8,28 @@ #include #include #include + namespace LocalMyList { namespace DynamicModel { class Data; class Node; +class DataModel; typedef bool (*NodeCompare)(Node *a, Node *b); class LOCALMYLISTSHARED_EXPORT DataType : public QObject { + friend class DataModel; Q_OBJECT public: DataType(QObject *parent = 0); virtual ~DataType(); + DataModel *model() const; + virtual QString name() const = 0; - virtual QStringList availableChildRelations() const; + QStringList availableChildRelations() const; virtual QString baseQuery() const = 0; @@ -32,10 +37,6 @@ public: virtual int size() const = 0; // InternalData *internalData(int key) const; - // Acquire - virtual bool canGetChildren(const QString &childTypeName) const; - virtual NodeList getChildren(Model *model, Node *parent, const QString &childTypeName, int offset) = 0; - // Update virtual void update(Data *data); virtual void childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation); @@ -45,13 +46,18 @@ public: virtual NodeCompare nodeCompareFunction() const = 0; + // Type relation interface + virtual Data *readEntry(const SqlResultIteratorInterface &it) = 0; + protected: + int sizeHelper(const QString &tableName, const QString &keyName) const; + SqlAsyncQuery *query; mutable int m_size; + QHash m_dataStore; - static const int LIMIT = 400; private: - QHash m_dataStore; + DataModel *m_model; }; } // namespace DynamicModel diff --git a/localmylist/dynamicmodel/dynamicmodel_global.h b/localmylist/dynamicmodel/dynamicmodel_global.h index 757a847..97c3693 100644 --- a/localmylist/dynamicmodel/dynamicmodel_global.h +++ b/localmylist/dynamicmodel/dynamicmodel_global.h @@ -1,9 +1,16 @@ #ifndef DYNAMICMODEL_GLOBAL_H #define DYNAMICMODEL_GLOBAL_H +#include + namespace LocalMyList { namespace DynamicModel { +class Node; +class Data; + +typedef ::std::function NodeFactory; + enum Operation { UpdateOperation, InsertOperation, diff --git a/localmylist/dynamicmodel/model.cpp b/localmylist/dynamicmodel/model.cpp index 953f4c9..ea74f58 100644 --- a/localmylist/dynamicmodel/model.cpp +++ b/localmylist/dynamicmodel/model.cpp @@ -17,6 +17,12 @@ Model::~Model() delete rootItem; } +void Model::setQuery(const QString &query) +{ + dataTypeNames = query.split(QChar('|')); + reload(); +} + QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) @@ -153,6 +159,20 @@ DataType *Model::rootDataType() const return m_dataModel->dataType("anime"); } +DataType *Model::childDataType(Node *node) const +{ + int d = node->depth() + 1; + + return childDataType(d); +} + +DataType *Model::childDataType(int i) const +{ + if (dataTypeNames.count() <= i) + return 0; + return dataModel()->dataType(dataTypeNames.at(i)); +} + void Model::reload() { beginResetModel(); @@ -175,8 +195,8 @@ void Model::setDataModel(DataModel *dataModel) Node *Model::createRootNode() { Node *n = new Node(this, 0, 0, 0); - if (m_dataModel) - n->setChildDataType(m_dataModel->dataType("anime")); + if (m_dataModel && !dataTypeNames.isEmpty()) + n->setChildDataType(dataModel()->dataType(dataTypeNames.at(0))); return n; } diff --git a/localmylist/dynamicmodel/model.h b/localmylist/dynamicmodel/model.h index a9c8ba8..6d98341 100644 --- a/localmylist/dynamicmodel/model.h +++ b/localmylist/dynamicmodel/model.h @@ -3,6 +3,7 @@ #include "../localmylist_global.h" #include +#include namespace LocalMyList { namespace DynamicModel { @@ -22,6 +23,8 @@ public: explicit Model(QObject *parent = 0); ~Model(); + void setQuery(const QString &query); + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; Qt::ItemFlags flags(const QModelIndex &index) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; @@ -43,6 +46,10 @@ public: DataType *rootDataType() const; + DataType *childDataType(Node *node) const; + DataType *childDataType(int i) const; + + public slots: void reload(); @@ -56,6 +63,8 @@ private: Node *rootItem; DataModel* m_dataModel; + + QStringList dataTypeNames; }; } // namespace DynamicModel diff --git a/localmylist/dynamicmodel/node.cpp b/localmylist/dynamicmodel/node.cpp index 996afc9..7a15780 100644 --- a/localmylist/dynamicmodel/node.cpp +++ b/localmylist/dynamicmodel/node.cpp @@ -4,6 +4,7 @@ #include "dynamicmodel_global.h" #include "data.h" #include "model.h" +#include "typerelation.h" #include #include @@ -38,7 +39,7 @@ DataType *Node::childDataType() const void Node::setChildDataType(DataType *dataType) { - Q_ASSERT_X(dataType, "dynamicmodel", "NULL data type"); +// Q_ASSERT_X(dataType, "dynamicmodel", "NULL data type"); m_childType = dataType; } @@ -79,7 +80,7 @@ bool Node::hasChildren() const QVariant Node::data(int column, int role) const { - qDebug() << parent() << column; +// qDebug() << parent() << column; if (parent()) return m_data->data(column, role); @@ -115,7 +116,7 @@ int Node::totalRowCount() const bool Node::canFetchMore() const { - if (!m_parent && !totalRowCount()) + if (isRoot() && !totalRowCount()) return true; if (childCount() < totalRowCount()) @@ -129,10 +130,28 @@ void Node::fetchMore() return; qDebug() << "fetchMote" << this; NodeList newItems; - if (m_data) - newItems = data()->type()->getChildren(m_model, this, m_childType->name(), childCount()); + + TypeRelation *rel = 0; + if (isRoot()) + rel = m_model->dataModel()->typeRelation(QString(), childDataType()->name()); else - newItems = m_model->rootDataType()->getChildren(m_model, this, m_childType->name(), childCount()); + rel = m_model->dataModel()->typeRelation(m_data->type()->name(), childDataType()->name()); + + if (!rel) + return; + + DataType *childDataType = m_model->childDataType(this); + DataType *rowCountType = m_model->childDataType(depth() + isRoot() ? 1 : 2); + + auto factory = [=](Data *d, int c) -> Node * + { + Node *n = new Node(m_model, this, c, d); + n->setChildDataType(childDataType); + return n; + }; + + + newItems = rel->getChildren(m_data, childCount(), rowCountType, factory); const QModelIndex parent = m_model->index(this); const int newrows = newItems.count(); @@ -232,6 +251,18 @@ MoveType Node::moveChild(Node *child, Operation type) return SuccessfulMove; } +int Node::depth() const +{ + Node *node = parent(); + int depth = 0; + while (node) + { + ++depth; + node = node->parent(); + } + return depth; +} + int Node::id() const { return m_data->id(); diff --git a/localmylist/dynamicmodel/node.h b/localmylist/dynamicmodel/node.h index ee6b6ff..e30c5a8 100644 --- a/localmylist/dynamicmodel/node.h +++ b/localmylist/dynamicmodel/node.h @@ -25,6 +25,8 @@ public: DataType *childDataType() const; void setChildDataType(DataType *dataType); + bool isRoot() const { return !m_parent; } + // Structure Node *parent() const; Node *child(int row) const; @@ -48,6 +50,9 @@ public: bool updated(Operation type); MoveType moveChild(Node *child, Operation type); + // Misc + int depth() const; + protected: Node *m_parent; NodeList m_children; diff --git a/localmylist/dynamicmodel/typerelation.cpp b/localmylist/dynamicmodel/typerelation.cpp index 3591b5e..38e32e3 100644 --- a/localmylist/dynamicmodel/typerelation.cpp +++ b/localmylist/dynamicmodel/typerelation.cpp @@ -11,13 +11,34 @@ namespace LocalMyList { namespace DynamicModel { -TypeRelation::TypeRelation(QObject *parent) : QObject(parent) +TypeRelation::TypeRelation(QObject *parent) : QObject(parent), m_dataType(0) { } -Node *TypeRelation::createNode(Model *model, Node *parent, int totalRowCount, Data *data) +DataType *TypeRelation::dataType() const +{ + return m_dataType; +} + +QString TypeRelation::childRowCountQuery(DataType *type) const +{ + static const QString zeroQuery("0"); + + if (!type) + return zeroQuery; + + TypeRelation *rel = dataType()->model()->typeRelation(destinationType(), type->name()); + + if (!rel) + return zeroQuery; + + return rel->rowCountQuery(); +} + +// =========================================================================== + +RootAnimeRelation::RootAnimeRelation(QObject *parent) : TypeRelation(parent) { - return new Node(model, parent, totalRowCount, data); } QString RootAnimeRelation::sourceType() const @@ -30,54 +51,208 @@ QString RootAnimeRelation::destinationType() const return "anime"; } -bool RootAnimeRelation::canGetChildren() const +NodeList RootAnimeRelation::getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory) { - return false; -} + Q_UNUSED(parent); -NodeList RootAnimeRelation::getChildren(Model *model, Node *parent, int offset) -{ -/* QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString( + QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString( + "SELECT %2, " "%1 " "ORDER BY title_romaji ASC " "LIMIT :limit " "OFFSET :offset ") - .arg(parent->childDataType()->baseQuery())); - q.bindValue(":limit", 200); + .arg(dataType()->baseQuery()) + .arg(childRowCountQuery(rowCountType))); + q.bindValue(":limit", LIMIT); + q.bindValue(":offset", offset); + + if (!q.exec()) + return NodeList(); + + NodeList newItems; + QSqlResultIterator it(q); + while (it.next()) + { + int totalRowCount = it.value(0).toInt(); + Data *data = dataType()->readEntry(it); + auto node = nodeFactory(data, totalRowCount); + newItems << node; + } + + return newItems; +} + +QString RootAnimeRelation::rowCountQuery() const +{ + return "(SELECT COUNT(aid) FROM anime)"; +} + +// ================================================= + +RootEpisodeRelation::RootEpisodeRelation(QObject *parent) : TypeRelation(parent) +{ +} + +QString RootEpisodeRelation::sourceType() const +{ + return QString(); +} + +QString RootEpisodeRelation::destinationType() const +{ + return "episode"; +} + +NodeList RootEpisodeRelation::getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory) +{ + Q_UNUSED(parent) + + QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString( + "SELECT " + " %2, " + " %1 " + " ORDER BY et.ordering ASC, e.epno ASC " + " LIMIT :limit " + " OFFSET :offset ").arg(dataType()->baseQuery(), childRowCountQuery(rowCountType))); + q.bindValue(":limit", LIMIT); q.bindValue(":offset", offset); if (!q.exec()) return NodeList(); NodeList newItems; - while (q.next()) + QSqlResultIterator it(q); + while (it.next()) { - AnimeData *ad = new AnimeData(this); - int totalRowCount = query->value(0).toInt(); - - { - QSqlResultIterator it(q); - fillAnimeData(*ad, it); - } - - auto it = cache.find(ad->id()); - if (it != cache.end()) - { - delete ad; - ad = *it; - } - else - { - cache.insert(ad->id(), ad); - } - - auto node = new Node(model, parent, totalRowCount, ad); + int totalRowCount = it.value(0).toInt(); + Data *data = dataType()->readEntry(it); + auto node = nodeFactory(data, totalRowCount); newItems << node; } return newItems; -*/ - return NodeList(); +} + +QString RootEpisodeRelation::rowCountQuery() const +{ + return "(SELECT COUNT(eid) FROM episode)"; +} + +// ================================================= + +AnimeEpisodeRelation::AnimeEpisodeRelation(QObject *parent) : TypeRelation(parent) +{ +} + +QString AnimeEpisodeRelation::sourceType() const +{ + return "anime"; +} + +QString AnimeEpisodeRelation::destinationType() const +{ + return "episode"; +} + +NodeList AnimeEpisodeRelation::getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory) +{ + if (!parent) + return NodeList(); + + QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString( + "SELECT " + " %2, " + " %1 " + " WHERE e.aid = :aid " + " ORDER BY et.ordering ASC, e.epno ASC " + " LIMIT :limit " + " OFFSET :offset ").arg(dataType()->baseQuery(), childRowCountQuery(rowCountType))); + q.bindValue(":aid", parent->id()); + q.bindValue(":limit", LIMIT); + q.bindValue(":offset", offset); + + if (!q.exec()) + return NodeList(); + + NodeList newItems; + QSqlResultIterator it(q); + while (it.next()) + { + int totalRowCount = it.value(0).toInt(); + Data *data = dataType()->readEntry(it); + auto node = nodeFactory(data, totalRowCount); + newItems << node; + } + + return newItems; +} + +QString AnimeEpisodeRelation::rowCountQuery() const +{ + return "(SELECT COUNT(eid) FROM episode WHERE aid = a.aid)"; +} + +EpisodeFileRelation::EpisodeFileRelation(QObject *parent) : TypeRelation(parent) +{ +} + +QString EpisodeFileRelation::sourceType() const +{ + return "episode"; +} + +QString EpisodeFileRelation::destinationType() const +{ + return "file"; +} + +NodeList EpisodeFileRelation::getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory) +{ + if (!parent) + return NodeList(); + + QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString( + "SELECT %2, %1 " + " FROM file f " + " WHERE f.eid = :eida " + "UNION " + "SELECT %2, %1 FROM file f " + " JOIN file_episode_rel fer ON (fer.fid = f.fid) " + " WHERE fer.eid = :eidb ").arg(dataType()->baseQuery(), childRowCountQuery(rowCountType))); + q.bindValue(":eida", parent->id()); + q.bindValue(":eidb", parent->id()); + q.bindValue(":limit", LIMIT); + q.bindValue(":offset", offset); + + if (!q.exec()) + return NodeList(); + + NodeList newItems; + QSqlResultIterator it(q); + while (it.next()) + { + int totalRowCount = it.value(0).toInt(); + Data *data = dataType()->readEntry(it); + auto node = nodeFactory(data, totalRowCount); + newItems << node; + } + + return newItems; +} + +QString EpisodeFileRelation::rowCountQuery() const +{ + return + " (SELECT COUNT(fid) " + " FROM ( " + " SELECT fid " + " FROM file " + " WHERE eid = e.eid " + " UNION " + " SELECT f.fid FROM file f " + " JOIN file_episode_rel fer ON (fer.fid = f.fid) " + " WHERE fer.eid = e.eid " + " ) sq) "; } } // namespace DynamicModel diff --git a/localmylist/dynamicmodel/typerelation.h b/localmylist/dynamicmodel/typerelation.h index 68e1be2..de10a33 100644 --- a/localmylist/dynamicmodel/typerelation.h +++ b/localmylist/dynamicmodel/typerelation.h @@ -1,6 +1,7 @@ #ifndef TYPERELATION_H #define TYPERELATION_H +#include "../localmylist_global.h" #include #include @@ -10,33 +11,88 @@ namespace LocalMyList { namespace DynamicModel { -class TypeRelation : public QObject +class LOCALMYLISTSHARED_EXPORT TypeRelation : public QObject { + Q_OBJECT + friend class DataModel; public: TypeRelation(QObject *parent = 0); virtual QString sourceType() const = 0; virtual QString destinationType() const = 0; - // Acquire - virtual bool canGetChildren() const = 0; - virtual NodeList getChildren(Model *model, Node *parent, const QString &childTypeName, int offset) = 0; + virtual NodeList getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory) = 0; + virtual QString rowCountQuery() const = 0; + DataType *dataType() const; protected: - Node *createNode(Model *model, Node *parent, int totalRowCount, Data *data); + QString childRowCountQuery(DataType *type) const; + + static const int LIMIT = 400; + +private: + DataType *m_dataType; +}; + +// ========================================================================================================= + +class LOCALMYLISTSHARED_EXPORT RootAnimeRelation : public TypeRelation +{ + Q_OBJECT +public: + RootAnimeRelation(QObject *parent); + + QString sourceType() const; + QString destinationType() const; + + NodeList getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory); + QString rowCountQuery() const; +}; + +// ================ + +class LOCALMYLISTSHARED_EXPORT RootEpisodeRelation : public TypeRelation +{ + Q_OBJECT +public: + RootEpisodeRelation(QObject *parent); + + QString sourceType() const; + QString destinationType() const; + + NodeList getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory); + QString rowCountQuery() const; +}; + +// ================ + +class LOCALMYLISTSHARED_EXPORT AnimeEpisodeRelation : public TypeRelation +{ + Q_OBJECT +public: + AnimeEpisodeRelation(QObject *parent); + + QString sourceType() const; + QString destinationType() const; + + NodeList getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory); + QString rowCountQuery() const; }; // ========================================================================================================= -class RootAnimeRelation +class LOCALMYLISTSHARED_EXPORT EpisodeFileRelation : public TypeRelation { + Q_OBJECT +public: + EpisodeFileRelation(QObject *parent); + QString sourceType() const; QString destinationType() const; - // Acquire - bool canGetChildren() const; - NodeList getChildren(Model *model, Node *parent, int offset); + NodeList getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory); + QString rowCountQuery() const; }; } // namespace DynamicModel diff --git a/localmylist/dynamicmodel/types.cpp b/localmylist/dynamicmodel/types.cpp index 6ed95e2..776632d 100644 --- a/localmylist/dynamicmodel/types.cpp +++ b/localmylist/dynamicmodel/types.cpp @@ -2,6 +2,8 @@ #include "../database.h" #include "../mylist.h" +#include "datamodel.h" +#include "typerelation.h" namespace LocalMyList { namespace DynamicModel { @@ -19,7 +21,6 @@ QStringList AnimeType::availableChildRelations() const QString AnimeType::baseQuery() const { return QString( - "SELECT (SELECT COUNT(eid) FROM episode WHERE aid = a.aid), " " (SELECT COUNT(e.eid) " " FROM episode e " " WHERE e.aid = a.aid), " @@ -55,84 +56,176 @@ QString AnimeType::baseQuery() const int AnimeType::size() const { - if (m_size) - return m_size; + return sizeHelper("anime", "aid"); +} - QSqlQuery &q = MyList::instance()->database()->prepare( - "SELECT count(aid) FROM anime"); +void AnimeType::update(Data *data) +{ - if (!MyList::instance()->database()->exec(q)) - return 0; +} - if (!q.next()) - return 0; +void AnimeType::childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation) +{ - m_size = q.value(0).toInt(); - q.finish(); +} - return m_size; +NodeCompare AnimeType::nodeCompareFunction() const +{ + return [](Node *a, Node *b) -> bool + { + return a < b; + }; } -bool AnimeType::canGetChildren(const QString &childTypeName) const +Data *AnimeType::readEntry(const SqlResultIteratorInterface &it) { - Q_UNUSED(childTypeName) - return false; + AnimeData *animeData = new AnimeData(this); + + fillAnimeData(*animeData, it); + + Data *newData = animeData; + Data *currentData = data(animeData->id()); + if (currentData) + { + delete animeData; + newData = currentData; + } + else + { + m_dataStore.insert(animeData->id(), newData); + } + return newData; } -NodeList AnimeType::getChildren(Model *model, Node *parent, const QString &childTypeName, int offset) +void AnimeType::fillAnimeData(AnimeData &data, const SqlResultIteratorInterface &query) { - QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString( - "%1 " - "ORDER BY title_romaji ASC " - "LIMIT :limit " - "OFFSET :offset ") - .arg(baseQuery())); - q.bindValue(":limit", LIMIT); - q.bindValue(":offset", offset); + data.episodesInMyList = query.value(1).toInt(); + data.watchedEpisodes = query.value(2).toInt(); + data.myState = query.value(3).toInt(); + Database::readAnimeData(query, data.animeData, 4); +} - if (!q.exec()) - return NodeList(); +// ============================================================================================================= - NodeList newItems; - while (q.next()) +QString EpisodeType::name() const +{ + return "episode"; +} + +QString EpisodeType::baseQuery() const +{ + return QString( + " (SELECT MIN(my_watched) " + " FROM " + " (SELECT my_watched " + " FROM file " + " WHERE eid = e.eid " + " AND my_watched IS NOT NULL " + " UNION " + " SELECT f.my_watched " + " FROM file f " + " JOIN file_episode_rel fer ON (fer.fid = f.fid) " + " WHERE fer.eid = e.eid " + " AND my_watched IS NOT NULL) AS sq) AS my_watched, " + " (SELECT CASE WHEN array_length(my_state_array, 1) > 1 THEN -1 ELSE my_state_array[1] END " + " FROM " + " (SELECT array_agg(my_state) my_state_array " + " FROM " + " (SELECT my_state " + " FROM file " + " WHERE eid = e.eid " + " UNION " + " SELECT f.my_state " + " FROM file f " + " JOIN file_episode_rel fer ON (fer.fid = f.fid) " + " WHERE fer.eid = e.eid) AS sq) AS sq) AS my_state, " + " et.ordering, %1 " + " FROM episode e " + " JOIN episode_type et ON (et.type = e.type)") + .arg(Database::episodeFields()); +} + +int EpisodeType::size() const +{ + return sizeHelper("episode", "eid"); +} + +void EpisodeType::update(Data *data) +{ + +} + +void EpisodeType::childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation) +{ + +} + +NodeCompare EpisodeType::nodeCompareFunction() const +{ + return [](Node *a, Node *b) -> bool { - AnimeData *ad = new AnimeData(this); - int totalRowCount = query->value(0).toInt(); - - { - QSqlResultIterator it(q); - fillAnimeData(*ad, it); - } - - auto it = cache.find(ad->id()); - if (it != cache.end()) - { - delete ad; - ad = *it; - } - else - { - cache.insert(ad->id(), ad); - } - - auto node = new Node(model, parent, totalRowCount, ad); - newItems << node; + return a < b; + }; +} + +Data *EpisodeType::readEntry(const SqlResultIteratorInterface &it) +{ + EpisodeData *animeData = new EpisodeData(this); + + fillEpisodeData(*animeData, it); + + Data *newData = animeData; + Data *currentData = data(animeData->id()); + if (currentData) + { + delete animeData; + newData = currentData; } + else + { + m_dataStore.insert(animeData->id(), newData); + } + return newData; +} - return newItems; +void EpisodeType::fillEpisodeData(EpisodeData &data, const SqlResultIteratorInterface &query) +{ + data.watchedDate = query.value(1).toDateTime(); + data.myStates = query.value(2).toInt(); + data.episodeTypeOrdering = query.value(3).toInt(); + Database::readEpisodeData(query, data.episodeData, 4); } -void AnimeType::update(Data *data) +// ============================================================================================================= + +QString FileType::name() const +{ + return "file"; +} + +QString FileType::baseQuery() const { + return QString( + "%1") + .arg(Database::fileFields()); +} +int FileType::size() const +{ + return sizeHelper("file", "fid"); } -void AnimeType::childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation) +void FileType::update(Data *data) { } -NodeCompare AnimeType::nodeCompareFunction() const +void FileType::childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation) +{ + +} + +NodeCompare FileType::nodeCompareFunction() const { return [](Node *a, Node *b) -> bool { @@ -140,12 +233,29 @@ NodeCompare AnimeType::nodeCompareFunction() const }; } -void AnimeType::fillAnimeData(AnimeData &data, SqlResultIteratorInterface &query) +Data *FileType::readEntry(const SqlResultIteratorInterface &it) { - data.episodesInMyList = query.value(1).toInt(); - data.watchedEpisodes = query.value(2).toInt(); - data.myState = query.value(3).toInt(); - Database::readAnimeData(query, data.animeData, 4); + FileData *animeData = new FileData(this); + + fillFileData(*animeData, it); + + Data *newData = animeData; + Data *currentData = data(animeData->id()); + if (currentData) + { + delete animeData; + newData = currentData; + } + else + { + m_dataStore.insert(animeData->id(), newData); + } + return newData; +} + +void FileType::fillFileData(FileData &data, const SqlResultIteratorInterface &query) +{ + Database::readFileData(query, data.fileData, 1); } } // namespace DynamicModel diff --git a/localmylist/dynamicmodel/types.h b/localmylist/dynamicmodel/types.h index 7b29476..4f69a1b 100644 --- a/localmylist/dynamicmodel/types.h +++ b/localmylist/dynamicmodel/types.h @@ -5,6 +5,8 @@ #include "datatype.h" #include "data.h" +#include + namespace LocalMyList { namespace DynamicModel { @@ -17,17 +19,56 @@ class LOCALMYLISTSHARED_EXPORT AnimeType : public DataType int size() const; - bool canGetChildren(const QString &childTypeName) const; - NodeList getChildren(Model *model, Node *parent, const QString &childTypeName, int offset); + void update(Data *data); + void childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation); + + NodeCompare nodeCompareFunction() const; + + Data *readEntry(const SqlResultIteratorInterface &it); + +private: + void fillAnimeData(AnimeData &data, const SqlResultIteratorInterface &query); +}; + +// ============================================================================================================= + +class LOCALMYLISTSHARED_EXPORT EpisodeType : public DataType +{ + QString name() const; + + QString baseQuery() const; + + int size() const; void update(Data *data); void childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation); NodeCompare nodeCompareFunction() const; +protected: + Data *readEntry(const SqlResultIteratorInterface &it); + +private: + void fillEpisodeData(EpisodeData &data, const SqlResultIteratorInterface &query); +}; + +// ============================================================================================================= + +class LOCALMYLISTSHARED_EXPORT FileType : public DataType +{ + QString name() const; + QString baseQuery() const; + int size() const; + + void update(Data *data); + void childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation); + + NodeCompare nodeCompareFunction() const; + + Data *readEntry(const SqlResultIteratorInterface &it); + private: - void fillAnimeData(AnimeData &data, SqlResultIteratorInterface &query); - QMap cache; + void fillFileData(FileData &data, const SqlResultIteratorInterface &query); }; } // namespace DynamicModel -- 2.52.0