#include "dynamicmodel/model.h"
#include "dynamicmodel/datamodel.h"
#include "dynamicmodel/types.h"
+#include "dynamicmodel/typerelation.h"
using namespace LocalMyList::DynamicModel;
{
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);
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()
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();
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();
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();
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();
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();
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();
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();
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();
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
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
dataTypes.insert(dataType);
dataTypeNames.insert(dataType->name(), dataType);
+ dataType->m_model = this;
return true;
}
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());
return false;
it.value().insert(typeRelation->destinationType(), typeRelation);
+ typeRelation->m_dataType = destinationTypeIt.value();
return true;
}
#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);
}
}
+DataModel *DataType::model() const
+{
+ return m_model;
+}
+
QStringList DataType::availableChildRelations() const
{
return QStringList();
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);
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
#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;
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);
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
#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,
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)
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();
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;
}
#include "../localmylist_global.h"
#include <QAbstractItemModel>
+#include <QStringList>
namespace LocalMyList {
namespace DynamicModel {
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;
DataType *rootDataType() const;
+ DataType *childDataType(Node *node) const;
+ DataType *childDataType(int i) const;
+
+
public slots:
void reload();
Node *rootItem;
DataModel* m_dataModel;
+
+ QStringList dataTypeNames;
};
} // namespace DynamicModel
#include "dynamicmodel_global.h"
#include "data.h"
#include "model.h"
+#include "typerelation.h"
#include <QModelIndex>
#include <QDebug>
void Node::setChildDataType(DataType *dataType)
{
- Q_ASSERT_X(dataType, "dynamicmodel", "NULL data type");
+// Q_ASSERT_X(dataType, "dynamicmodel", "NULL data type");
m_childType = dataType;
}
QVariant Node::data(int column, int role) const
{
- qDebug() << parent() << column;
+// qDebug() << parent() << column;
if (parent())
return m_data->data(column, role);
bool Node::canFetchMore() const
{
- if (!m_parent && !totalRowCount())
+ if (isRoot() && !totalRowCount())
return true;
if (childCount() < totalRowCount())
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();
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();
DataType *childDataType() const;
void setChildDataType(DataType *dataType);
+ bool isRoot() const { return !m_parent; }
+
// Structure
Node *parent() const;
Node *child(int row) const;
bool updated(Operation type);
MoveType moveChild(Node *child, Operation type);
+ // Misc
+ int depth() const;
+
protected:
Node *m_parent;
NodeList m_children;
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
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
#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
#include "../database.h"
#include "../mylist.h"
+#include "datamodel.h"
+#include "typerelation.h"
namespace LocalMyList {
namespace DynamicModel {
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), "
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
{
};
}
-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
#include "datatype.h"
#include "data.h"
+#include <functional>
+
namespace LocalMyList {
namespace DynamicModel {
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