]> Some of my projects - localmylist.git/commitdiff
More complete dynamic model.
authorAPTX <marek321@gmail.com>
Sat, 3 Aug 2013 17:04:35 +0000 (19:04 +0200)
committerAPTX <marek321@gmail.com>
Sat, 3 Aug 2013 17:04:35 +0000 (19:04 +0200)
17 files changed:
localmylist-management/tabs/dynamicmodeltab.cpp
localmylist/database.cpp
localmylist/database.h
localmylist/dynamicmodel/data.cpp
localmylist/dynamicmodel/data.h
localmylist/dynamicmodel/datamodel.cpp
localmylist/dynamicmodel/datatype.cpp
localmylist/dynamicmodel/datatype.h
localmylist/dynamicmodel/dynamicmodel_global.h
localmylist/dynamicmodel/model.cpp
localmylist/dynamicmodel/model.h
localmylist/dynamicmodel/node.cpp
localmylist/dynamicmodel/node.h
localmylist/dynamicmodel/typerelation.cpp
localmylist/dynamicmodel/typerelation.h
localmylist/dynamicmodel/types.cpp
localmylist/dynamicmodel/types.h

index 40a9026cbb00862a9763303a63e9b96f6ed62332..b18cba9ca16a8c55d55a5828f86da8ec29b8adc4 100644 (file)
@@ -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()
index ee587c461dd059b72df3fa47dcfe54d96babeb5e..f832e5b9beb1b169eb5014b440811e81832a5245 100644 (file)
@@ -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();
index fb3f0c4e908e3f75d118f48b0b16a36171cf0ac9..85ba8174553a1e06a9d055195524d2d6b68954fc 100644 (file)
@@ -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();
index 47c1bc896ff1829d2c8d360b5731828bc1b6ae3f..32e46b1ea624b20de9e05222ea04d888594e8f50 100644 (file)
@@ -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
index 54692b471683cbb26fb949ff6c5756c4013228a6..430a9b23dfe952a3457927240908c36e90a28448 100644 (file)
@@ -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
 
index 67e8b5250a7e2337eb582b3d4273d90d0a4f5a57..a713a6706405f6e2044df56a073e780970a33aed 100644 (file)
@@ -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;
 }
 
index 19966a6a3eb415a278cbf6b7e8168070c87b99ee..300665e5322f42dc751f8194ad45940b3dd54487 100644 (file)
@@ -1,13 +1,15 @@
 #include "datatype.h"
 
 #include <QtGlobal>
-
+#include <QSqlQuery>
+#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
index 575cd41f43a91c5f583a3193e88a9a931c0f2f2b..c81fa953afb236278835e22bab22a50d2baec859 100644 (file)
@@ -8,23 +8,28 @@
 #include <QObject>
 #include <QString>
 #include <QStringList>
+
 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<int, Data *> m_dataStore;
 
-       static const int LIMIT = 400;
 private:
-       QHash<int, Data *> m_dataStore;
+       DataModel *m_model;
 };
 
 } // namespace DynamicModel
index 757a847f29131e22626dfac7082c39b8cf434413..97c369318a548b3b06aeafdd1340438cc424ef04 100644 (file)
@@ -1,9 +1,16 @@
 #ifndef DYNAMICMODEL_GLOBAL_H
 #define DYNAMICMODEL_GLOBAL_H
 
+#include <functional>
+
 namespace LocalMyList {
 namespace DynamicModel {
 
+class Node;
+class Data;
+
+typedef ::std::function<Node *(Data *, int)> NodeFactory;
+
 enum Operation {
        UpdateOperation,
        InsertOperation,
index 953f4c9c1826e8c88fb752548ed81147b065cef6..ea74f58e695c66c97c10ce0299631b9a8681aeeb 100644 (file)
@@ -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;
 }
 
index a9c8ba8c6f13d0c23060e72f692b3057e3fef1c4..6d9834107831ba083b8af097f92b2a75191744db 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "../localmylist_global.h"
 #include <QAbstractItemModel>
+#include <QStringList>
 
 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
index 996afc94470047428b9d8886364d67bc8c0bd36d..7a1578024342a63a45a636332fc55b4872f34be1 100644 (file)
@@ -4,6 +4,7 @@
 #include "dynamicmodel_global.h"
 #include "data.h"
 #include "model.h"
+#include "typerelation.h"
 #include <QModelIndex>
 
 #include <QDebug>
@@ -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();
index ee6b6ff4f919916d0524e9fc9efddcdad17741db..e30c5a87f5a63ec42d70fa4821cd933bf05a61e7 100644 (file)
@@ -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;
index 3591b5eeac78ccf2677c89b0553eb3ed503b5d92..38e32e33463497da378cb445dc27604565c10cf3 100644 (file)
 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
index 68e1be2fa969543db09833f22ae33c00dc2d80ec..de10a3313912ec44b14d0ea74aca463b2f331ed1 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef TYPERELATION_H
 #define TYPERELATION_H
 
+#include "../localmylist_global.h"
 #include <QObject>
 
 #include <QString>
 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
index 6ed95e24ce18ab029182369c2e32a4d5fd95038c..776632d6d637f2451479e2a0bcb34fca39fd7f73 100644 (file)
@@ -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
index 7b294762b20309c9fb0a087f55f0206d84c1cb95..4f69a1b99a8ef90dfbca66852d96931e7a9417d8 100644 (file)
@@ -5,6 +5,8 @@
 #include "datatype.h"
 #include "data.h"
 
+#include <functional>
+
 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<int, AnimeData *> cache;
+       void fillFileData(FileData &data, const SqlResultIteratorInterface &query);
 };
 
 } // namespace DynamicModel