]> Some of my projects - localmylist.git/commitdiff
Progress!! dynamicmodel
authorAPTX <marek321@gmail.com>
Wed, 31 Dec 2014 23:37:32 +0000 (00:37 +0100)
committerAPTX <marek321@gmail.com>
Wed, 31 Dec 2014 23:37:32 +0000 (00:37 +0100)
(see master branch for actual commit with new dynamic model)

42 files changed:
localmylist-management/dynamicmodelfiltermodel.cpp [new file with mode: 0644]
localmylist-management/dynamicmodelfiltermodel.h [new file with mode: 0644]
localmylist-management/dynamicmodelitemdelegate.cpp [new file with mode: 0644]
localmylist-management/dynamicmodelitemdelegate.h [new file with mode: 0644]
localmylist-management/dynamicmodelview.cpp [new file with mode: 0644]
localmylist-management/dynamicmodelview.h [new file with mode: 0644]
localmylist-management/localmylist-management.pro
localmylist-management/tabs/dynamicmodeltab.cpp
localmylist-management/tabs/dynamicmodeltab.h
localmylist-management/tabs/dynamicmodeltab.ui
localmylist/database.h
localmylist/dynamicmodel/data.cpp
localmylist/dynamicmodel/data.h
localmylist/dynamicmodel/datamodel.cpp
localmylist/dynamicmodel/datamodel.h
localmylist/dynamicmodel/datatype.cpp
localmylist/dynamicmodel/datatype.h
localmylist/dynamicmodel/entry.cpp [new file with mode: 0644]
localmylist/dynamicmodel/entry.h [new file with mode: 0644]
localmylist/dynamicmodel/model.cpp
localmylist/dynamicmodel/model.h
localmylist/dynamicmodel/node.cpp
localmylist/dynamicmodel/node.h
localmylist/dynamicmodel/query.cpp
localmylist/dynamicmodel/queryparser.cpp [new file with mode: 0644]
localmylist/dynamicmodel/queryparser.h [new file with mode: 0644]
localmylist/dynamicmodel/typerelation.cpp
localmylist/dynamicmodel/typerelation.h
localmylist/dynamicmodel/types.cpp
localmylist/dynamicmodel/types.h
localmylist/localmylist.pro
localmylist/mylistnode.cpp
localmylist/sqlasyncquery.cpp
localmylist/sqlasyncquery.h
localmylist/sqlasyncqueryinternal.cpp
localmylist/sqlasyncqueryinternal.h
localmylist/sqlresultiteratorinterface.h
query-test/main.cpp
query-test/query-test.pro
query-test/queryparser.cpp [deleted file]
query-test/queryparser.h [deleted file]
query-test/tabledata.cpp

diff --git a/localmylist-management/dynamicmodelfiltermodel.cpp b/localmylist-management/dynamicmodelfiltermodel.cpp
new file mode 100644 (file)
index 0000000..f2ade69
--- /dev/null
@@ -0,0 +1,42 @@
+#include "dynamicmodelfiltermodel.h"
+
+#include "mylist.h"
+#include "settings.h"
+#include "dynamicmodel/model.h"
+#include "dynamicmodel/node.h"
+
+#include <QDebug>
+
+DynamicModelFilterModel::DynamicModelFilterModel(QObject *parent) :
+       QSortFilterProxyModel(parent)
+{
+       setFilterCaseSensitivity(Qt::CaseInsensitive);
+
+       connect(LocalMyList::instance()->database(), SIGNAL(configChanged()), this, SLOT(configChanged()));
+}
+
+LocalMyList::DynamicModel::Model *DynamicModelFilterModel::dynamicModel() const
+{
+       return qobject_cast<LocalMyList::DynamicModel::Model *>(sourceModel());
+}
+
+LocalMyList::DynamicModel::Node *DynamicModelFilterModel::node(const QModelIndex &idx) const
+{
+       if (!idx.isValid())
+               return 0;
+
+       return dynamicModel()->node(mapToSource(idx));
+}
+
+
+bool DynamicModelFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
+{
+       const QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
+
+       if (!source_parent.isValid())
+       {
+               return dynamicModel()->node(idx)->data()->matchesFilter(filterRegExp());
+       }
+
+       return true;
+}
diff --git a/localmylist-management/dynamicmodelfiltermodel.h b/localmylist-management/dynamicmodelfiltermodel.h
new file mode 100644 (file)
index 0000000..9d98d25
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef DYNAMICMODELFILTERMODEL_H
+#define DYNAMICMODELFILTERMODEL_H
+
+#include <QSortFilterProxyModel>
+
+namespace LocalMyList {
+namespace DynamicModel {
+class Model;
+class Node;
+}
+}
+
+class DynamicModelFilterModel : public QSortFilterProxyModel
+{
+       Q_OBJECT
+
+public:
+       explicit DynamicModelFilterModel(QObject *parent = 0);
+
+public slots:
+       LocalMyList::DynamicModel::Model *dynamicModel() const;
+       LocalMyList::DynamicModel::Node *node(const QModelIndex &idx) const;
+
+protected:
+       bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
+};
+
+#endif // DYNAMICMODELFILTERMODEL_H
diff --git a/localmylist-management/dynamicmodelitemdelegate.cpp b/localmylist-management/dynamicmodelitemdelegate.cpp
new file mode 100644 (file)
index 0000000..9d8ab4e
--- /dev/null
@@ -0,0 +1,63 @@
+#include "dynamicmodelitemdelegate.h"
+
+#include <QDoubleSpinBox>
+
+#include "dynamicmodel/data.h"
+#include "dynamicmodel/node.h"
+#include "dynamicmodelfiltermodel.h"
+
+DynamicModelItemDelegate::DynamicModelItemDelegate(QObject *parent) :
+       QStyledItemDelegate(parent)
+{
+}
+
+QWidget *DynamicModelItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+       using namespace LocalMyList;
+
+       if (!isVoteField(index))
+               return QStyledItemDelegate::createEditor(parent, option, index);
+
+       QDoubleSpinBox *ed = new QDoubleSpinBox(parent);
+
+       ed->setRange(0.99, 10.00);
+       ed->setDecimals(2);
+       ed->setSingleStep(0.50);
+       ed->setSpecialValueText(tr("No Vote/Revoke"));
+       return ed;
+}
+
+void DynamicModelItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
+{
+       if (!isVoteField(index))
+               return QStyledItemDelegate::setEditorData(editor, index);
+
+       double vote = index.data(Qt::EditRole).toDouble();
+
+       if (vote < 1.00 || vote > 10.00)
+               vote = 5.00;
+
+       QDoubleSpinBox *ed = qobject_cast<QDoubleSpinBox *>(editor);
+       ed->setValue(vote);
+}
+
+void DynamicModelItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
+{
+       if (!isVoteField(index))
+               QStyledItemDelegate::setModelData(editor, model, index);
+
+       QDoubleSpinBox *ed = qobject_cast<QDoubleSpinBox *>(editor);
+       model->setData(index, ed->value());
+}
+
+bool DynamicModelItemDelegate::isVoteField(const QModelIndex &index) const
+{
+       using namespace LocalMyList;
+       const DynamicModelFilterModel *model = qobject_cast<const DynamicModelFilterModel *>(index.model());
+       const DynamicModel::Node *node = model->node(index);
+
+       if (!node->data())
+               return false;
+
+       return node->data()->isVoteColumn(index.column());
+}
diff --git a/localmylist-management/dynamicmodelitemdelegate.h b/localmylist-management/dynamicmodelitemdelegate.h
new file mode 100644 (file)
index 0000000..cdd6624
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef DYNAMICMODELITEMDELEGATE_H
+#define DYNAMICMODELITEMDELEGATE_H
+
+#include <QStyledItemDelegate>
+
+class DynamicModelItemDelegate : public QStyledItemDelegate
+{
+       Q_OBJECT
+public:
+       explicit DynamicModelItemDelegate(QObject *parent = 0);
+
+       QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const;
+       void setEditorData(QWidget *editor, const QModelIndex &index ) const;
+       void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
+
+private:
+       bool isVoteField(const QModelIndex &index) const;
+
+};
+
+#endif // DYNAMICMODELITEMDELEGATE_H
diff --git a/localmylist-management/dynamicmodelview.cpp b/localmylist-management/dynamicmodelview.cpp
new file mode 100644 (file)
index 0000000..3ea75a0
--- /dev/null
@@ -0,0 +1,260 @@
+#include "dynamicmodelview.h"
+#include "mylist.h"
+#include "database.h"
+#include "dynamicmodel/model.h"
+#include "dynamicmodel/node.h"
+#include "dynamicmodel/datatype.h"
+#include "dynamicmodel/data.h"
+
+#include "dynamicmodelfiltermodel.h"
+
+#include <QHeaderView>
+#include <QMenu>
+#include <QDesktopServices>
+#include <QUrl>
+#include <QKeyEvent>
+
+DynamicModelView::DynamicModelView(QWidget *parent) :
+       QTreeView(parent)
+{
+       setContextMenuPolicy(Qt::CustomContextMenu);
+       connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showCustomContextMenu(QPoint)));
+
+       this->setExpandsOnDoubleClick(false);
+       connect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(doubleClick(QModelIndex)));
+
+       openAction = new QAction(tr("Open"), this);
+       connect(openAction, SIGNAL(triggered()), this, SLOT(requestOpenFile()));
+       openNextAction = new QAction(tr("Open Next"), this);
+       connect(openNextAction, SIGNAL(triggered()), this, SLOT(requestOpenFile()));
+       markAnimeWatchedAction = new QAction(tr("Mark Anime Watched"), this);
+       connect(markAnimeWatchedAction, SIGNAL(triggered()), this, SLOT(markAnimeWatched()));
+       markEpisodeWatchedAction = new QAction(tr("Mark Episode Watched"), this);
+       connect(markEpisodeWatchedAction, SIGNAL(triggered()), this, SLOT(markEpisodeWatched()));
+       markFileWatchedAction = new QAction(tr("Mark Watched"), this);
+       connect(markFileWatchedAction, SIGNAL(triggered()), this, SLOT(markFileWatched()));
+       markFileUnwatchedAction = new QAction(tr("Mark Unwatched"), this);
+       connect(markFileUnwatchedAction, SIGNAL(triggered()), this, SLOT(markFileUnwatched()));
+       aniDBLinkAction = new QAction(tr("Open AniDB Page"), this);
+       connect(aniDBLinkAction, SIGNAL(triggered()), this, SLOT(openAnidbPage()));
+       renameFilesAction = new QAction(tr("Rename Files Related to Entry"), this);
+       connect(renameFilesAction, SIGNAL(triggered()), this, SLOT(requestFileRename()));
+       renameTestAction = new QAction(tr("Use For Rename Testing"), this);
+       connect(renameTestAction, SIGNAL(triggered()), this, SLOT(renameTest()));
+       requestDataAction = new QAction(tr("Request Data for this Entry"), this);
+       connect(requestDataAction, SIGNAL(triggered()), this, SLOT(requestData()));
+       removeFileLocationAction = new QAction(tr("Remove this File Location"), this);
+       connect(removeFileLocationAction, SIGNAL(triggered()), this, SLOT(removeFileLocation()));
+
+       if (!LocalMyList::MyList::isUdpClientAvailable())
+       {
+               renameFilesAction->setDisabled(true);
+               renameTestAction->setDisabled(true);
+       }
+}
+
+void DynamicModelView::keyPressEvent(QKeyEvent *event)
+{
+       if (event->key() == Qt::Key_Return && currentIndex().isValid())
+       {
+               emit openFileRequested(currentIndex());
+               event->accept();
+       }
+       else
+       {
+               QTreeView::keyPressEvent(event);
+       }
+}
+
+DynamicModelFilterModel *DynamicModelView::dynamicModelFilterModel() const
+{
+       return qobject_cast<DynamicModelFilterModel *>(model());
+}
+
+void DynamicModelView::showCustomContextMenu(const QPoint &pos)
+{
+       using namespace LocalMyList;
+
+       const QModelIndex idx = indexAt(pos);
+       if (!idx.isValid())
+               return;
+
+       DynamicModel::Node *node = dynamicModelFilterModel()->node(idx);
+
+       QList<QAction *> actions;
+
+       if (node->data()->type()->name() == "anime")
+       {
+               aniDBLinkAction->setText(tr("Open AniDB Page (%1%2)").arg('a').arg(node->id()));
+               actions << aniDBLinkAction
+                               << openNextAction
+                               << markAnimeWatchedAction
+                               << renameFilesAction
+                               << requestDataAction;
+       }
+       else if (node->data()->type()->name() == "episode")
+       {
+               aniDBLinkAction->setText(tr("Open AniDB Page (%1%2)").arg('e').arg(node->id()));
+               actions << aniDBLinkAction
+                               << openAction
+                               << markEpisodeWatchedAction
+                               << renameFilesAction
+                               << requestDataAction;
+       }
+       else if (node->data()->type()->name() == "file")
+       {
+               aniDBLinkAction->setText(tr("Open AniDB Page (%1%2)").arg('f').arg(node->id()));
+               actions << aniDBLinkAction
+                               << openAction
+                               << markFileWatchedAction
+                               << markFileUnwatchedAction
+                               << renameTestAction
+                               << renameFilesAction
+                               << requestDataAction;
+       }
+       else if (node->data()->type()->name() == "file_location")
+       {
+               aniDBLinkAction->setText(tr("Open AniDB Page (%1%2) (%3%4)")
+                                                                .arg('f').arg(node->parent()->id())
+                                                                .arg("LocationId").arg(node->id()));
+               actions << aniDBLinkAction
+                               << renameTestAction
+                               << renameFilesAction
+                               << removeFileLocationAction;
+       }
+
+       if(actions.isEmpty())
+               return;
+
+       customContextMenuIndex = idx;
+       QMenu::exec(actions, viewport()->mapToGlobal(pos));
+       customContextMenuIndex = QModelIndex();
+}
+
+void DynamicModelView::doubleClick(const QModelIndex &index)
+{
+       if (!(model()->flags(index) & Qt::ItemIsEditable))
+               emit openFileRequested(index);
+}
+
+void DynamicModelView::requestOpenFile()
+{
+       emit openFileRequested(customContextMenuIndex);
+}
+
+void DynamicModelView::markAnimeWatched()
+{
+       using namespace LocalMyList;
+
+       DynamicModel::Node *node = dynamicModelFilterModel()->node(customContextMenuIndex);
+
+       if (node->data()->type()->name() != "anime")
+               return;
+
+       PendingMyListUpdate pmu;
+       pmu.aid = node->id();
+
+       pmu.setMyWatched = true;
+       pmu.myWatched = QDateTime::currentDateTime();
+
+       MyList::instance()->database()->addPendingMyListUpdate(pmu);
+}
+
+void DynamicModelView::markEpisodeWatched()
+{
+       using namespace LocalMyList;
+
+       DynamicModel::Node *node = dynamicModelFilterModel()->node(customContextMenuIndex);
+
+       if (node->data()->type()->name() != "episode")
+               return;
+
+       const auto data = static_cast<DynamicModel::EpisodeData *>(node->data());
+
+       PendingMyListUpdate pmu;
+       pmu.aid = data->episodeData.aid;
+       pmu.epno = data->episodeData.epno;
+       pmu.eptype = data->episodeData.type;
+
+       pmu.setMyWatched = true;
+       pmu.myWatched = QDateTime::currentDateTime();
+
+       MyList::instance()->database()->addPendingMyListUpdate(pmu);
+}
+
+void DynamicModelView::markFileWatched()
+{
+       using namespace LocalMyList;
+
+       DynamicModel::Node *node = dynamicModelFilterModel()->node(customContextMenuIndex);
+
+       if (node->data()->type()->name() != "file")
+               return;
+
+       MyList::instance()->markWatched(node->id());
+}
+
+void DynamicModelView::markFileUnwatched()
+{
+       using namespace LocalMyList;
+
+       DynamicModel::Node *node = dynamicModelFilterModel()->node(customContextMenuIndex);
+
+       if (node->data()->type()->name() != "file")
+               return;
+
+       MyList::instance()->markUnwatched(node->id());
+}
+
+void DynamicModelView::openAnidbPage()
+{
+       using namespace LocalMyList;
+
+       static const QString aniDBUrlBase = "http://anidb.net/%1%2";
+       DynamicModel::Node *node = dynamicModelFilterModel()->node(customContextMenuIndex);
+
+       if (node->data()->type()->name() == "anime")
+               QDesktopServices::openUrl(QUrl(aniDBUrlBase.arg('a').arg(node->id())));
+       else if (node->data()->type()->name() == "episode")
+               QDesktopServices::openUrl(QUrl(aniDBUrlBase.arg('e').arg(node->id())));
+       else if (node->data()->type()->name() == "file")
+               QDesktopServices::openUrl(QUrl(aniDBUrlBase.arg('f').arg(node->id())));
+       else if (node->data()->type()->name() == "file_location")
+               QDesktopServices::openUrl(QUrl(aniDBUrlBase.arg('f').arg(node->parent()->id())));
+}
+
+void DynamicModelView::requestFileRename()
+{
+       emit renameFilesRequested(customContextMenuIndex);
+}
+
+void DynamicModelView::renameTest()
+{
+       using namespace LocalMyList;
+       int id;
+       DynamicModel::Node *node = dynamicModelFilterModel()->node(customContextMenuIndex);
+       if (node->data()->type()->name() == "file")
+       {
+               id = node->id();
+       }
+       else if (node->data()->type()->name() == "file_location")
+       {
+               const auto data = static_cast<DynamicModel::FileLocationData *>(node->data());
+               id = data->fileLocationData.fid;
+       }
+
+       if (id)
+               emit renameTest(id);
+}
+
+void DynamicModelView::requestData()
+{
+       emit dataRequested(customContextMenuIndex);
+}
+
+void DynamicModelView::removeFileLocation()
+{
+       int id = dynamicModelFilterModel()->node(customContextMenuIndex)->id();
+       if (id)
+               emit removeFileLocationRequested(id);
+}
diff --git a/localmylist-management/dynamicmodelview.h b/localmylist-management/dynamicmodelview.h
new file mode 100644 (file)
index 0000000..2ec824e
--- /dev/null
@@ -0,0 +1,63 @@
+#ifndef DYNAMICMODELVIEW_H
+#define DYNAMICMODELVIEW_H
+
+#include <QTreeView>
+
+namespace LocalMyList {
+namespace DynamicModel {
+class Model;
+class Node;
+}
+}
+
+class DynamicModelFilterModel;
+
+class DynamicModelView : public QTreeView
+{
+       Q_OBJECT
+public:
+       explicit DynamicModelView(QWidget *parent = 0);
+
+protected:
+       void keyPressEvent(QKeyEvent *event);
+
+signals:
+       void openFileRequested(const QModelIndex &index);
+       void renameFilesRequested(const QModelIndex &index);
+       void dataRequested(const QModelIndex &index);
+       void renameTest(int fid);
+       void removeFileLocationRequested(int locationId);
+
+private slots:
+       DynamicModelFilterModel *dynamicModelFilterModel() const;
+       void showCustomContextMenu(const QPoint &pos);
+       void doubleClick(const QModelIndex &index);
+       void requestOpenFile();
+       void markAnimeWatched();
+       void markEpisodeWatched();
+       void markFileWatched();
+       void markFileUnwatched();
+       void openAnidbPage();
+       void requestFileRename();
+       void renameTest();
+       void requestData();
+       void removeFileLocation();
+
+private:
+       QModelIndex customContextMenuIndex;
+
+
+       QAction *openAction;
+       QAction *openNextAction;
+       QAction *markAnimeWatchedAction;
+       QAction *markEpisodeWatchedAction;
+       QAction *markFileWatchedAction;
+       QAction *markFileUnwatchedAction;
+       QAction *aniDBLinkAction;
+       QAction *renameTestAction;
+       QAction *renameFilesAction;
+       QAction *requestDataAction;
+       QAction *removeFileLocationAction;
+};
+
+#endif // DYNAMICMODELVIEW_H
index f3a1948ebf9dd88be3696a02b917ab7d732db94b..218a1afdd5e825ae27f8cf1344f86471b7fc5b53 100644 (file)
@@ -32,7 +32,10 @@ SOURCES += main.cpp\
        aniaddsyntaxhighlighter.cpp \
        settingsdialog.cpp \
        codeeditor.cpp \
-    tabs/dynamicmodeltab.cpp
+       tabs/dynamicmodeltab.cpp \
+       dynamicmodelfiltermodel.cpp \
+       dynamicmodelview.cpp \
+       dynamicmodelitemdelegate.cpp
 
 HEADERS += mainwindow.h \
        databaseconnectiondialog.h \
@@ -55,7 +58,10 @@ HEADERS += mainwindow.h \
        aniaddsyntaxhighlighter.h \
        settingsdialog.h \
        codeeditor.h \
-    tabs/dynamicmodeltab.h
+       tabs/dynamicmodeltab.h \
+       dynamicmodelfiltermodel.h \
+       dynamicmodelview.h \
+       dynamicmodelitemdelegate.h
 
 FORMS += mainwindow.ui \
        databaseconnectiondialog.ui \
@@ -67,7 +73,7 @@ FORMS += mainwindow.ui \
        tabs/pendingrequesttab.ui \
        tabs/databaselogtab.ui \
        tabs/clientlogtab.ui \
-    tabs/dynamicmodeltab.ui
+       tabs/dynamicmodeltab.ui
 
 include(../localmylist.pri)
 include(qtsingleapplication/qtsingleapplication.pri)
@@ -80,5 +86,11 @@ include(qtsingleapplication/qtsingleapplication.pri)
 } else {
        DEFINES += LOCALMYLIST_NO_ANIDBUDPCLIENT
 }
+
+# Why is this required with Qt5.4?
+win32 {
+       LIBS += -ladvapi32 -lshell32
+}
+
 target.path = $${PREFIX}/bin
 INSTALLS += target
index e016f0523d81a38c50a24fb6c29440ff1eb57be6..33b5bb486dd60c896b637a17f5e840ecb79071fd 100644 (file)
@@ -2,12 +2,15 @@
 #include "ui_dynamicmodeltab.h"
 
 #include <QMessageBox>
+#include <QDesktopServices>
+#include <QUrl>
+#include <QSqlError>
 
 #include "mainwindow.h"
 #include "database.h"
 #include "mylist.h"
-#include "mylistfiltermodel.h"
-#include "mylistitemdelegate.h"
+#include "dynamicmodelfiltermodel.h"
+#include "dynamicmodelitemdelegate.h"
 
 #include "dynamicmodel/model.h"
 #include "dynamicmodel/datamodel.h"
@@ -16,6 +19,7 @@
 
 #include <QDebug>
 
+using namespace LocalMyList;
 using namespace LocalMyList::DynamicModel;
 
 DynamicModelTab::DynamicModelTab(QWidget *parent) :
@@ -46,28 +50,30 @@ QString DynamicModelTab::name()
 
 void DynamicModelTab::init()
 {
+       // Model must be deleted before the DataModel is uses.
+       model = new Model(this);
+
+       // TODO: move outside the tab as it should be useful globally
        dataModel = new DataModel(this);
+       dataModel->registerDataType(new ColumnType);
        dataModel->registerDataType(new AnimeType);
        dataModel->registerDataType(new EpisodeType);
        dataModel->registerDataType(new FileType);
        dataModel->registerDataType(new FileLocationType);
        dataModel->registerDataType(new AnimeTitleType);
-       dataModel->registerTypeRelation(new RootAnimeRelation(this));
-       dataModel->registerTypeRelation(new RootEpisodeRelation(this));
-       dataModel->registerTypeRelation(new AnimeEpisodeRelation(this));
-       dataModel->registerTypeRelation(new EpisodeFileRelation(this));
-       dataModel->registerTypeRelation(new FileFileLocationRelation(this));
-       dataModel->registerTypeRelation(new RootAnimeTitleRelation(this));
-       dataModel->registerTypeRelation(new AnimeTitleAnimeRelation(this));
-       dataModel->registerTypeRelation(new AnimeTitleEpisodeRelation(this));
-       dataModel->registerTypeRelation(new AnimeAnimeTitleRelation(this));
+       dataModel->registerTypeRelation(new ForeignKeyRelation("anime", "episode", "aid"));
+       dataModel->registerTypeRelation(new ForeignKeyRelation("episode", "anime", "aid"));
+       dataModel->registerTypeRelation(new ForeignKeyRelation("anime", "file", "aid"));
+       dataModel->registerTypeRelation(new ForeignKeyRelation("file", "anime", "aid"));
+       dataModel->registerTypeRelation(new ForeignKeyRelation("episode", "file", "eid"));
+       dataModel->registerTypeRelation(new ForeignKeyRelation("file", "episode", "eid"));
+       dataModel->registerTypeRelation(new ForeignKeyRelation("file", "file_location", "fid"));
 
-       model = new Model(this);
 
-       myListFilterModel = new MyListFilterModel(this);
-       myListFilterModel->setSourceModel(model);
-       ui->myListView->setModel(myListFilterModel);
-       ui->myListView->setItemDelegate(new MyListItemDelegate(ui->myListView));
+       dynamicModelFilterModel = new DynamicModelFilterModel(this);
+       dynamicModelFilterModel->setSourceModel(model);
+       ui->myListView->setModel(dynamicModelFilterModel);
+       ui->myListView->setItemDelegate(new DynamicModelItemDelegate(ui->myListView));
 
 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
        ui->myListView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
@@ -87,9 +93,8 @@ void DynamicModelTab::init()
        connect(ui->filterInput, SIGNAL(textChanged(QString)), this, SLOT(currentSelectionChanged()));
 
        connect(model, SIGNAL(queryChanged(QString)), ui->modelQuery, SLOT(setText(QString)));
-       //model->setQuery("anime|episode|file|file_location");
-       Query q(dataModel);
-       q.parse("anime|episode");
+       QueryParser q(dataModel);
+       q.parse("episode.epno/anime/...");
 
        if (!q.isValid()) {
                qDebug() << "Invalid query" << q.errorString();
@@ -120,19 +125,148 @@ void DynamicModelTab::changeEvent(QEvent *e)
        }
 }
 
+
+void DynamicModelTab::on_myListView_openFileRequested(const QModelIndex &index)
+{
+       DynamicModel::Node *node = dynamicModelFilterModel->node(index);
+
+       if (!node->id())
+               return;
+
+       OpenFileData data;
+
+       if (node->data()->type()->name() == "anime")
+       {
+               data = MyList::instance()->database()->firstUnwatchedByAid(node->id());
+       }
+       else if (node->data()->type()->name() == "episode")
+       {
+               data = MyList::instance()->database()->openFileByEid(node->id());
+       }
+       else if (node->data()->type()->name() == "file")
+       {
+               data = MyList::instance()->database()->openFile(node->id());
+       }
+       else
+       {
+               return;
+       }
+
+       if (!data.fid)
+       {
+               mainWindow()->showMessage(tr("No file found."));
+               return;
+       }
+
+       QDesktopServices::openUrl(QUrl("file:///" + data.path, QUrl::TolerantMode));
+       mainWindow()->showMessage(tr("Openieng file: %1").arg(data.path));
+
+}
+
+void DynamicModelTab::on_myListView_renameFilesRequested(const QModelIndex &index)
+{
+       DynamicModel::Node *node = dynamicModelFilterModel->node(index);
+
+       if (!node->id())
+               return;
+
+       QString path;
+       QSqlQuery q(MyList::instance()->database()->connection());
+
+       QChar typeLetter;
+       if (node->data()->type()->name() == "anime")
+       {
+               q.prepare(
+                       "UPDATE file_location fl SET renamed = NULL, failed_rename = false "
+                       "       FROM file f "
+                       "       WHERE f.fid = fl.fid AND f.aid = :aid");
+               q.bindValue(":aid", node->id());
+
+               typeLetter = 'a';
+       }
+       else if (node->data()->type()->name() == "episode")
+       {
+               q.prepare(
+                       "UPDATE file_location fl SET renamed = NULL, failed_rename = false "
+                       "       FROM file f "
+                       "       WHERE f.fid = fl.fid AND f.eid = :eid");
+               q.bindValue(":eid", node->id());
+
+               typeLetter = 'e';
+       }
+       else if (node->data()->type()->name() == "file")
+       {
+               q.prepare(
+                       "UPDATE file_location fl SET renamed = NULL, failed_rename = false "
+                       "       WHERE fl.fid = :fid");
+               q.bindValue(":fid", node->id());
+
+               typeLetter = 'f';
+       }
+       else if (node->data()->type()->name() == "file_location")
+       {
+               q.prepare(
+                       "UPDATE file_location fl SET renamed = NULL, failed_rename = false "
+                       "       WHERE fl.location_id = :locationId");
+               q.bindValue(":locationId", node->id());
+
+               typeLetter = 'l';
+       }
+       else
+       {
+               return;
+       }
+
+       if (!q.exec())
+       {
+               qDebug() << q.lastError();
+               return;
+       }
+
+       mainWindow()->showMessage(tr("Files for %1%2 scheduled for rename").arg(typeLetter).arg(node->id()));
+}
+
+void DynamicModelTab::on_myListView_dataRequested(const QModelIndex &index)
+{
+       DynamicModel::Node *node = dynamicModelFilterModel->node(index);
+
+       if (!node->id())
+               return;
+
+       PendingRequest r;
+
+       if (node->data()->type()->name() == "anime")
+               r.aid = node->id();
+       else if (node->data()->type()->name() == "episode")
+               r.eid = node->id();
+       else if (node->data()->type()->name() == "file")
+               r.fid = node->id();
+       else
+               return;
+
+       MyList::instance()->database()->addRequest(r);
+}
+
+void DynamicModelTab::on_myListView_removeFileLocationRequested(int id)
+{
+       Q_UNUSED(id);
+       //myListModel()->removeFileLocation(id);
+}
+
+
 void DynamicModelTab::on_filterInput_textChanged(const QString &filter)
 {
        switch (ui->filterType->currentIndex())
        {
                case 1:
-                       myListFilterModel->setFilterWildcard(filter);
+                       dynamicModelFilterModel->setFilterWildcard(filter);
                break;
                case 2:
-                       myListFilterModel->setFilterRegExp(filter);
+                       dynamicModelFilterModel->setFilterRegExp(filter);
                break;
                case 0:
                default:
-                       myListFilterModel->setFilterFixedString(filter);
+                       dynamicModelFilterModel->setFilterFixedString(filter);
                break;
        }
 }
@@ -144,58 +278,51 @@ void DynamicModelTab::on_filterType_currentIndexChanged(int)
 
 void DynamicModelTab::on_filterInput_keyUpPressed()
 {
-       selectedRow = qMax(-1, selectedRow - 1);
-       updateSelection();
+       const int rowCount{ui->myListView->model()->rowCount()};
 
-}
+       if (!rowCount)
+               return;
 
-void DynamicModelTab::on_filterInput_keyDownPressed()
-{
-       int newSelectedRow = qMin(model->rowCount() - 1, selectedRow + 1);
+       const QModelIndex currentIdx{ui->myListView->selectionModel()->currentIndex()};
+       QModelIndex nextIdx{ui->myListView->model()->index(currentIdx.row() - 1, 0)};
 
-       if (selectedRow == newSelectedRow)
-               return;
+       if (!nextIdx.isValid())
+               nextIdx = ui->myListView->model()->index(rowCount - 1, 0);
 
-       selectedRow = newSelectedRow;
-       updateSelection();
+       ui->myListView->selectionModel()->
+                       setCurrentIndex(nextIdx, QItemSelectionModel::ClearAndSelect
+                                                       | QItemSelectionModel::Rows);
 }
 
-void DynamicModelTab::on_filterInput_returnPressed()
+void DynamicModelTab::on_filterInput_keyDownPressed()
 {
-       if (selectedRow < 0)
+       if (!ui->myListView->model()->rowCount())
                return;
 
-       const QModelIndex idx = myListFilterModel->index(selectedRow, 0);
-//     on_myListView_openFileRequested(idx);
-}
+       const QModelIndex currentIdx{ui->myListView->selectionModel()->currentIndex()};
+       QModelIndex nextIdx{ui->myListView->model()->index(currentIdx.row() + 1, 0)};
 
-void DynamicModelTab::currentSelectionChanged(const QModelIndex &current, const QModelIndex &)
-{
-       selectedRow = current.row();
-}
+       if (!nextIdx.isValid())
+               nextIdx = ui->myListView->model()->index(0, 0);
 
-void DynamicModelTab::currentSelectionChanged()
-{
-       selectedRow = -1;
+       ui->myListView->selectionModel()->
+                       setCurrentIndex(nextIdx, QItemSelectionModel::ClearAndSelect
+                                                       | QItemSelectionModel::Rows);
 }
 
-void DynamicModelTab::updateSelection()
+void DynamicModelTab::on_filterInput_returnPressed()
 {
-       if (selectedRow < 0)
-       {
-               ui->myListView->selectionModel()->clear();
+       const QModelIndex idx{ui->myListView->selectionModel()->currentIndex()};
+
+       if (!idx.isValid())
                return;
-       }
 
-       const QModelIndex idx = myListFilterModel->index(selectedRow, 0);
-       ui->myListView->selectionModel()->
-                       setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect
-                                                       | QItemSelectionModel::Rows);
+       on_myListView_openFileRequested(idx);
 }
 
 void DynamicModelTab::on_modelQuery_returnPressed()
 {
-       Query q(dataModel);
+       QueryParser q(dataModel);
        if (q.parse(ui->modelQuery->text()))
        {
                model->setQuery(q);
index 9d4731c1ffb39b8c9ac2836945a894b391379ca4..0f3c17632baef648ecf517e59078e8c7dbc74f1b 100644 (file)
@@ -4,7 +4,7 @@
 #include "abstracttab.h"
 #include <QModelIndex>
 
-class MyListFilterModel;
+class DynamicModelFilterModel;
 
 namespace LocalMyList {
 namespace DynamicModel {
@@ -37,6 +37,11 @@ protected:
        void changeEvent(QEvent *e);
 
 private slots:
+       void on_myListView_openFileRequested(const QModelIndex &index);
+       void on_myListView_renameFilesRequested(const QModelIndex &index);
+       void on_myListView_dataRequested(const QModelIndex &index);
+       void on_myListView_removeFileLocationRequested(int id);
+
        void on_filterInput_textChanged(const QString &filter);
        void on_filterType_currentIndexChanged(int);
 
@@ -44,23 +49,16 @@ private slots:
        void on_filterInput_keyDownPressed();
        void on_filterInput_returnPressed();
 
-       void currentSelectionChanged(const QModelIndex &current, const QModelIndex &previous);
-       void currentSelectionChanged();
-
        void on_modelQuery_returnPressed();
 
        void on_modelQueryButton_clicked();
 
 private:
-       void updateSelection();
-
        Ui::DynamicModelTab *ui;
 
-       MyListFilterModel *myListFilterModel;
+       DynamicModelFilterModel *dynamicModelFilterModel;
        LocalMyList::DynamicModel::DataModel *dataModel;
        LocalMyList::DynamicModel::Model *model;
-
-       int selectedRow;
 };
 
 #endif // DYNAMICMODELTAB_H
index 8cf1db4cf6caf3e8c3706a863cb4bf224d5057e2..c06b755039b2905fb6a96bccb992f40f86bf99f6 100644 (file)
@@ -20,6 +20,9 @@
    <property name="rightMargin">
     <number>0</number>
    </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
    <item>
     <layout class="QGridLayout" name="gridLayout">
      <item row="0" column="0">
@@ -45,7 +48,7 @@
     </layout>
    </item>
    <item>
-    <widget class="MyListView" name="myListView"/>
+    <widget class="DynamicModelView" name="myListView"/>
    </item>
   </layout>
  </widget>
@@ -56,9 +59,9 @@
    <header>filterlineedit.h</header>
   </customwidget>
   <customwidget>
-   <class>MyListView</class>
+   <class>DynamicModelView</class>
    <extends>QTreeView</extends>
-   <header>mylistview.h</header>
+   <header>dynamicmodelview.h</header>
   </customwidget>
  </customwidgets>
  <resources/>
index de7e97a24e9f5581581f3837e77c7bb52c0a98aa..6377008a6f6bcf8b0817b83e6a55488beedf8c87 100644 (file)
@@ -25,6 +25,7 @@ public:
        QVariant value(int index) const { return q.value(index); }
        QVariant value(const QString &name) const { return q.record().value(name); }
        int indexOf(const QString &name ) const { return q.record().indexOf(name); }
+       QString fieldName(int i) const override {return q.record().fieldName(i); }
 private:
        QSqlQuery &q;
 };
index 0b060318e5a06964126bf82bab666aaf0313184b..5b333c74b1f3e6e99590ba287b91d735785948bb 100644 (file)
@@ -1,7 +1,8 @@
-#include "data.h"
+#include "dynamicmodel/data.h"
 
-#include "node.h"
-#include "datatype.h"
+#include "dynamicmodel/node.h"
+#include "dynamicmodel/datatype.h"
+#include "mylist.h"
 
 #include <QDebug>
 
@@ -35,13 +36,43 @@ Data::~Data()
        Q_ASSERT(references.isEmpty());
 }
 
-QVariant Data::data(int row, int role) const
+QVariant Data::primaryValue() const
 {
-       Q_UNUSED(row);
+       return id();
+}
+
+QVariant Data::data(int column, int role) const
+{
+       Q_UNUSED(column);
        Q_UNUSED(role);
        return QVariant();
 }
 
+bool Data::setData(int column, const QVariant &data, int role)
+{
+       Q_UNUSED(column);
+       Q_UNUSED(data);
+       Q_UNUSED(role);
+       return false;
+}
+
+Qt::ItemFlags Data::flags(int column) const
+{
+       Q_UNUSED(column);
+       return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+}
+
+bool Data::matchesFilter(const QRegExp &filter) const
+{
+       return data(0, Qt::DisplayRole).toString().contains(filter);
+}
+
+bool Data::isVoteColumn(int column) const
+{
+       Q_UNUSED(column);
+       return false;
+}
+
 void Data::ref(Node *node)
 {
        Q_ASSERT(!references.contains(node));
@@ -64,22 +95,40 @@ void Data::deref(Node *node)
 
 void Data::updated(Data *oldData)
 {
-       Q_UNUSED(oldData);
-       foreach (Node *node, references)
+       for (Node *node : references)
        {
                Q_ASSERT_X(node->parent(), "dynamicmodel", "Updating node without parent");
-               node->updated(UpdateOperation);
+               node->updated(oldData);
 //             node->parent()->childUpdate(node, oldData, UpdateOperation);
        }
 }
 
-void Data::added(Data *newData)
+ColumnData::ColumnData(DataType *dataType) : Data{dataType}
 {
-       foreach (Node *node, references)
-       {
-               if (node->childDataType() == newData->type())
-                       node->childAdded(newData);
-       }
+
+}
+
+ColumnData &ColumnData::operator=(ColumnData &other)
+{
+       value = other.value;
+       return *this;
+}
+
+int ColumnData::id() const
+{
+       return 0;
+}
+
+QVariant ColumnData::primaryValue() const
+{
+       return value;
+}
+
+QVariant ColumnData::data(int column, int role) const
+{
+       if (column != 0) return {};
+       if (role != Qt::DisplayRole) return {};
+       return value;
 }
 
 AnimeData::AnimeData(DataType *dataType) : Data(dataType)
@@ -101,8 +150,20 @@ int AnimeData::id() const
        return animeData.aid;
 }
 
+Qt::ItemFlags AnimeData::flags(int column) const
+{
+       Qt::ItemFlags flags = Data::flags(column);
+       if (column == 3)
+               flags |= Qt::ItemIsEditable;
+       return flags;
+}
+
+
 QVariant AnimeData::data(int column, int role) const
 {
+       static const QString epCountString{"%1%3 of %2%4"};
+       static const QString unknownEpCountString{"%1%3 of (%2%4)"};
+       static const QString specialsCountString{"+%1"};
        switch (role)
        {
                case Qt::DisplayRole:
@@ -111,13 +172,13 @@ QVariant AnimeData::data(int column, int role) const
                                case 0:
                                        return animeData.titleRomaji;
                                case 1:
-                                       if (animeData.totalEpisodeCount)
-                                               return QString("%1 of %2")
-                                                               .arg(episodesInMyList).arg(animeData.totalEpisodeCount);
-                                       return QString("%1 of (%2)")
+                                       return (animeData.totalEpisodeCount ? epCountString : unknownEpCountString)
                                                        .arg(episodesInMyList)
-                                                       .arg(qMax(animeData.highestEpno,
-                                                                       episodesInMyList));
+                                                       .arg(animeData.totalEpisodeCount
+                                                                ? animeData.totalEpisodeCount
+                                                                : qMax(animeData.highestEpno, episodesInMyList))
+                                                       .arg(specialsInMyList ? specialsCountString.arg(specialsInMyList) : "")
+                                                       .arg("");
                                case 2:
                                        if (animeData.rating < 1)
                                                return "n/a";
@@ -127,8 +188,10 @@ QVariant AnimeData::data(int column, int role) const
                                                return "n/a";
                                        return QString::number(animeData.myVote, 'f', 2);
                                case 4:
-                                       return QString("%1 of %2").arg(watchedEpisodes)
-                                                       .arg(episodesInMyList);
+                                       return epCountString.arg(watchedEpisodes)
+                                                       .arg(episodesInMyList)
+                                                       .arg(specialsInMyList ? specialsCountString.arg(watchedSpecials) : "")
+                                                       .arg(specialsInMyList ? specialsCountString.arg(specialsInMyList) : "");
                                case 5:
                                        return stateIdToState(myState);
                        }
@@ -158,6 +221,51 @@ QVariant AnimeData::data(int column, int role) const
        return QVariant();
 }
 
+bool AnimeData::setData(int column, const QVariant &data, int role)
+{
+       if (role != Qt::EditRole)
+               return false;
+
+       switch (column)
+       {
+               case 3:
+               {
+                       double vote = data.toDouble();
+
+                       if (qFuzzyCompare(animeData.myVote, vote))
+                               return false;
+
+                       if (vote < 1.0 || vote > 10.0)
+                               vote = 0;
+
+                       animeData.myVote = vote;
+
+                       MyList::instance()->voteAnime(animeData.aid, vote);
+
+                       return true;
+               }
+       }
+       return false;
+}
+
+bool AnimeData::matchesFilter(const QRegExp &filter) const
+{
+       if (Data::matchesFilter(filter))
+               return true;
+
+       for (auto &&title : alternateTitles)
+       {
+               if (title.contains(filter))
+                       return true;
+       }
+       return false;
+}
+
+bool AnimeData::isVoteColumn(int column) const
+{
+       return column == 3;
+}
+
 // ==========================================================
 
 EpisodeData::EpisodeData(DataType *dataType) : Data(dataType)
@@ -230,6 +338,11 @@ QVariant EpisodeData::data(int column, int role) const
        return QVariant();
 }
 
+bool EpisodeData::isVoteColumn(int column) const
+{
+       return column == 3;
+}
+
 FileData::FileData(DataType *dataType) : Data(dataType)
 {
 }
@@ -308,7 +421,7 @@ QVariant FileLocationData::data(int column, int role) const
                        if (!fileLocationData.renamed.isValid())
                                return QObject::tr("No");
                        if (fileLocationData.failedRename)
-                               return QObject::tr("Rename failed");
+                               return QObject::tr("Rename failed: %1").arg(fileLocationData.renameError);
                        return QObject::tr("Yes, on %1").arg(fileLocationData.renamed.toString());
        }
        return QVariant();
index aa2a92ecdcd9dfd95c7d551fbaa48000b4528dd7..77a4ded8eed20bd7e9ed388a5814290302a16fd0 100644 (file)
@@ -22,7 +22,13 @@ public:
        virtual ~Data();
 
        virtual int id() const = 0;
-       virtual QVariant data(int row, int role) const;
+       virtual QVariant primaryValue() const;
+       virtual Qt::ItemFlags flags(int column) const;
+       virtual QVariant data(int column, int role) const;
+       virtual bool setData(int column, const QVariant &data, int role);
+       virtual bool matchesFilter(const QRegExp &filter) const;
+
+       virtual bool isVoteColumn(int column) const;
 
        DataType *type() const { return m_type; }
        // Referencing
@@ -30,26 +36,46 @@ public:
        void deref(Node *node);
 
        void updated(Data *oldData);
-       void added(Data *newData);
 
 private:
        NodeList references;
        DataType * const m_type;
 };
 
+class LOCALMYLISTSHARED_EXPORT ColumnData : public Data
+{
+public:
+       ColumnData(DataType *dataType);
+       ColumnData &operator=(ColumnData &other);
+
+       int id() const override;
+       QVariant primaryValue() const override;
+       QVariant data(int column, int role) const override;
+
+       QVariant value;
+};
+
 class LOCALMYLISTSHARED_EXPORT AnimeData : public Data
 {
 public:
        AnimeData(DataType *dataType);
        AnimeData &operator=(AnimeData &other);
 
-       int id() const;
-       QVariant data(int column, int role) const;
+       int id() const override;
+       virtual Qt::ItemFlags flags(int column) const override;
+       QVariant data(int column, int role) const override;
+       bool setData(int column, const QVariant &data, int role) override;
+       bool matchesFilter(const QRegExp &filter) const override;
+
+       bool isVoteColumn(int column) const override;
 
        Anime animeData;
        int episodesInMyList;
+       int specialsInMyList;
        int watchedEpisodes;
+       int watchedSpecials;
        int myState;
+       QList<QString> alternateTitles;
 };
 
 class LOCALMYLISTSHARED_EXPORT EpisodeData : public Data
@@ -58,8 +84,10 @@ public:
        EpisodeData(DataType *dataType);
        EpisodeData &operator=(EpisodeData &other);
 
-       int id() const;
-       QVariant data(int column, int role) const;
+       int id() const override;
+       QVariant data(int column, int role) const override;
+
+       bool isVoteColumn(int column) const override;
 
        Episode episodeData;
        QDateTime watchedDate;
@@ -73,8 +101,8 @@ public:
        FileData(DataType *dataType);
        FileData &operator=(FileData &other);
 
-       int id() const;
-       QVariant data(int column, int role) const;
+       int id() const override;
+       QVariant data(int column, int role) const override;
 
        File fileData;
 };
@@ -85,8 +113,8 @@ public:
        FileLocationData(DataType *dataType);
        FileLocationData &operator=(FileLocationData &other);
 
-       int id() const;
-       QVariant data(int column, int role) const;
+       int id() const override;
+       QVariant data(int column, int role) const override;
 
        FileLocation fileLocationData;
        QString hostName;
@@ -98,8 +126,8 @@ public:
        AnimeTitleData(DataType *dataType);
        AnimeTitleData &operator=(AnimeTitleData &other);
 
-       int id() const;
-       QVariant data(int column, int role) const;
+       int id() const override;
+       QVariant data(int column, int role) const override;
 
        AnimeTitle animeTitleData;
 };
index 3dde9e8c9cb735823051b1d266d4faa82414bd62..f3ad417832628a40caf4fe85032e25007dd6a8e8 100644 (file)
@@ -1,7 +1,9 @@
-#include "datamodel.h"
+#include "dynamicmodel/datamodel.h"
 
-#include "datatype.h"
-#include "typerelation.h"
+#include "dynamicmodel/datatype.h"
+#include "dynamicmodel/typerelation.h"
+
+#include <QDebug>
 
 namespace LocalMyList {
 namespace DynamicModel {
@@ -12,7 +14,10 @@ DataModel::DataModel(QObject *parent) : QObject(parent)
 
 DataModel::~DataModel()
 {
+       for (auto &&relations : typeRelations)
+               qDeleteAll(relations);
        qDeleteAll(dataTypes);
+       qDebug() << "Deleted data model";
 }
 
 bool DataModel::registerDataType(DataType *dataType)
@@ -91,10 +96,8 @@ TypeRelation *DataModel::typeRelation(const QString &source, const QString &dest
 bool DataModel::hasTypeRelation(const QString &source, const QString &destiantion) const
 {
        const auto it = typeRelations.find(source);
-
        if (it == typeRelations.constEnd())
                return false;
-
        const auto inner = it.value().find(destiantion);
        return inner != it.value().constEnd();
 }
index 0e42b57c69582e1d04af56e28e7c8ca8d10db466..6d907d859dc56032ce107cde793f5b4ed97b126e 100644 (file)
@@ -33,6 +33,8 @@ public:
        bool hasTypeRelation(const QString &source, const QString &destiantion) const;
 
 
+signals:
+       void entryAdded(DataType *dataType, int id);
 
 private slots:
 /*     void animeUpdate(int aid);
index 3d33f03627d6cd82a9abc7b46d8fb5a118cb45c5..bffbba99ee9f6044a515f53bcdf57bb29f4264e1 100644 (file)
@@ -2,8 +2,10 @@
 
 #include <QtGlobal>
 #include <QSqlQuery>
-#include "../database.h"
-#include "../mylist.h"
+#include "database.h"
+#include "mylist.h"
+
+#include <QDebug>
 
 namespace LocalMyList {
 namespace DynamicModel {
@@ -23,9 +25,19 @@ DataModel *DataType::model() const
        return m_model;
 }
 
-QStringList DataType::availableChildRelations() const
+QString DataType::name() const
+{
+       return tableName();
+}
+
+QString DataType::orderBy() const
+{
+       return {};
+}
+
+QString DataType::additionalJoins() const
 {
-       return QStringList();
+       return {};
 }
 
 Data *DataType::data(int key) const
@@ -41,30 +53,78 @@ void DataType::unregistered()
 {
 }
 
+NodeList DataType::readEntries(SqlResultIteratorInterface &it, Node *parent)
+{
+       qDebug() << "readEntries" << tableName();
+       NodeList ret;
+       while (it.next())
+       {
+               int totalRowCount = it.value(0).toInt();
+               Data *data = readEntry(it);
+               if (data->id())
+               {
+                       auto it = m_dataStore.find(data->id());
+                       if (it != m_dataStore.end())
+                               data = it.value();
+                       else
+                               m_dataStore.insert(data->id(), data);
+               }
+               Node *node = new Node(parent->model(), parent, totalRowCount, data);
+               ret << node;
+       }
+       return ret;
+}
+
+QString DataType::updateQuery() const
+{
+       return QString{R"(
+               SELECT 0, %1
+                               FROM %2 %3
+                               %5
+                       WHERE %3.%4 = :id
+       )"}
+               .arg(additionalColumns())
+               .arg(tableName())
+               .arg(alias())
+               .arg(primaryKeyName())
+               .arg(additionalJoins());
+}
+
 void DataType::update(Data *data)
 {
        Q_UNUSED(data);
 }
 
-void DataType::childUpdate(Node *parent, const Data *oldData, Operation operation)
+void DataType::childUpdated(Node *child, const Data * const oldData)
 {
-       Q_UNUSED(parent);
+       Q_UNUSED(child);
        Q_UNUSED(oldData);
-       Q_UNUSED(operation);
 }
 
 void DataType::released(Data *data)
 {
-       Q_ASSERT(data != 0);
+       Q_ASSERT_X(data, "dynamicmodel/released", "released() got NULL data");
 
-       bool removed = m_dataStore.remove(data->id());
+       if (data->id())
+       {
+               bool removed = m_dataStore.remove(data->id());
 
-       Q_ASSERT_X(removed, "released", "releasing node not in data store");
-       Q_UNUSED(removed);
+               Q_ASSERT_X(removed, "dynamicmodel/released", "releasing node not in data store");
+               Q_UNUSED(removed);
+       }
 
        delete data;
 }
 
+QList<QString> DataType::availableActions() const
+{
+       return {};
+}
+
+void DataType::actionRequested(int)
+{
+}
+
 int DataType::sizeHelper(const QString &tableName, const QString &keyName) const
 {
        if (m_size)
index 3792df1f1738708a53d93c601f6a9a34fadbd481..cbfbf22b0f4c9c5580e0894b2a83b21b573f907f 100644 (file)
@@ -29,21 +29,63 @@ public:
 
        DataModel *model() const;
 
-       virtual QString name() const = 0;
-       QStringList availableChildRelations() const;
-
-       virtual QString baseQuery() const = 0;
+       /**
+        * @brief The name of the data type
+        * @return table name
+        */
+       virtual QString name() const;
+
+       /**
+        * @brief The name of the table this data type represents
+        * @return table name
+        */
+       virtual QString tableName() const = 0;
+
+       /**
+        * @brief The alias alias to the table returned by name()
+        * @return table alias
+        */
+       virtual QString alias() const = 0;
+
+       /**
+        * @brief The name of the primary key column in the table returned by name()
+        * @return name of the primary key
+        */
+       virtual QString primaryKeyName() const = 0;
+
+       /**
+        * @brief comma separated list of columns prefixed with the table alias that
+        * this data type requires.
+        * @return columns
+        */
+       virtual QString additionalColumns() const = 0;
+
+       /**
+        * @brief SQL ORDER BY clause
+        * @return SQL ORDER BY clause or empty string if ordering is not required
+        */
+       virtual QString orderBy() const;
+
+       /**
+        * @brief Additional joins for columns this data type requires.
+        * @return join statements or empty string
+        */
+       virtual QString additionalJoins() const;
 
        Data *data(int key) const;
-       virtual int size() const = 0;
 
        // Register
        virtual void registerd();
        virtual void unregistered();
 
+       // Obtain
+       virtual NodeList readEntries(SqlResultIteratorInterface &it, Node *parent);
+
        // Update
+       virtual QString updateQuery() const;
+
        virtual void update(Data *data);
-       virtual void childUpdate(Node *child, const Data *oldData, Operation operation);
+       virtual void childUpdated(Node *child, const Data *const oldData);
 
        // Release
        void released(Data *data);
@@ -53,6 +95,10 @@ public:
        // Type relation interface
        virtual Data *readEntry(const SqlResultIteratorInterface &it) = 0;
 
+       // Actions
+       virtual QList<QString> availableActions() const;
+       virtual void actionRequested(int action);
+
 protected:
        int sizeHelper(const QString &tableName, const QString &keyName) const;
 
@@ -63,16 +109,6 @@ protected:
                func(*typedData, it);
 
                Data *newData = typedData;
-               Data *currentData = data(typedData->id());
-               if (currentData)
-               {
-                       delete typedData;
-                       newData = currentData;
-               }
-               else
-               {
-                       m_dataStore.insert(typedData->id(), newData);
-               }
                return newData;
        }
 
diff --git a/localmylist/dynamicmodel/entry.cpp b/localmylist/dynamicmodel/entry.cpp
new file mode 100644 (file)
index 0000000..39eb94b
--- /dev/null
@@ -0,0 +1,5 @@
+#include "entry.h"
+
+Entry::Entry()
+{
+}
diff --git a/localmylist/dynamicmodel/entry.h b/localmylist/dynamicmodel/entry.h
new file mode 100644 (file)
index 0000000..5651087
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef ENTRY_H
+#define ENTRY_H
+
+#include <QString>
+
+class Entry
+{
+public:
+       Entry();
+
+       virtual QString fields() const = 0;
+};
+
+#endif // ENTRY_H
index ffa2b9e3b9db8623252722c13f37470f2b3f9a00..ad240ff129dcf4521721f3016b5cc9596c8f41bb 100644 (file)
@@ -1,11 +1,12 @@
-#include "model.h"
-
-#include "node.h"
-#include "datamodel.h"
-#include "datatype.h"
-#include "typerelation.h"
-#include "query.h"
-
+#include "dynamicmodel/model.h"
+
+#include "dynamicmodel/node.h"
+#include "dynamicmodel/datamodel.h"
+#include "dynamicmodel/datatype.h"
+#include "dynamicmodel/typerelation.h"
+#include "dynamicmodel/query.h"
+#include "dynamicmodel/queryparser.h"
+#include "mylist.h"
 #include <QDebug>
 
 namespace LocalMyList {
@@ -19,15 +20,17 @@ Model::Model(QObject *parent) :
 
 Model::~Model()
 {
+       qDebug() << "deleting model";
        delete rootItem;
+       qDebug() << "deleted model";
 }
 
-Query Model::query() const
+QueryParser Model::query() const
 {
        return m_query;
 }
 
-void Model::setQuery(const Query &query)
+void Model::setQuery(const QueryParser &query)
 {
        if (query == m_query)
                return;
@@ -35,12 +38,20 @@ void Model::setQuery(const Query &query)
        if (!query.isValid())
                return;
 
+       if (m_query.dataModel() != query.dataModel())
+       {
+               if (m_query.dataModel())
+                       disconnect(m_query.dataModel(), 0, this, 0);
+               if (query.dataModel())
+                       connect(query.dataModel(), SIGNAL(entryAdded(DataType*,int)), this, SLOT(entryAdded(DataType*,int)));
+       }
+
        m_query = query;
 
        reload();
 
        emit queryChanged(query);
-       emit queryChanged(query.queryString());
+       emit queryChanged(query.query());
 }
 
 QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const
@@ -56,7 +67,11 @@ Qt::ItemFlags Model::flags(const QModelIndex &index) const
        if (!index.isValid())
                return 0;
 
-       return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+       Node *node = static_cast<Node *>(index.internalPointer());
+       if (!node->data())
+               return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+
+       return node->data()->flags(index.column());
 }
 
 QVariant Model::data(const QModelIndex &index, int role) const
@@ -69,6 +84,21 @@ QVariant Model::data(const QModelIndex &index, int role) const
        return item->data(index.column(), role);
 }
 
+bool Model::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+       if (!index.isValid())
+               return false;
+
+       Node *item = static_cast<Node *>(index.internalPointer());
+
+       bool ret = item->setData(index.column(), value, role);
+
+       if (ret)
+               emit dataChanged(index, index);
+
+       return ret;
+}
+
 QModelIndex Model::index(int row, int column, const QModelIndex &parent) const
 {
        if (!hasIndex(row, column, parent))
@@ -179,18 +209,11 @@ DataModel *Model::dataModel() const
        return m_query.dataModel();
 }
 
-DataType *Model::grandChildDataType(Node *node) const
-{
-       int d = node->depth() + 1;
-
-       return childDataType(d);
-}
-
 DataType *Model::childDataType(int i) const
 {
-       if (m_query.dataTypeNames().count() <= i)
+       if (i > m_query.levels())
                return 0;
-       return dataModel()->dataType(m_query.dataTypeNames().at(i));
+       return m_query.dataType(i);
 }
 
 void Model::reload()
@@ -201,56 +224,134 @@ void Model::reload()
        endResetModel();
 }
 
+void Model::entryAdded(DataType *dataType, int id)
+{
+       qDebug() << "entryAdded" << dataType << id;
+       for (int i = 0; i < m_query.levels(); ++i)
+       {
+               if (dataType == m_query.dataType(i))
+               {
+                       newEntryCheck(i, id, dataType);
+               }
+       }
+}
+
 void Model::episodeInsert(int aid, int eid)
 {
        Q_UNUSED(aid);
-       DataType *episodeDataType = m_query.dataModel()->dataType("episode");
+       Q_UNUSED(eid);
+//     DataType *episodeDataType = m_query.dataModel()->dataType("episode");
 
-       if (!episodeDataType)
-               return;
+//     if (!episodeDataType)
+//             return;
 
-       if (!m_query.dataModel()->dataType("anime"))
-               return;
+//     if (!m_query.dataModel()->dataType("anime"))
+//             return;
 
-       QString previousDataTypeName = QString();
-//     DataType *previousDataType = 0;
+//     QString previousDataTypeName = QString();
+////   DataType *previousDataType = 0;
 
-       for (const QString &dataTypeName : m_query.dataTypeNames())
-       {
-               DataType *currentDataType = m_query.dataModel()->dataType(dataTypeName);
+//     for (const QString &dataTypeName : m_query.dataTypeNames())
+//     {
+//             DataType *currentDataType = m_query.dataModel()->dataType(dataTypeName);
 
-               if (currentDataType == episodeDataType)
-               {
-                       TypeRelation *rel = m_query.dataModel()->typeRelation(previousDataTypeName, dataTypeName);
+//             if (currentDataType == episodeDataType)
+//             {
+//                     TypeRelation *rel = m_query.dataModel()->typeRelation(previousDataTypeName, dataTypeName);
 
-                       if (previousDataTypeName.isNull())
-                       {
-                               // The root is the parent, just see if it needs to be added.
-                       }
-                       else
-                       {
-                               IdList ids = rel->getParents(eid);
+//                     if (previousDataTypeName.isNull())
+//                     {
+//                             // The root is the parent, just see if it needs to be added.
+//                     }
+//                     else
+//                     {
+//                             IdList ids = rel->getParents(eid);
 
 
-                       }
-               }
+//                     }
+//             }
 
-               previousDataTypeName = dataTypeName;
-       }
+//             previousDataTypeName = dataTypeName;
+//     }
 }
 
 Node *Model::createRootNode()
 {
-       int size = (m_query.dataModel() && !m_query.dataTypeNames().isEmpty())
-                               ? dataModel()->dataType(m_query.dataTypeNames().at(0))->size()
-                               : 0;
+       int size = rootNodeSize();
        Node *n = new Node(this, 0, size, 0);
        qDebug() << "SIZE" << size;
-       if (m_query.dataModel() && !m_query.dataTypeNames().isEmpty())
-               n->setChildDataType(dataModel()->dataType(m_query.dataTypeNames().at(0)));
        return n;
 }
 
+int Model::rootNodeSize() const
+{
+       if (!m_query.isValid())
+               return 0;
+
+       QSqlQuery q = MyList::instance()->database()->prepareOneShot(
+                               query().buildCountSql(-1));
+
+       if (!MyList::instance()->database()->exec(q))
+               return 0;
+
+       if (!q.next())
+               return 0;
+
+       int count = q.value(0).toInt();
+
+       q.finish();
+
+       return count;
+}
+
+void Model::newEntryCheck(int currentLevel, int id, DataType *dataType)
+{
+       qDebug() << "newEntryCheck" << currentLevel << id << dataType;
+       // Children of the rootNode don't need any checks
+       if (!currentLevel)
+       {
+               rootItem->childAdded(id, dataType);
+               return;
+       }
+
+       QSqlQuery q = MyList::instance()->database()->prepareOneShot(
+                               query().buildPrimaryValuesSql(currentLevel));
+
+       q.bindValue(":id", id);
+
+       if (!MyList::instance()->database()->exec(q))
+               return;
+
+       QSqlResultIterator it(q);
+       while (it.next())
+       {
+               QVariantList primaryValues;
+               for (int i = 0; i < currentLevel; ++i)
+               {
+                       primaryValues << it.value(i);
+               }
+
+               Node *parent = rootItem->findParentOfNewEntry(primaryValues);
+
+               if (!parent)
+                       continue;
+
+               // TODO this will fetch the data from the DB for every parent (it's always the same data)
+               // entryAddedToNode to be implemented for handling this
+               parent->childAdded(id, dataType);
+       }
+       q.finish();
+}
+
+Data *Model::entryAddedToNode(Node *node, int id, DataType *dataType, Data *data)
+{
+       Q_UNUSED(node);
+       Q_UNUSED(id);
+       Q_UNUSED(dataType);
+       Q_UNUSED(data);
+       return 0;
+}
+
 
 } // namespace DynamicModel
-} // namespace Local
+} // namespace LocalMyList
index ee4cb6f597c09f02277c6dc0bbe9d3c92e337b4f..5c3ca3ed06258b0782066248fad1e415683cf2d7 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "../localmylist_global.h"
 #include "query.h"
+#include "dynamicmodel/queryparser.h"
 #include <QAbstractItemModel>
 #include <QStringList>
 
@@ -17,29 +18,34 @@ class Query;
 class LOCALMYLISTSHARED_EXPORT Model : public QAbstractItemModel
 {
        Q_OBJECT
-       Q_PROPERTY(Query query READ query WRITE setQuery NOTIFY queryChanged)
+       Q_PROPERTY(QueryParser query READ query WRITE setQuery NOTIFY queryChanged)
        friend class Node;
 
 public:
        explicit Model(QObject *parent = 0);
        ~Model();
 
-       Query query() const;
-       void setQuery(const Query &query);
+       QueryParser query() const;
+       void setQuery(const QueryParser &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;
-       QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
-       QModelIndex parent(const QModelIndex &index) const;
+       // Data
+       QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+       Qt::ItemFlags flags(const QModelIndex &index) const override;
+       QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+       bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
 
-       int rowCount(const QModelIndex &parent = QModelIndex()) const;
-       int columnCount(const QModelIndex &parent = QModelIndex()) const;
+       // Structure
+       QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+       QModelIndex parent(const QModelIndex &index) const override;
+
+       // Dimensions
+       int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+       int columnCount(const QModelIndex &parent = QModelIndex()) const override;
 
        // Lazy loading
-       bool canFetchMore(const QModelIndex &parent) const;
-       void fetchMore(const QModelIndex &parent);
-       bool hasChildren(const QModelIndex &parent = QModelIndex()) const;
+       bool canFetchMore(const QModelIndex &parent) const override;
+       void fetchMore(const QModelIndex &parent) override;
+       bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
 
        Node *node(const QModelIndex &idx) const;
        QModelIndex index(Node *node) const;
@@ -47,25 +53,29 @@ public:
        DataType *rootDataType() const;
        DataModel *dataModel() const;
 
-       DataType *grandChildDataType(Node *node) const;
        DataType *childDataType(int i) const;
 
 public slots:
        void reload();
 
 private slots:
+       void entryAdded(DataType *dataType, int id);
        void episodeInsert(int aid, int eid);
 
 signals:
-       void queryChanged(Query query);
+       void queryChanged(QueryParser query);
        void queryChanged(QString query);
 
 private:
        Node *createRootNode();
+       int rootNodeSize() const;
+
+       void newEntryCheck(int currentLevel, int id, DataType *dataType);
+       Data *entryAddedToNode(Node *node, int id, DataType *dataType, Data *data);
 
        Node *rootItem;
 
-       Query m_query;
+       QueryParser m_query;
 };
 
 } // namespace DynamicModel
index 9baa6233c70df9b97afbf8bf299f12ca96910922..3463558639a227ca8441ae77dff3929fe887b842 100644 (file)
@@ -1,10 +1,12 @@
-#include "node.h"
-#include "datatype.h"
-
-#include "dynamicmodel_global.h"
-#include "data.h"
-#include "model.h"
-#include "typerelation.h"
+#include "dynamicmodel/node.h"
+
+#include "dynamicmodel/datatype.h"
+#include "dynamicmodel/dynamicmodel_global.h"
+#include "dynamicmodel/data.h"
+#include "dynamicmodel/model.h"
+#include "dynamicmodel/typerelation.h"
+#include "dynamicmodel/queryparser.h"
+#include "mylist.h"
 #include <QModelIndex>
 
 #include <QDebug>
@@ -14,7 +16,7 @@ namespace DynamicModel {
 
 Node::Node(Model *model, Node *parent, int totalRowCount, Data *data)
        : m_parent(parent), m_model(model), m_totalRowCount(totalRowCount),
-         m_data(data), m_childType(0)
+         m_data(data)
 {
        Q_ASSERT_X((parent && data) || (!parent && !data), "dynamic model", "Root node has no data and no parent. Other nodes must have both");
 
@@ -25,22 +27,15 @@ Node::Node(Model *model, Node *parent, int totalRowCount, Data *data)
 
 Node::~Node()
 {
-       if (!m_data)
-               return;
+       if (m_data)
+               m_data->deref(this);
 
-       m_data->deref(this);
        qDeleteAll(m_children);
 }
 
 DataType *Node::childDataType() const
 {
-       return m_childType;
-}
-
-void Node::setChildDataType(DataType *dataType)
-{
-//     Q_ASSERT_X(dataType, "dynamicmodel", "NULL data type");
-       m_childType = dataType;
+       return 0;
 }
 
 Node *Node::parent() const
@@ -60,7 +55,7 @@ int Node::childCount() const
 
 int Node::columnCount() const
 {
-       return 5;
+       return 6;
 }
 
 int Node::row() const
@@ -73,11 +68,16 @@ int Node::row() const
 
 bool Node::hasChildren() const
 {
-       if (this == m_model->rootItem)
-               return true;
+//     if (isRoot())
+//             return true;
        return totalRowCount() > 0;
 }
 
+Model *Node::model() const
+{
+       return m_model;
+}
+
 QVariant Node::data(int column, int role) const
 {
 //     qDebug() << parent() << column;
@@ -99,6 +99,8 @@ QVariant Node::data(int column, int role) const
                        return QObject::tr("Vote");
                case 4:
                        return QObject::tr("Watched / Renamed");
+               case 5:
+                       return QObject::tr("State");
        }
 
        return QVariant();
@@ -109,6 +111,13 @@ Data *Node::data() const
        return m_data;
 }
 
+bool Node::setData(int column, const QVariant &data, int role)
+{
+       if (!m_data)
+               return false;
+       return m_data->setData(column, data, role);
+}
+
 int Node::totalRowCount() const
 {
        return m_totalRowCount;// ? m_totalRowCount : childDataType() ? childDataType()->size() : 0;
@@ -126,31 +135,30 @@ bool Node::canFetchMore() const
 
 void Node::fetchMore()
 {
-       if (!m_childType)
-               return;
        qDebug() << "fetchMore" << this;
-       NodeList newItems;
 
-       TypeRelation *rel = 0;
-       if (isRoot())
-               rel = m_model->dataModel()->typeRelation(QString(), childDataType()->name());
-       else
-               rel = m_model->dataModel()->typeRelation(m_data->type()->name(), childDataType()->name());
+       DataType *dataType = model()->childDataType(level());
 
-       if (!rel)
-               return;
-
-       DataType *grandChildDataType = m_model->grandChildDataType(this);
 /*     qDebug() << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
        qDebug() << "currentType\t" << (m_data ? m_data->type()->name() : "<root>");
        qDebug() << "grandChildDataType\t" << (grandChildDataType ? grandChildDataType->name() : QString("0"));
 //     qDebug() << "rowCountType\t" << (rowCountType ? rowCountType->name() : QString("0"));
        qDebug() << "getting from rel" << rel->sourceType() << rel->destinationType();
 */
-       auto factory = childNodeFactory();
+       QSqlQuery q = MyList::instance()->database()->prepareOneShot(
+                               m_model->query().buildSql(level()));
 
+       bindValues(q);
+       qDebug() << LIMIT << childCount();
+       q.bindValue(":limit", LIMIT);
+       q.bindValue(":offset", childCount());
 
-       newItems = rel->getChildren(m_data, childCount(), grandChildDataType, factory);
+       if (!MyList::instance()->database()->exec(q))
+               return;
+
+       QSqlResultIterator it(q);
+       NodeList newItems = dataType->readEntries(it, this);
+       q.finish();
 
        const QModelIndex parent = m_model->index(this);
        const int newrows = newItems.count();
@@ -172,6 +180,28 @@ void Node::fetchComplete()
 
 }
 
+Node *Node::findParentOfNewEntry(const QVariantList &primaryValues)
+{
+       // Not loaded
+       if (!m_children.size() && m_totalRowCount)
+               return nullptr;
+qDebug() << level() << "magic level" << primaryValues.size();
+       if (level() == primaryValues.size())
+               return this;
+
+       const QVariant &primaryValue = primaryValues.at(level());
+
+       Node *child = findChildByPrimaryValue(primaryValue);
+
+       if (!child)
+               return nullptr;
+
+       Q_ASSERT_X(child->parent() == this, "dynamicmodel/update", "Found node that is not child of it's parent");
+qDebug() << child->level() << "magic level";
+
+       return child->findParentOfNewEntry(primaryValues);
+}
+
 MoveType Node::moveChild(Node *child, Operation type)
 {
 qDebug() << "a";
@@ -258,7 +288,7 @@ qDebug() << "g";
        return SuccessfulMove;
 }
 
-int Node::depth() const
+int Node::level() const
 {
        Node *node = parent();
        int depth = 0;
@@ -270,49 +300,90 @@ int Node::depth() const
        return depth;
 }
 
+Node *Node::findChildByPrimaryValue(const QVariant &primaryValue)
+{
+       // TODO this can be made logarithmic
+       // children of type column are ordered ascending by their valie
+       // datatypes with ids can bee looked up in the DataType's dataStore,
+       // followed by node lookup.
+       for (Node *child : m_children)
+       {
+               if (child->data()->primaryValue() == primaryValue)
+                       return child;
+       }
+       return 0;
+}
+
 NodeFactory Node::childNodeFactory()
 {
        return [=](Data *d, int c) -> Node *
        {
                Node *n = new Node(m_model, this, c, d);
-               n->setChildDataType(m_model->grandChildDataType(this));
+//             n->setChildDataType(m_model->grandChildDataType(this));
                return n;
        };
 }
 
+void Node::bindValues(QSqlQuery &query) const
+{
+       if (parent())
+               parent()->bindValues(query);
+       m_model->query().bindValue(query, m_data, level() - 1);
+}
+
 int Node::id() const
 {
        return m_data->id();
 }
 
-void Node::childAdded(Data *newData)
+bool Node::childAdded(int id, DataType *dataType)
 {
-       qDebug() << "childAdded" << newData;
+       qDebug() << "childAdded" << id << dataType;
 
-/*     Node *childNode = childNodeFactory()(newData);
+       // The total row count increases regardless if we actually add a child or not
+       // as there is a row in the db that is a child of this parent
+       ++m_totalRowCount;
 
-       MoveType moveType = moveChild(childNode, InsertOperation);
+       QSqlQuery q = MyList::instance()->database()->prepareOneShot(
+                               m_model->query().buildEntrySql(level()));
+       bindValues(q);
+       q.bindValue(":id", id);
 
-       if (moveType == OutOfBoundsMove)
-               delete childNode;
-*/
-}
-/*
-void Node::childUpdate(Node *child, const Data *newData, Operation operation)
-{
+       if (!q.exec())
+               return false;
+
+       Node *child = nullptr;
+
+       {
+               QSqlResultIterator it(q);
+               NodeList nodelist = dataType->readEntries(it, this);
+               q.finish();
+               if (!nodelist.length())
+                       return false;
+               child = nodelist.at(0);
+       }
 
+       // TODO pg orders differently than the usual node compare leading to arbitrary ordering
+       auto it = std::upper_bound(m_children.begin(), m_children.end(), child, dataType->nodeCompareFunction());
+       if (it == m_children.end())
+               return false;
+       const int newRow = qMax(0, (it - m_children.begin()) - 1);
+       m_model->beginInsertRows(m_model->index(this), newRow, newRow);
+       it = m_children.insert(it, child);
+       m_model->endInsertRows();
+       return true;
 }
-*/
-bool Node::updated(Operation type)
-{
-       Q_UNUSED(type);
 
+void Node::updated(const Data * const oldData)
+{
        const int r = row();
        const QModelIndex parentIndex(m_model->index(parent()));
        emit m_model->dataChanged(m_model->index(r, 0, parentIndex),
                                                          m_model->index(r, columnCount() - 1, parentIndex));
 
-       return false;
+       if (m_parent && m_parent->data())
+               m_parent->data()->type()->childUpdated(this, oldData);
+
 }
 
 } // namespace DynamicModel
index ea25af05f8484ca2fcf7f60769ae9c3fd149fa6c..baa672bcf02fdb15f3e70d52c36a52e2627bac44 100644 (file)
@@ -7,6 +7,8 @@
 #include <QVariant>
 #include <QList>
 
+class QSqlQuery;
+
 namespace LocalMyList {
 namespace DynamicModel {
 
@@ -23,7 +25,6 @@ public:
        ~Node();
 
        DataType *childDataType() const;
-       void setChildDataType(DataType *dataType);
 
        bool isRoot() const { return !m_parent; }
 
@@ -34,11 +35,13 @@ public:
        int columnCount() const;
        int row() const;
        bool hasChildren() const;
+       Model *model() const;
 
        // Data
        int id() const;
        QVariant data(int column, int role) const;
        Data *data() const;
+       bool setData(int column, const QVariant &data, int role);
        int totalRowCount() const;
 
        bool canFetchMore() const;
@@ -46,15 +49,19 @@ public:
        void fetchComplete();
 
        // Changes
-       void childAdded(Data *newData);
-       bool updated(Operation type);
+       Node *findParentOfNewEntry(const QVariantList &primaryValues);
+       bool childAdded(int id, DataType *dataType);
+       void updated(const Data * const oldData);
        MoveType moveChild(Node *child, Operation type);
 
        // Misc
-       int depth() const;
+       int level() const;
+
+       Node *findChildByPrimaryValue(const QVariant &primaryValue);
 
 private:
        NodeFactory childNodeFactory();
+       void bindValues(QSqlQuery &query) const;
 
        Node *m_parent;
        Model *m_model;
@@ -62,7 +69,8 @@ private:
        int m_totalRowCount;
 
        Data *m_data;
-       DataType *m_childType;
+
+       static const int LIMIT = 400;
 };
 
 } // namespace DynamicModel
index 5a1626f8f77ef2a3dac9f2b8d41c8c5e546205c3..a812f68ebb5322e7710b1a8defe6771ae9a51dfe 100644 (file)
@@ -1,6 +1,6 @@
-#include "query.h"
+#include "dynamicmodel/query.h"
 
-#include "datamodel.h"
+#include "dynamicmodel/datamodel.h"
 
 namespace LocalMyList {
 namespace DynamicModel {
diff --git a/localmylist/dynamicmodel/queryparser.cpp b/localmylist/dynamicmodel/queryparser.cpp
new file mode 100644 (file)
index 0000000..4320809
--- /dev/null
@@ -0,0 +1,575 @@
+#include "dynamicmodel/queryparser.h"
+
+#include <QStringList>
+#include <QSet>
+
+#include "dynamicmodel/datatype.h"
+#include "dynamicmodel/typerelation.h"
+
+#include <QDebug>
+
+namespace LocalMyList {
+namespace DynamicModel {
+
+// TODO this data has to come from the data model
+namespace {
+const QMap<QString, QStringList> table_columns = []() {
+       QMap<QString, QStringList> r;
+       r["anime"] = QStringList()
+               << "aid"
+               << "entry_added"
+               << "anidb_update"
+               << "entry_update"
+               << "my_update"
+               << "title_english"
+               << "title_romaji"
+               << "title_kanji"
+               << "description"
+               << "year"
+               << "start_date"
+               << "end_date"
+               << "type"
+               << "total_episode_count"
+               << "highest_epno"
+               << "rating"
+               << "votes"
+               << "temp_rating"
+               << "temp_votes"
+               << "my_vote"
+               << "my_vote_date"
+               << "my_temp_vote"
+               << "my_temp_vote_date";
+       r["episode"] = QStringList()
+               << "eid"
+               << "aid"
+               << "entry_added"
+               << "anidb_update"
+               << "entry_update"
+               << "my_update"
+               << "epno"
+               << "title_english"
+               << "title_romaji"
+               << "title_kanji"
+               << "length"
+               << "airdate"
+               << "state"
+               << "type"
+               << "recap"
+               << "rating"
+               << "votes"
+               << "my_vote"
+               << "my_vote_date";
+       r["file"] = QStringList()
+               << "fid"
+               << "eid"
+               << "aid"
+               << "gid"
+               << "lid"
+               << "entry_added"
+               << "anidb_update"
+               << "entry_update"
+               << "my_update"
+               << "ed2k"
+               << "size"
+               << "length"
+               << "extension"
+               << "group_name"
+               << "group_name_short"
+               << "crc"
+               << "release_date"
+               << "version"
+               << "censored"
+               << "deprecated"
+               << "source"
+               << "quality"
+               << "resolution"
+               << "video_codec"
+               << "audio_codec"
+               << "audio_language"
+               << "subtitle_language"
+               << "aspect_ratio"
+               << "my_watched"
+               << "my_state"
+               << "my_file_state"
+               << "my_storage"
+               << "my_source"
+               << "my_other";
+       return r;
+}();
+
+const QString ellipsisPart{"..."};
+
+const QList<QString> ellipsisParts = []() {
+       QList<QString> ret;
+       ret << "anime"
+               << "episode"
+               << "file"
+               << "file_location";
+       return ret;
+}();
+}
+
+QDebug operator<<(QDebug dbg, const QueryParser::Level &l)
+{
+       if (l.column.isEmpty())
+               dbg << QString("[%1:%2]").arg(l.type).arg(l.table);
+       else
+               dbg << QString("[%1:%2.%3]").arg(l.type).arg(l.table).arg(l.column);
+       return dbg;
+}
+
+QueryParser::QueryParser(DataModel *dataModel) : m_dataModel{dataModel}, m_valid{false}
+{
+}
+
+bool QueryParser::parse(const QString &rawPath)
+{
+       static const QString emptyString{};
+
+       if (!m_dataModel)
+       {
+               m_errorString = QObject::tr("QueryParser needs a DataModel");
+               m_valid = false;
+               return m_valid;
+       }
+
+       m_errorString = QString{};
+
+       m_queryString = rawPath;
+       QStringList parts = m_queryString.split(QChar('/'), QString::SkipEmptyParts);
+       qDebug() << "parse " << parts;
+
+       if (!parts.length())
+               parts << "...";
+
+       m_levels.clear();
+       m_levels.reserve(parts.length());
+
+       for (int i = 0; i < parts.length(); ++i)
+       {
+               Level currentLevel;
+
+               if (parts[i] == ellipsisPart)
+               {
+                       if (i != parts.length() - 1)
+                       {
+                               m_errorString = QObject::tr("Ellipsis can only be the last element of the Query");
+                               m_valid = false;
+                               return m_valid;
+                       }
+
+                       //parts.removeLast();
+                       int startIndex = 0;
+                       if (parts.length() > 1)
+                       {
+                               const Level &lastLevel = level(parts.length() - 2);
+                               for (int j = 0; j < ellipsisParts.length(); ++j)
+                               {
+                                       if (ellipsisParts[j] == lastLevel.table)
+                                       {
+                                               startIndex = j + (lastLevel.type != ColumnEntry);
+                                               break;
+                                       }
+                               }
+                       }
+
+                       parts.reserve(parts.length() + ellipsisParts.length() - startIndex);
+                       if (startIndex < ellipsisParts.length())
+                       {
+                               parts[i] = ellipsisParts[startIndex];
+                               for (int j = startIndex + 1; j < ellipsisParts.length(); ++j)
+                               {
+                                       parts << ellipsisParts[j];
+                               }
+                       }
+                       else
+                       {
+                               parts.removeLast();
+                               break;
+                       }
+               }
+
+               const QString &part = parts[i];
+
+               const QStringList tableColumn = part.split(QChar('.'));
+               const QString &table = tableColumn[0];
+               const QString &column = tableColumn.size() > 1 ? tableColumn[1] : emptyString;
+
+//             qDebug() << "----------------------- Iteration" << i << "-----------------------";
+               qDebug() << "part(" << part.length() << ") =" << table << "(" << column << ")";
+
+               if (!m_dataModel->hasDataType(table))
+               {
+                       m_errorString = QObject::tr("Table \"%1\" does not exist.").arg(table);
+                       m_valid = false;
+                       return m_valid;
+               }
+               else
+               {
+                       currentLevel.table = table;
+                       currentLevel.tableAlias = m_dataModel->dataType(table)->alias();
+                       currentLevel.type = TableEntry;
+               }
+
+               if (!column.isEmpty())
+               {
+                       if (!table_columns[currentLevel.table].contains(column))
+                       {
+                               m_errorString = QObject::tr("Column %1 does not exist in table %2.")
+                                               .arg(column).arg(table);
+                               m_valid = false;
+                               return m_valid;
+                       }
+                       currentLevel.column = column;
+                       currentLevel.type = ColumnEntry;
+               }
+
+               if (i
+                       && m_levels.last().table != currentLevel.table
+                       && !m_dataModel->hasTypeRelation(m_levels.last().table, currentLevel.table))
+               {
+                       m_errorString = QObject::tr("No relation defined between table %1 and table %2.")
+                                       .arg(m_levels.last().table).arg(currentLevel.table);
+                       m_valid = false;
+                       return m_valid;
+               }
+
+               m_levels.push_back(currentLevel);
+       }
+
+       qDebug() << m_levels;
+
+       m_valid = true;
+       return m_valid;
+}
+
+
+QString QueryParser::buildSql(int currentLevel) const
+{
+       if (!m_valid) return {};
+       resetPlaceHolderUse();
+
+       const Level &lastLevel = level(currentLevel);
+       const DataType *dataType = m_dataModel->dataType(lastLevel.table);
+
+       QString columns = QString("(%1)").arg(buildChildCountSql(currentLevel));
+
+       if (!lastLevel.column.isEmpty())
+       {
+               columns += QString(", %2.%1")
+                               .arg(lastLevel.column).arg(dataType->alias());
+       }
+       else
+       {
+               QString additionalColumns = dataType->additionalColumns();
+               if (!additionalColumns.isEmpty())
+               {
+                       columns += QString(", %1").arg(additionalColumns);
+               }
+       }
+
+       QString ret = buildSelect(currentLevel, columns, true, true);
+
+       if (lastLevel.type == ColumnEntry)
+       {
+               QString column = QString("%2.%1")
+                               .arg(lastLevel.column).arg(dataType->alias());
+               ret += QString("\n\tGROUP BY %1\n\tORDER BY %1")
+                               .arg(column);
+       }
+       else if (!dataType->orderBy().isEmpty())
+       {
+               ret += QString("\n\tORDER BY %1").arg(dataType->orderBy());
+       }
+
+       ret += QString("\n\tLIMIT :limit\n\tOFFSET :offset\n");
+
+       qDebug() << "================================================== sql ========================================================";
+       qDebug() << ret;
+       qDebug() << "===============================================================================================================";
+       return ret;
+}
+
+QString QueryParser::buildCountSql(int currentLevel) const
+{
+       if (!m_valid) return {};
+       resetPlaceHolderUse();
+       QString ret = buildChildCountSql(currentLevel);
+
+       qDebug() << "============================================ child count sql ==================================================";
+       qDebug() << ret;
+       qDebug() << "===============================================================================================================";
+       return ret;
+}
+
+QString QueryParser::buildEntrySql(int currentLevel) const
+{
+       if (!m_valid) return {};
+       resetPlaceHolderUse();
+       QString ret = buildEntrySqlInternal(currentLevel);
+
+       qDebug() << "=============================================== entry sql =====================================================";
+       qDebug() << ret;
+       qDebug() << "===============================================================================================================";
+       return ret;
+}
+
+QString QueryParser::buildPrimaryValuesSql(int maxLevel) const
+{
+       if (!m_valid) return {};
+       resetPlaceHolderUse();
+
+       QStringList columnList;
+
+       for (int i = 0; i < maxLevel; ++i)
+       {
+               const DataType *type = dataType(i);
+               QString alias = level(i).type == ColumnEntry ? level(i).tableAlias : type->alias();
+               columnList << valueColumn(i, alias);
+       }
+
+       if (!columnList.length())
+       {
+               return "SELECT 0";
+       }
+
+       QString columns = columnList.join(", ");
+
+       QString ret = buildSelect(maxLevel, columns, false, false);
+
+       const DataType *maxLevelDataType = dataType(maxLevel);
+       ret += QString{"\n\tWHERE %1 = :id"}.arg(valueColumn(maxLevel, maxLevelDataType->alias()));
+
+       qDebug() << "=========================================== primaryValues sql =================================================";
+       qDebug() << ret;
+       qDebug() << "===============================================================================================================";
+       return ret;
+}
+
+void QueryParser::bindValue(QSqlQuery &query, Data *data, int currentLevel) const
+{
+       Q_ASSERT_X(m_valid, "dynamicmodel/query", "Bind value for invalid query");
+       Q_ASSERT_X(currentLevel >= -1 && m_levels.count() >= currentLevel, "dynamicmodel/query", "Bind value for invalid level");
+       if (!data) return;
+
+       qDebug() << "binding" << data->primaryValue() << "on level" << currentLevel;
+       QRegExp rx(QString(":level_%1_value_([0-9]+)").arg(currentLevel));
+       const QString sqlQuery = query.lastQuery();
+       int pos = 0;
+       while ((pos = rx.indexIn(sqlQuery, pos)) != -1)
+       {
+               qDebug() << "WWWWW0" << placeHolder(currentLevel, rx.cap(1).toInt());
+               query.bindValue(placeHolder(currentLevel, rx.cap(1).toInt()), data->primaryValue());
+               pos += rx.matchedLength();
+       }
+}
+
+QString QueryParser::buildChildCountSql(int currentLevel, const QString &aliasSuffix) const
+{
+       if (currentLevel >= levels() - 1)
+               return "0";
+
+       const Level &nextLevel = level(currentLevel + 1);
+       const DataType *nextLeveldataType = m_dataModel->dataType(nextLevel.table);
+
+       QString countColumn = QString{"count(DISTINCT %1)"}
+                       .arg(valueColumn(currentLevel + 1, nextLeveldataType->alias() + aliasSuffix));
+
+       QString query = buildSelect(currentLevel + 1, countColumn, false, true, aliasSuffix);
+       if (currentLevel >= 0)
+       {
+               const Level &lastLevel = level(currentLevel);
+               const DataType *lastLeveldataType = m_dataModel->dataType(lastLevel.table);
+               query.replace(currentPlaceHolder(currentLevel), valueColumn(currentLevel, lastLeveldataType->alias() /*+ aliasSuffix*/));
+       }
+       return query;
+}
+
+QString QueryParser::buildEntrySqlInternal(int currentLevel) const
+{
+       const Level &lastLevel = level(currentLevel);
+       const DataType *dataType = m_dataModel->dataType(lastLevel.table);
+
+       QString columns = QString("(%1)").arg(buildChildCountSql(currentLevel));
+
+       if (!lastLevel.column.isEmpty())
+       {
+               columns += QString(", %2.%1")
+                               .arg(lastLevel.column).arg(dataType->alias());
+       }
+       else
+       {
+               QString additionalColumns = dataType->additionalColumns();
+               if (!additionalColumns.isEmpty())
+               {
+                       columns += QString(", %1").arg(additionalColumns);
+               }
+       }
+
+       QString ret = buildSelect(currentLevel, columns, true, true);
+       ret += QString{"\n\t\tWHERE %1.%2 = :id"}
+                       .arg(dataType->alias())
+                       .arg(dataType->primaryKeyName());
+       return ret;
+}
+
+QString QueryParser::buildSelect(int currentLevel, const QString &columns, bool willRequireAdditionalJoins, bool includeConditions, const QString &aliasSuffix) const
+{
+       const Level &lastLevel = level(currentLevel);
+       const DataType *dataType = m_dataModel->dataType(lastLevel.table);
+       const QString joins = buildJoins(currentLevel - 1, willRequireAdditionalJoins, includeConditions, aliasSuffix);
+       return QString("\nSELECT DISTINCT %4 FROM %1 %2%3")
+                       .arg(lastLevel.table).arg(dataType->alias() + aliasSuffix).arg(joins).arg(columns);
+}
+
+QString QueryParser::buildJoins(int currentLevel, bool willRequireAdditionalJoins, bool includeConditions, const QString &aliasSuffix) const
+{
+       QMap<QString, QStringList> conditions;
+
+       // main table is the one in FROM
+       const QString &mainTable = level(currentLevel + 1).table;
+
+       for (int i = currentLevel; i >= 0; --i)
+       {
+               const QString &nextTable = level(i + 1).table;
+               const QString &table = level(i).table;
+
+               auto it = conditions.find(table);
+               const DataType *dataType = m_dataModel->dataType(level(i).table);
+
+               if (it == conditions.end())
+               {
+                       it = conditions.insert(table, QStringList());
+                       if (table != nextTable)
+                       {
+                               const TypeRelation *rel = m_dataModel->typeRelation(table, nextTable);
+                               const DataType *nextDataType = m_dataModel->dataType(rel->destinationType());
+
+                               *it << rel->joinCondition(dataType->alias() + aliasSuffix, nextDataType->alias() + aliasSuffix);
+                       }
+               }
+
+               if (!includeConditions)
+                       continue;
+
+               if (level(i).type == ColumnEntry)
+               {
+                       *it << QString("%1.%2 = %3")
+                                       .arg(dataType->alias() + aliasSuffix).arg(level(i).column).arg(nextPlaceHolder(i));
+               }
+               else
+               {
+                       *it << QString("%1.%2 = %3")
+                                       .arg(dataType->alias() + aliasSuffix).arg(dataType->primaryKeyName()).arg(nextPlaceHolder(i));
+               }
+       }
+       qDebug() << conditions;
+
+       QString ret;
+       QSet<QString> addedTables;
+       addedTables.insert(mainTable);
+       for (int i = currentLevel; i >= 0; --i)
+       {
+               const Level &l = level(i);
+               if (!addedTables.contains(l.table) && conditions.contains(l.table))
+               {
+                       const DataType *dataType = m_dataModel->dataType(l.table);
+                       ret += QString("\n\tJOIN %1 %2 ON %3\n").arg(l.table).arg(dataType->alias() + aliasSuffix).arg(conditions[l.table].join("\n\t\tAND "));
+                       addedTables.insert(l.table);
+               }
+       }
+
+       if (willRequireAdditionalJoins)
+       {
+               const QString additionalJoins = m_dataModel->dataType(mainTable)->additionalJoins();
+               if (!additionalJoins.isEmpty())
+                       ret += QString("\n\t%1\n").arg(additionalJoins);
+       }
+
+       if (conditions.contains(mainTable))
+       {
+               ret += QString("\n\tWHERE %1\n").arg(conditions[mainTable].join("\n\t\tAND "));
+       }
+       return ret;
+}
+
+QString QueryParser::valueColumn(int currentLevel, const QString &alias) const
+{
+       const Level &lastLevel = level(currentLevel);
+       QString column = QString{"%2.%1"};
+       if (lastLevel.type == ColumnEntry)
+       {
+               return column
+                               .arg(lastLevel.column).arg(alias);
+       }
+       const DataType *dataType = m_dataModel->dataType(lastLevel.table);
+       return column
+                       .arg(dataType->primaryKeyName()).arg(alias);
+}
+
+QString QueryParser::currentPlaceHolder(int currentLevel) const
+{
+       return placeHolder(currentLevel, m_placeholderUse[currentLevel]);
+}
+
+QString QueryParser::nextPlaceHolder(int currentLevel) const
+{
+       return placeHolder(currentLevel, ++m_placeholderUse[currentLevel]);
+}
+
+QString QueryParser::placeHolder(int currentLevel, int i) const
+{
+       return QString(":level_%1_value_%2").arg(currentLevel).arg(i);
+}
+
+void QueryParser::resetPlaceHolderUse() const
+{
+       m_placeholderUse.fill(0, levels());
+}
+
+bool QueryParser::isValid() const
+{
+       return m_valid;
+}
+
+int QueryParser::levels() const
+{
+       return m_levels.count();
+}
+
+const QueryParser::Level &QueryParser::level(int i) const
+{
+       Q_ASSERT_X(i >= 0 && m_levels.count() >= i, "dynamicmodel/query", "Requestesd invlaid level index");
+       return m_levels[i];
+}
+
+QString QueryParser::query() const
+{
+       return m_queryString;
+}
+
+QString QueryParser::errorString() const
+{
+       return m_errorString;
+}
+
+DataModel *QueryParser::dataModel() const
+{
+       return m_dataModel;
+}
+
+DataType *QueryParser::dataType(int currentLevel) const
+{
+       const Level l = level(currentLevel);
+       if (l.type == ColumnEntry)
+               return m_dataModel->dataType("column");
+       return m_dataModel->dataType(l.table);
+}
+
+bool operator ==(const QueryParser &a, const QueryParser &b)
+{
+       return a.m_dataModel == b.m_dataModel && a.m_queryString == b.m_queryString;
+}
+
+} // namespace DynamicModel
+} // namespace LocalMyList
diff --git a/localmylist/dynamicmodel/queryparser.h b/localmylist/dynamicmodel/queryparser.h
new file mode 100644 (file)
index 0000000..0b5db34
--- /dev/null
@@ -0,0 +1,79 @@
+#ifndef QUERYPARSER_H
+#define QUERYPARSER_H
+
+#include "localmylist_global.h"
+#include <QString>
+#include <QVector>
+#include <QSqlQuery>
+#include "dynamicmodel/datamodel.h"
+#include "dynamicmodel/data.h"
+
+namespace LocalMyList {
+namespace DynamicModel {
+
+// TODO split this class into a (model) Query Parser and a (SQL)Query builder
+class LOCALMYLISTSHARED_EXPORT QueryParser
+{
+public:
+       enum EntryType {
+               TableEntry,
+               ColumnEntry,
+       };
+
+       struct Level {
+               EntryType type;
+               QString table;
+               QString column;
+               QString tableAlias;
+       };
+
+       QueryParser(DataModel *dataModel = 0);
+
+       bool parse(const QString &rawPath);
+
+       QString buildSql(int currentLevel) const;
+       QString buildCountSql(int currentLevel) const;
+       QString buildEntrySql(int currentLevel) const;
+       QString buildPrimaryValuesSql(int maxLevel) const;
+       void bindValue(QSqlQuery &query, Data *data, int currentLevel) const;
+
+       bool isValid() const;
+       int levels() const;
+       const Level &level(int i) const;
+
+       QString query() const;
+       QString errorString() const;
+       DataModel *dataModel() const;
+       DataType *dataType(int currentLevel) const;
+
+       friend bool operator ==(const QueryParser& a, const QueryParser& b);
+
+private:
+       QString buildChildCountSql(int currentLevel, const QString &aliasSuffix = "2") const;
+       QString buildEntrySqlInternal(int currentLevel) const;
+       QString buildJoins(int currentLevel, bool willRequireAdditionalJoins, bool includeConditions, const QString &aliasSuffix = QString{}) const;
+       QString buildSelect(int currentLevel, const QString &columns, bool willRequireAdditionalJoins, bool includeConditions, const QString &aliasSuffix = QString{}) const;
+
+       QString valueColumn(int currentLevel, const QString &alias) const;
+       QString currentPlaceHolder(int currentLevel) const;
+       QString nextPlaceHolder(int currentLevel) const;
+       QString placeHolder(int currentLevel, int i) const;
+       void resetPlaceHolderUse() const;
+
+       bool m_valid;
+       QString m_queryString;
+       QString m_errorString;
+       QVector<Level> m_levels;
+       mutable QVector<int> m_placeholderUse;
+       DataModel *m_dataModel;
+
+};
+
+
+QDebug operator<<(QDebug dbg, const QueryParser::Level &l);
+
+} // namespace DynamicModel
+} // namespace LocalMyList
+
+
+#endif // QUERYPARSER_H
index 5889df6c13ba694f5a10b6af49c459adaea59b8a..6aa5c9fbc2890deb35a727978349477518af09d9 100644 (file)
@@ -1,12 +1,12 @@
 #include "typerelation.h"
 
-#include "../mylist.h"
-#include "../database.h"
-#include "../databaseclasses.h"
-#include "node.h"
-#include "datatype.h"
-#include "data.h"
-#include "types.h"
+#include "dynamicmodel/node.h"
+#include "dynamicmodel/datatype.h"
+#include "dynamicmodel/data.h"
+#include "dynamicmodel/types.h"
+#include "mylist.h"
+#include "database.h"
+#include "databaseclasses.h"
 
 #include <QDebug>
 
@@ -17,531 +17,41 @@ TypeRelation::TypeRelation(QObject *parent) : QObject(parent), m_dataType(0)
 {
 }
 
-IdList TypeRelation::getParents(int id)
-{
-       Q_UNUSED(id);
-       return IdList();
-}
+QString TypeRelation::joinCondition(const QString &, const QString &) const { return {}; }
+
 
 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());
-
-       qDebug() << "relation" << rel->sourceType() << rel->destinationType();
-       if (!rel)
-               return zeroQuery;
-
-       return rel->rowCountQuery();
-}
-
 // ===========================================================================
 
-RootAnimeRelation::RootAnimeRelation(QObject *parent) : TypeRelation(parent)
-{
-}
-
-QString RootAnimeRelation::sourceType() const
-{
-       return QString();
-}
-
-QString RootAnimeRelation::destinationType() const
-{
-       return "anime";
-}
-
-NodeList RootAnimeRelation::getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory)
-{
-       Q_UNUSED(parent);
-
-       QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString(
-       "SELECT %2, "
-       "%1 "
-       "ORDER BY title_romaji ASC "
-       "LIMIT :limit "
-       "OFFSET :offset ")
-       .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)";
-}
-
-// =================================================
-
-RootAnimeTitleRelation::RootAnimeTitleRelation(QObject *parent) : TypeRelation(parent)
+ForeignKeyRelation::ForeignKeyRelation(const QString &left, const QString &right, const QString &pk, const QString &fk, QObject *parent)
+       : TypeRelation{parent}, m_left{left}, m_right{right}, m_pk{pk}, m_fk{fk}
 {
 }
 
-QString RootAnimeTitleRelation::sourceType() const
+ForeignKeyRelation::ForeignKeyRelation(const QString &left, const QString &right, const QString &pk, QObject *parent)
+       : TypeRelation{parent}, m_left{left}, m_right{right}, m_pk{pk}, m_fk{pk}
 {
-       return QString();
 }
 
-QString RootAnimeTitleRelation::destinationType() const
+QString ForeignKeyRelation::sourceType() const
 {
-       return "anime_title";
+       return m_left;
 }
 
-NodeList RootAnimeTitleRelation::getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory)
+QString ForeignKeyRelation::destinationType() const
 {
-       Q_UNUSED(parent);
-
-       QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString(
-       "SELECT %2, %1 "
-       "       ORDER BY title ASC "
-       "LIMIT :limit "
-       "OFFSET :offset ")
-       .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 RootAnimeTitleRelation::rowCountQuery() const
-{
-       return "(SELECT COUNT(title_id) FROM anime_title)";
-}
-
-// =================================================
-
-
-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;
-       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 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)";
-}
-
-IdList AnimeEpisodeRelation::getParents(int id)
-{
-       QSqlQuery &q = MyList::instance()->database()->prepare(
-       "SELECT e.aid FROM episode e WHERE e.eid = :eid");
-       q.bindValue(":eid", id);
-
-       IdList ret;
-
-       if (!q.exec())
-               return ret;
-
-       while (q.next())
-               ret << q.value(0).toInt();
-
-       q.finish();
-
-       return ret;
-}
-
-// =================================================
-
-AnimeAnimeTitleRelation::AnimeAnimeTitleRelation(QObject *parent) : TypeRelation(parent)
-{
-}
-
-QString AnimeAnimeTitleRelation::sourceType() const
-{
-       return "anime";
-}
-
-QString AnimeAnimeTitleRelation::destinationType() const
-{
-       return "anime_title";
-}
-
-NodeList AnimeAnimeTitleRelation::getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory)
-{
-       if (!parent)
-               return NodeList();
-
-       QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString(
-       "SELECT "
-       "       %2, "
-       "       %1 "
-       "       WHERE at.aid = :aid "
-       "       ORDER BY at.type ASC, at.title 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 AnimeAnimeTitleRelation::rowCountQuery() const
-{
-       return "(SELECT count(title_id) FROM anime_title 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) ";
-}
-
-// =================================================
-
-FileFileLocationRelation::FileFileLocationRelation(QObject *parent) : TypeRelation(parent)
-{
-
-}
-
-QString FileFileLocationRelation::sourceType() const
-{
-       return "file";
-}
-
-QString FileFileLocationRelation::destinationType() const
-{
-       return "file_location";
-}
-
-NodeList FileFileLocationRelation::getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory)
-{
-       if (!parent)
-               return NodeList();
-
-       QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString(
-       "SELECT %2, %1 "
-       "       WHERE fl.fid = :fid "
-       "       LIMIT :limit "
-       "       OFFSET :offset ").arg(dataType()->baseQuery(), childRowCountQuery(rowCountType)));
-       q.bindValue(":fid", 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 FileFileLocationRelation::rowCountQuery() const
-{
-       return "(SELECT COUNT(location_id) FROM file_location WHERE fid = f.fid)";
-}
-
-// =================================================
-
-AnimeTitleAnimeRelation::AnimeTitleAnimeRelation(QObject *parent) : TypeRelation(parent)
-{
-}
-
-QString AnimeTitleAnimeRelation::sourceType() const
-{
-       return "anime_title";
-}
-
-QString AnimeTitleAnimeRelation::destinationType() const
-{
-       return "anime";
-}
-
-NodeList AnimeTitleAnimeRelation::getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory)
-{
-       if (!parent)
-               return NodeList();
-
-       int aid = static_cast<AnimeTitleData *>(parent)->animeTitleData.aid;
-
-       QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString(
-       "SELECT %2, %1 "
-       "       WHERE a.aid = :aid "
-       "       LIMIT :limit "
-       "       OFFSET :offset ").arg(dataType()->baseQuery(), childRowCountQuery(rowCountType)));
-       q.bindValue(":aid", aid);
-       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 AnimeTitleAnimeRelation::rowCountQuery() const
-{
-       return "(SELECT COUNT(aid) FROM anime WHERE aid = at.aid)";
-}
-
-// =================================================
-
-AnimeTitleEpisodeRelation::AnimeTitleEpisodeRelation(QObject *parent) : TypeRelation(parent)
-{
-}
-
-QString AnimeTitleEpisodeRelation::sourceType() const
-{
-       return "anime_title";
-}
-
-QString AnimeTitleEpisodeRelation::destinationType() const
-{
-       return "episode";
-}
-
-NodeList AnimeTitleEpisodeRelation::getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory)
-{
-       if (!parent)
-               return NodeList();
-
-       int aid = static_cast<AnimeTitleData *>(parent)->animeTitleData.aid;
-
-       QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString(
-       "SELECT %2, %1 "
-       "       WHERE e.aid = :aid "
-       "       LIMIT :limit "
-       "       OFFSET :offset ").arg(dataType()->baseQuery(), childRowCountQuery(rowCountType)));
-       q.bindValue(":aid", aid);
-       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;
+       return m_right;
 }
 
-QString AnimeTitleEpisodeRelation::rowCountQuery() const
+QString ForeignKeyRelation::joinCondition(const QString &leftAlias, const QString &rightAlias) const
 {
-       return "(SELECT COUNT(eid) FROM episode WHERE aid = at.aid)";
+       return QString{"%2.%3 = %1.%4"}
+               .arg(rightAlias).arg(leftAlias).arg(m_pk).arg(m_fk);
 }
 
 } // namespace DynamicModel
index 63b6263b874e6afb30fe70d179f82a17652b13c7..ba612e7f423a6e12e4fc37d34c365e76616f3aba 100644 (file)
@@ -22,173 +22,31 @@ public:
 
        virtual QString sourceType() const = 0;
        virtual QString destinationType() const = 0;
-
-       virtual NodeList getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory) = 0;
-       virtual QString rowCountQuery() const = 0;
-       virtual IdList getParents(int id);
+       virtual QString joinCondition(const QString &leftAlias, const QString &rightAlias) const;
 
        DataType *dataType() const;
 
-protected:
-       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 RootAnimeTitleRelation : public TypeRelation
-{
-       Q_OBJECT
-public:
-       RootAnimeTitleRelation(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;
-       IdList getParents(int id);
-};
-
-// =========================================================================================================
-
-class LOCALMYLISTSHARED_EXPORT AnimeAnimeTitleRelation : public TypeRelation
-{
-       Q_OBJECT
-public:
-       AnimeAnimeTitleRelation(QObject *parent);
-
-       QString sourceType() const;
-       QString destinationType() const;
-
-       NodeList getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory);
-       QString rowCountQuery() const;
-};
-
-// =========================================================================================================
-
-class LOCALMYLISTSHARED_EXPORT EpisodeFileRelation : public TypeRelation
-{
-       Q_OBJECT
-public:
-       EpisodeFileRelation(QObject *parent);
-
-       QString sourceType() const;
-       QString destinationType() const;
-
-       NodeList getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory);
-       QString rowCountQuery() const;
-};
-
-// =========================================================================================================
-
-/*
-class LOCALMYLISTSHARED_EXPORT EpisodeFileLocationRelation : public TypeRelation
+class LOCALMYLISTSHARED_EXPORT ForeignKeyRelation : public TypeRelation
 {
-       Q_OBJECT
 public:
-       EpisodeFileLocationRelation(QObject *parent);
-
-       QString sourceType() const;
-       QString destinationType() const;
+       ForeignKeyRelation(const QString &left, const QString &right, const QString &pk, const QString &fk, QObject *parent = 0);
+       ForeignKeyRelation(const QString &left, const QString &right, const QString &pk, QObject *parent = 0);
 
-       NodeList getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory);
-       QString rowCountQuery() const;
-};
-*/
+       virtual QString sourceType() const override;
+       virtual QString destinationType() const override;
+       QString joinCondition(const QString &leftAlias, const QString &rightAlias) const override;
 
-// =========================================================================================================
-
-class LOCALMYLISTSHARED_EXPORT FileFileLocationRelation : public TypeRelation
-{
-       Q_OBJECT
-public:
-       FileFileLocationRelation(QObject *parent);
-
-       QString sourceType() const;
-       QString destinationType() const;
-
-       NodeList getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory);
-       QString rowCountQuery() const;
-};
-
-// =========================================================================================================
-
-class LOCALMYLISTSHARED_EXPORT AnimeTitleAnimeRelation : public TypeRelation
-{
-       Q_OBJECT
-public:
-       AnimeTitleAnimeRelation(QObject *parent);
-
-       QString sourceType() const;
-       QString destinationType() const;
-
-       NodeList getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory);
-       QString rowCountQuery() const;
-};
-
-// =========================================================================================================
-
-class LOCALMYLISTSHARED_EXPORT AnimeTitleEpisodeRelation : public TypeRelation
-{
-       Q_OBJECT
-public:
-       AnimeTitleEpisodeRelation(QObject *parent);
-
-       QString sourceType() const;
-       QString destinationType() const;
-
-       NodeList getChildren(Data *parent, int offset, DataType *rowCountType, NodeFactory nodeFactory);
-       QString rowCountQuery() const;
+private:
+       QString m_left;
+       QString m_right;
+       QString m_pk;
+       QString m_fk;
 };
 
 } // namespace DynamicModel
index df77a71b9570f87e6cf3b1821adad0b669546de1..c9e70c004ea93365b85c8a3808c6af2679cd328c 100644 (file)
 #include "types.h"
 
-#include "../database.h"
-#include "../mylist.h"
-#include "datamodel.h"
-#include "typerelation.h"
-#include "data.h"
-#include "node.h"
+#include "dynamicmodel/datamodel.h"
+#include "dynamicmodel/typerelation.h"
+#include "dynamicmodel/data.h"
+#include "dynamicmodel/node.h"
+#include "database.h"
+#include "mylist.h"
 
 #include <QDebug>
 
 namespace LocalMyList {
 namespace DynamicModel {
 
-QString AnimeType::name() const
+QString ColumnType::name() const
 {
-       return "anime";
+       return "column";
 }
 
-QStringList AnimeType::availableChildRelations() const
+QString ColumnType::tableName() const
 {
-       return QStringList();
+       return {};
 }
 
-QString AnimeType::baseQuery() const
+QString ColumnType::alias() const
 {
-       return QString(
-       "               (SELECT COUNT(e.eid) "
-       "                       FROM episode e "
-       "                       WHERE e.aid = a.aid), "
-       "               (SELECT COUNT(DISTINCT eid) "
-       "                       FROM "
-       "                       (SELECT e.eid FROM episode e "
-       "                               JOIN file f ON (f.eid = e.eid) "
-       "                               WHERE e.aid = a.aid "
-       "                                       AND f.my_watched IS NOT NULL "
-       "                       UNION "
-       "                       SELECT e.eid FROM episode e "
-       "                               JOIN file_episode_rel fer ON fer.eid = e.eid "
-       "                               JOIN file f ON f.fid = fer.fid "
-       "                               WHERE e.aid = a.aid "
-       "                                       AND f.my_watched IS NOT NULL) sq), "
-       "               (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 aid = a.aid "
-       "                                       UNION "
-       "                                       SELECT f.my_state "
-       "                                               FROM file f "
-       "                                               JOIN file_episode_rel fer ON (fer.fid = f.eid) "
-       "                                               JOIN episode e ON (e.eid = fer.eid AND e.aid = a.aid) "
-       "                                               ) AS sq) AS sq) AS my_state, "
-       "               %1 "
-       "       FROM anime a ")
-                       .arg(Database::animeFields());
+       return {};
 }
 
-int AnimeType::size() const
+QString ColumnType::primaryKeyName() const
 {
-       return sizeHelper("anime", "aid");
+       return {};
+}
+
+QString ColumnType::additionalColumns() const
+{
+       return {};
+}
+
+NodeCompare ColumnType::nodeCompareFunction() const
+{
+       return [](Node *a, Node *b) -> bool
+       {
+               const ColumnData *aa = static_cast<ColumnData *>(a->data());
+               const ColumnData *ab = static_cast<ColumnData *>(b->data());
+               return aa->value < ab->value;
+       };
+}
+
+Data *ColumnType::readEntry(const SqlResultIteratorInterface &it)
+{
+       auto typedData = new ColumnData(this);
+       typedData->value = it.value(1);
+       return typedData;
+}
+
+// =============================================================================================================
+
+QString AnimeType::tableName() const
+{
+       return "anime";
+}
+
+QString AnimeType::alias() const
+{
+       return "a";
+}
+
+QString AnimeType::primaryKeyName() const
+{
+       return "aid";
+}
+
+QString AnimeType::additionalColumns() const
+{
+       return QString{R"(
+               (SELECT COUNT(e.eid)
+                       FROM episode e
+                       WHERE e.aid = a.aid
+                               AND e.type = ''),
+               (SELECT COUNT(e.eid)
+                       FROM episode e
+                       WHERE e.aid = a.aid
+                               AND e.type <> ''),
+               (SELECT COUNT(DISTINCT eid)
+                       FROM
+                       (SELECT e.eid FROM episode e
+                               JOIN file f ON (f.eid = e.eid)
+                               WHERE e.aid = a.aid
+                                       AND f.my_watched IS NOT NULL
+                                       AND e.type = ''
+                       UNION
+                       SELECT e.eid FROM episode e
+                               JOIN file_episode_rel fer ON fer.eid = e.eid
+                               JOIN file f ON f.fid = fer.fid
+                               WHERE e.aid = a.aid
+                                       AND f.my_watched IS NOT NULL
+                                       AND e.type = '') sq),
+               (SELECT COUNT(DISTINCT eid)
+                       FROM
+                       (SELECT e.eid FROM episode e
+                               JOIN file f ON (f.eid = e.eid)
+                               WHERE e.aid = a.aid
+                                       AND f.my_watched IS NOT NULL
+                                       AND e.type <> ''
+                       UNION
+                       SELECT e.eid FROM episode e
+                               JOIN file_episode_rel fer ON fer.eid = e.eid
+                               JOIN file f ON f.fid = fer.fid
+                               WHERE e.aid = a.aid
+                                       AND f.my_watched IS NOT NULL
+                                       AND e.type <> '') sq),
+               (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 aid = a.aid
+                                       UNION
+                                       SELECT f.my_state
+                                               FROM file f
+                                               JOIN file_episode_rel fer ON (fer.fid = f.eid)
+                                               JOIN episode e ON (e.eid = fer.eid AND e.aid = a.aid)
+                                               ) AS sq) AS sq) AS my_state,
+               (SELECT string_agg(at.title, '''') -- Quotes are replaced by backticks in everything returned by anidb
+                       FROM anime_title at
+                               WHERE at.aid = a.aid AND at.language = 'en') AS alternate_titles,
+               %1
+                               )"}.arg(Database::animeFields());
+}
+
+QString AnimeType::orderBy() const
+{
+       return "a.title_romaji";
 }
 
 void AnimeType::registerd()
 {
        connect(MyList::instance()->database(), SIGNAL(animeUpdate(int)), this, SLOT(animeUpdated(int)));
+       connect(MyList::instance()->database(), SIGNAL(fileUpdate(int,int,int)), this, SLOT(fileUpdated(int,int,int)));
+
+       connect(MyList::instance()->database(), SIGNAL(episodeInsert(int,int)), this, SLOT(episodeAdded(int,int)));
+       connect(MyList::instance()->database(), SIGNAL(fileInsert(int,int,int)), this, SLOT(fileAdded(int,int,int)));
+
+       connect(MyList::instance()->database(), SIGNAL(animeInsert(int)), this, SLOT(animeAdded(int)));
 }
 
 void AnimeType::update(Data *data)
 {
-       QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString(
-       "SELECT 0, %1 "
-       "WHERE aid = :aid ")
-       .arg(baseQuery()));
-       q.bindValue(":aid", data->id());
+       QSqlQuery q = MyList::instance()->database()->prepareOneShot(updateQuery());
+       q.bindValue(":id", data->id());
 
        if (!q.exec())
                return;
@@ -90,6 +165,7 @@ NodeCompare AnimeType::nodeCompareFunction() const
        {
                const AnimeData *aa = static_cast<AnimeData *>(a->data());
                const AnimeData *ab = static_cast<AnimeData *>(b->data());
+               qDebug() << "CMP" << aa->animeData.titleRomaji << ab->animeData.titleRomaji << (aa->animeData.titleRomaji < ab->animeData.titleRomaji);
                return aa->animeData.titleRomaji < ab->animeData.titleRomaji;
        };
 }
@@ -99,6 +175,12 @@ Data *AnimeType::readEntry(const SqlResultIteratorInterface &it)
        return genericReadEntry<AnimeData>(it, fillAnimeData);
 }
 
+void AnimeType::animeAdded(int aid)
+{
+       Q_UNUSED(aid);
+       emit model()->entryAdded(this, aid);
+}
+
 void AnimeType::animeUpdated(int aid)
 {
        const auto it = m_dataStore.find(aid);
@@ -109,24 +191,61 @@ void AnimeType::animeUpdated(int aid)
        update(*it);
 }
 
+void AnimeType::fileUpdated(int fid, int eid, int aid)
+{
+       // TODO this is not perfect because
+       // there may be a secondary anime in file_episode_rel.
+       Q_UNUSED(fid);
+       Q_UNUSED(eid);
+       animeUpdated(aid);
+}
+
+void AnimeType::episodeAdded(int eid, int aid)
+{
+       // When an ep is added the ep count for anime changes
+       Q_UNUSED(eid);
+       animeUpdated(aid);
+}
+
+void AnimeType::fileAdded(int fid, int eid, int aid)
+{
+       // When a file is added the watched ep count for anime can change
+       Q_UNUSED(fid);
+       Q_UNUSED(eid);
+       animeUpdated(aid);
+}
+
 void AnimeType::fillAnimeData(AnimeData &data, const SqlResultIteratorInterface &query)
 {
        data.episodesInMyList = query.value(1).toInt();
-       data.watchedEpisodes = query.value(2).toInt();
-       data.myState = query.value(3).toInt();
-       Database::readAnimeData(query, data.animeData, 4);
+       data.specialsInMyList = query.value(2).toInt();
+       data.watchedEpisodes = query.value(3).toInt();
+       data.watchedSpecials = query.value(4).toInt();
+       data.myState = query.value(5).toInt();
+       data.alternateTitles = query.value(6).toString().split(QChar('\''));
+       Database::readAnimeData(query, data.animeData, 7);
 }
 
 // =============================================================================================================
 
-QString EpisodeType::name() const
+QString EpisodeType::tableName() const
 {
        return "episode";
 }
 
-QString EpisodeType::baseQuery() const
+QString EpisodeType::alias() const
 {
-       return QString(
+       return "e";
+}
+
+QString EpisodeType::primaryKeyName() const
+{
+       return "eid";
+}
+
+QString EpisodeType::additionalColumns() const
+{
+       return QString{
        "       (SELECT MIN(my_watched) "
        "               FROM "
        "                       (SELECT my_watched "
@@ -151,29 +270,32 @@ QString EpisodeType::baseQuery() const
        "                                               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());
+       "       et.ordering, %1 "}
+       .arg(Database::episodeFields());
 }
 
-int EpisodeType::size() const
+QString EpisodeType::orderBy() const
 {
-       return sizeHelper("episode", "eid");
+       return "et.ordering ASC, e.epno ASC";
+}
+
+QString EpisodeType::additionalJoins() const
+{
+       return "JOIN episode_type et ON (et.type = e.type)";
 }
 
 void EpisodeType::registerd()
 {
        connect(MyList::instance()->database(), SIGNAL(episodeUpdate(int,int)), this, SLOT(episodeUpdated(int,int)));
+       connect(MyList::instance()->database(), SIGNAL(fileUpdate(int,int,int)), this, SLOT(fileUpdated(int,int,int)));
+
+       connect(MyList::instance()->database(), SIGNAL(fileInsert(int,int,int)), this, SLOT(fileAdded(int,int,int)));
 }
 
 void EpisodeType::update(Data *data)
 {
-       QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString(
-       "SELECT 0, %1 "
-       "WHERE eid = :eid ")
-       .arg(baseQuery()));
-       q.bindValue(":eid", data->id());
+       QSqlQuery q = MyList::instance()->database()->prepareOneShot(updateQuery());
+       q.bindValue(":id", data->id());
 
        if (!q.exec())
                return;
@@ -212,6 +334,20 @@ void EpisodeType::episodeUpdated(int eid, int aid)
        update(*it);
 }
 
+void EpisodeType::fileUpdated(int fid, int eid, int aid)
+{
+       // TODO this is not perfect because
+       // there may be a secondary episode in file_episode_rel.
+       Q_UNUSED(fid);
+       episodeUpdated(eid, aid);
+}
+
+void EpisodeType::fileAdded(int fid, int eid, int aid)
+{
+       Q_UNUSED(fid);
+       episodeUpdated(eid, aid);
+}
+
 void EpisodeType::fillEpisodeData(EpisodeData &data, const SqlResultIteratorInterface &query)
 {
        data.watchedDate = query.value(1).toDateTime();
@@ -222,34 +358,44 @@ void EpisodeType::fillEpisodeData(EpisodeData &data, const SqlResultIteratorInte
 
 // =============================================================================================================
 
-QString FileType::name() const
+QString FileType::tableName() const
 {
        return "file";
 }
 
-QString FileType::baseQuery() const
+QString FileType::alias() const
+{
+       return "f";
+}
+
+QString FileType::primaryKeyName() const
+{
+       return "fid";
+}
+
+QString FileType::additionalColumns() const
 {
        return QString(
        "%1")
                        .arg(Database::fileFields());
 }
 
-int FileType::size() const
+void FileType::registerd()
 {
-       return sizeHelper("file", "fid");
+       connect(MyList::instance()->database(), SIGNAL(fileUpdate(int,int,int)), this, SLOT(fileUpdated(int,int,int)));
 }
 
 void FileType::update(Data *data)
 {
-       Q_UNUSED(data);
-}
+       QSqlQuery q = MyList::instance()->database()->prepareOneShot(updateQuery());
+       q.bindValue(":id", data->id());
 
-void FileType::childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation)
-{
-       Q_UNUSED(parentData);
-       Q_UNUSED(oldData);
-       Q_UNUSED(newData);
-       Q_UNUSED(operation);
+       if (!q.exec())
+               return;
+
+       QSqlResultIterator it(q);
+
+       genericUpdate<FileData>(data, it, fillFileData);
 }
 
 NodeCompare FileType::nodeCompareFunction() const
@@ -265,6 +411,18 @@ Data *FileType::readEntry(const SqlResultIteratorInterface &it)
        return genericReadEntry<FileData>(it, fillFileData);
 }
 
+void FileType::fileUpdated(int fid, int eid, int aid)
+{
+       Q_UNUSED(aid);
+       Q_UNUSED(eid);
+       const auto it = m_dataStore.find(fid);
+
+       if (it == m_dataStore.constEnd())
+               return;
+
+       update(*it);
+}
+
 void FileType::fillFileData(FileData &data, const SqlResultIteratorInterface &query)
 {
        Database::readFileData(query, data.fileData, 1);
@@ -272,23 +430,31 @@ void FileType::fillFileData(FileData &data, const SqlResultIteratorInterface &qu
 
 // =============================================================================================================
 
-QString FileLocationType::name() const
+QString FileLocationType::tableName() const
 {
        return "file_location";
 }
 
-QString FileLocationType::baseQuery() const
+QString FileLocationType::alias() const
+{
+       return "fl";
+}
+
+QString FileLocationType::primaryKeyName() const
+{
+       return "location_id";
+}
+
+QString FileLocationType::additionalColumns() const
 {
        return QString(
-       "h.name, %1 "
-       "       FROM file_location fl "
-       "       JOIN host h ON (fl.host_id = h.host_id) ")
+       "h.name, %1 ")
                        .arg(Database::fileLocationFields());
 }
 
-int FileLocationType::size() const
+QString FileLocationType::additionalJoins() const
 {
-       return sizeHelper("file_location", "location_id");
+       return "JOIN host h ON (fl.host_id = h.host_id)";
 }
 
 void FileLocationType::update(Data *data)
@@ -296,14 +462,6 @@ void FileLocationType::update(Data *data)
        Q_UNUSED(data);
 }
 
-void FileLocationType::childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation)
-{
-       Q_UNUSED(parentData);
-       Q_UNUSED(oldData);
-       Q_UNUSED(newData);
-       Q_UNUSED(operation);
-}
-
 NodeCompare FileLocationType::nodeCompareFunction() const
 {
        return [](Node *a, Node *b) -> bool
@@ -325,35 +483,31 @@ void FileLocationType::fillFileLocationData(FileLocationData &data, const SqlRes
 
 // =============================================================================================================
 
-QString AnimeTitleType::name() const
+QString AnimeTitleType::tableName() const
 {
        return "anime_title";
 }
 
-QString AnimeTitleType::baseQuery() const
+QString AnimeTitleType::alias() const
 {
-       return QString(
-       "%1 "
-       "       FROM anime_title at ")
-                       .arg(Database::animeTitleFields());
+       return "at";
 }
 
-int AnimeTitleType::size() const
+QString AnimeTitleType::primaryKeyName() const
 {
-       return sizeHelper("anime_title", "title");
+       return "title_id";
 }
 
-void AnimeTitleType::update(Data *data)
+QString AnimeTitleType::additionalColumns() const
 {
-       Q_UNUSED(data);
+       return QString(
+       "%1 ")
+                       .arg(Database::animeTitleFields());
 }
 
-void AnimeTitleType::childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation)
+void AnimeTitleType::update(Data *data)
 {
-       Q_UNUSED(parentData);
-       Q_UNUSED(oldData);
-       Q_UNUSED(newData);
-       Q_UNUSED(operation);
+       Q_UNUSED(data);
 }
 
 NodeCompare AnimeTitleType::nodeCompareFunction() const
index 1698970e3fb73eba7b30384c2bda413a0a5843b7..47e010e67d83358556226c851a2f3ac4e73868d9 100644 (file)
@@ -9,44 +9,38 @@
 
 namespace LocalMyList {
 namespace DynamicModel {
-/*
-class LOCALMYLISTSHARED_EXPORT RootType : public DataType
-{
-       QString name() const;
-       QStringList availableChildRelations() const;
-
-       QString baseQuery() const;
 
-       int size() const;
+class LOCALMYLISTSHARED_EXPORT ColumnType : public DataType
+{
+       Q_OBJECT
 
-       void update(Data *data);
-       void childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation);
+       QString name() const override;
+       QString tableName() const override;
+       QString alias() const override;
+       QString primaryKeyName() const override;
+       QString additionalColumns() const override;
 
        NodeCompare nodeCompareFunction() const;
 
+protected:
        Data *readEntry(const SqlResultIteratorInterface &it);
-
-private:
-       void fillAnimeData(AnimeData &data, const SqlResultIteratorInterface &query);
 };
-*/
+
 // =============================================================================================================
 
 class LOCALMYLISTSHARED_EXPORT AnimeType : public DataType
 {
        Q_OBJECT
 
-       QString name() const;
-       QStringList availableChildRelations() const;
-
-       QString baseQuery() const;
+       QString tableName() const override;
+       QString alias() const override;
+       QString primaryKeyName() const override;
+       QString additionalColumns() const override;
+       QString orderBy() const override;
 
-       int size() const;
+       void registerd() override;
 
-       void registerd();
-
-       void update(Data *data);
-       void childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation);
+       void update(Data *data) override;
 
        NodeCompare nodeCompareFunction() const;
 
@@ -54,7 +48,12 @@ protected:
        Data *readEntry(const SqlResultIteratorInterface &it);
 
 private slots:
+       void animeAdded(int aid);
+
        void animeUpdated(int aid);
+       void fileUpdated(int fid, int eid, int aid);
+       void episodeAdded(int eid, int aid);
+       void fileAdded(int fid, int eid, int aid);
 
 private:
        static void fillAnimeData(AnimeData &data, const SqlResultIteratorInterface &query);
@@ -66,24 +65,27 @@ class LOCALMYLISTSHARED_EXPORT EpisodeType : public DataType
 {
        Q_OBJECT
 
-       QString name() const;
+       QString tableName() const override;
+       QString alias() const override;
+       QString primaryKeyName() const override;
+       QString additionalColumns() const override;
+       QString orderBy() const override;
+       QString additionalJoins() const override;
 
-       QString baseQuery() const;
+       void registerd() override;
 
-       int size() const;
-
-       void registerd();
-
-       void update(Data *data);
+       void update(Data *data) override;
        void added(int id);
 
        NodeCompare nodeCompareFunction() const;
 
 protected:
-       Data *readEntry(const SqlResultIteratorInterface &it);
+       Data *readEntry(const SqlResultIteratorInterface &it) override;
 
 private slots:
        void episodeUpdated(int eid, int aid);
+       void fileUpdated(int fid, int eid, int aid);
+       void fileAdded(int fid, int eid, int aid);
 
 private:
        static void fillEpisodeData(EpisodeData &data, const SqlResultIteratorInterface &query);
@@ -95,16 +97,21 @@ class LOCALMYLISTSHARED_EXPORT FileType : public DataType
 {
        Q_OBJECT
 
-       QString name() const;
-       QString baseQuery() const;
-       int size() const;
+       QString tableName() const override;
+       QString alias() const override;
+       QString primaryKeyName() const override;
+       QString additionalColumns() const override;
 
-       void update(Data *data);
-       void childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation);
+       void registerd() override;
+
+       void update(Data *data) override;
 
        NodeCompare nodeCompareFunction() const;
 
-       Data *readEntry(const SqlResultIteratorInterface &it);
+       Data *readEntry(const SqlResultIteratorInterface &it) override;
+
+private slots:
+       void fileUpdated(int fid, int eid, int aid);
 
 private:
        static void fillFileData(FileData &data, const SqlResultIteratorInterface &query);
@@ -116,16 +123,17 @@ class LOCALMYLISTSHARED_EXPORT FileLocationType : public DataType
 {
        Q_OBJECT
 
-       QString name() const;
-       QString baseQuery() const;
-       int size() const;
+       QString tableName() const;
+       QString alias() const;
+       QString primaryKeyName() const override;
+       QString additionalColumns() const;
+       QString additionalJoins() const override;
 
-       void update(Data *data);
-       void childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation);
+       void update(Data *data) override;
 
        NodeCompare nodeCompareFunction() const;
 
-       Data *readEntry(const SqlResultIteratorInterface &it);
+       Data *readEntry(const SqlResultIteratorInterface &it) override;
 
 private:
        static void fillFileLocationData(FileLocationData &data, const SqlResultIteratorInterface &query);
@@ -137,17 +145,16 @@ class LOCALMYLISTSHARED_EXPORT AnimeTitleType : public DataType
 {
        Q_OBJECT
 
-       QString name() const;
-       QString baseQuery() const;
+       QString tableName() const;
+       QString alias() const;
+       QString primaryKeyName() const override;
+       QString additionalColumns() const;
 
-       int size() const;
-
-       void update(Data *data);
-       void childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation);
+       void update(Data *data) override;
 
        NodeCompare nodeCompareFunction() const;
 
-       Data *readEntry(const SqlResultIteratorInterface &it);
+       Data *readEntry(const SqlResultIteratorInterface &it) override;
 
 private:
        static void fillAnimeTitleData(AnimeTitleData &data, const SqlResultIteratorInterface &query);
index e36ade55fb3fe01269dba67a49a5396f6190816d..90ad93ad50693da932b7d7fb7a4d16ca3e7e6b2a 100644 (file)
@@ -44,8 +44,9 @@ SOURCES += \
        dynamicmodel/types.cpp \
        dynamicmodel/datamodel.cpp \
        dynamicmodel/typerelation.cpp \
-    dynamicmodel/query.cpp \
-    dynamicmodel/entry.cpp
+       dynamicmodel/query.cpp \
+       dynamicmodel/queryparser.cpp \
+       dynamicmodel/entry.cpp
 
 HEADERS += \
        localmylist_global.h \
@@ -84,8 +85,9 @@ HEADERS += \
        dynamicmodel/types.h \
        dynamicmodel/datamodel.h \
        dynamicmodel/typerelation.h \
-    dynamicmodel/query.h \
-    dynamicmodel/entry.h
+       dynamicmodel/query.h \
+       dynamicmodel/queryparser.h \
+       dynamicmodel/entry.h
 
 CONV_HEADERS += \
        include/LocalMyList/AbstractTask \
@@ -129,6 +131,8 @@ CONV_HEADERS += \
        DEFINES += LOCALMYLIST_NO_ANIDBUDPCLIENT
 }
 
+INCLUDEPATH += .
+
 REV = $$system(git show-ref --head -s HEAD)
 DEFINES += REVISION=\"$${REV}\"
 
index cd57eb4ff3153297a5d21cee104eb1b8d4e44210..9361ad788bfb51a8b660031d3b17a8183fb13beb 100644 (file)
@@ -465,12 +465,13 @@ bool MyListAnimeNode::setData(int column, const QVariant &data, int role)
 void MyListAnimeNode::fetchMore()
 {
        qDebug() << "fetching some more for aid" << id();
-       query->prepare(QString(
-       " %1 "
-       "       WHERE e.aid = :aid "
-       "       ORDER BY et.ordering ASC, e.epno ASC "
-       "       LIMIT :limit "
-       "       OFFSET :offset ").arg(MyListEpisodeNode::baseQuery()));
+       query->prepare(QString(R"(
+       %1
+       WHERE e.aid = :aid
+       ORDER BY et.ordering ASC, e.epno ASC
+       LIMIT :limit
+       OFFSET :offset
+       )").arg(MyListEpisodeNode::baseQuery()));
        query->bindValue(":aid", id());
        query->bindValue(":limit", LIMIT);
        query->bindValue(":offset", childCount());
index fdb76313b8fbf518851d34106e872a882fafb299..461fbfc12573ddeb646266b999635ab28feaeb8e 100644 (file)
@@ -57,6 +57,11 @@ int SqlAsyncQuery::indexOf(const QString &name) const
        return d->indexOf(name);
 }
 
+QString SqlAsyncQuery::fieldName(int i) const
+{
+       return d->fieldName(i);
+}
+
 void SqlAsyncQuery::finish()
 {
        return d->finish();
index 2ee63638a4883c8572683cfe2f62f86b450faa0a..cdc3e49dc73214376f8cc3c1c5f010952b76f677 100644 (file)
@@ -36,6 +36,7 @@ public:
        QVariant value(int index) const;
        QVariant value(const QString &name) const;
        int indexOf(const QString &name ) const;
+       QString fieldName(int i) const override;
        void finish();
 
        bool isWorking() const;
index 2f74c6cadf4afde81030b52c58a02840b723f87a..9016c075bf03c72a4ebd9c4b74b47ecec9f585c2 100644 (file)
@@ -120,6 +120,11 @@ int SqlAsyncQueryInternal::indexOf(const QString &name) const
        return result->fieldNames.indexOf(name);
 }
 
+QString SqlAsyncQueryInternal::fieldName(int i) const
+{
+       return result->fieldNames[i];
+}
+
 void SqlAsyncQueryInternal::finish()
 {
        if (working)
index 92b67e0ef6cae3e9e741678b371c592cb2aa42a3..58584b6652b48cb914b7f237b2efefaeeb77482a 100644 (file)
@@ -55,6 +55,7 @@ public:
        QVariant value(int index) const;
        QVariant value(const QString &name) const;
        int indexOf(const QString &name) const;
+       QString fieldName(int i) const;
        void finish();
 
        QString executedQuery() const;
index e9dea89f044697b1770e6a2772dc1371fe575b72..c50b3cfc86c972da3e65dbee6a672126829a6760 100644 (file)
@@ -11,7 +11,8 @@ public:
        virtual bool next() = 0;
        virtual QVariant value(int index) const = 0;
        virtual QVariant value(const QString &name) const = 0;
-       virtual int indexOf(const QString &name ) const = 0;
+       virtual int indexOf(const QString &name) const = 0;
+       virtual QString fieldName(int i) const = 0;
 };
 
 } // namespace LocalMyList
index d62dccab0617308179ab06684fa2bad4accd8032..f1365da4e0b816eb8b72b1d7a69866da6516fa33 100644 (file)
@@ -5,11 +5,13 @@
 #include <QUrl>
 #include "mylist.h"
 #include "settings.h"
-#include "queryparser.h"
+#include "dynamicmodel/queryparser.h"
+#include "dynamicmodel/types.h"
+#include "dynamicmodel/typerelation.h"
 
 #include <QDebug>
-
 using namespace LocalMyList;
+using namespace LocalMyList::DynamicModel;
 
 int main(int argc, char *argv[])
 {
@@ -29,11 +31,31 @@ int main(int argc, char *argv[])
        }
 
        DataModel d{};
+       d.registerDataType(new DynamicModel::AnimeType);
+       d.registerDataType(new DynamicModel::EpisodeType);
+       d.registerDataType(new DynamicModel::FileType);
+       qDebug() << d.registerTypeRelation(new ForeignKeyRelation("anime", "episode", "aid"));
+       qDebug() << d.registerTypeRelation(new ForeignKeyRelation("episode", "anime", "aid"));
+       qDebug() << d.registerTypeRelation(new ForeignKeyRelation("anime", "file", "aid"));
+       qDebug() << d.registerTypeRelation(new ForeignKeyRelation("file", "anime", "aid"));
+       qDebug() << d.registerTypeRelation(new ForeignKeyRelation("episode", "file", "eid"));
+       qDebug() << d.registerTypeRelation(new ForeignKeyRelation("file", "episode", "eid"));
        QueryParser p{&d};
 
        bool success = p.parse(a.arguments()[1]);
 
        qDebug() << "Success" << success;
+       if (!success)
+       {
+               qDebug() << p.errorString();
+               return 1;
+       }
+
+       for (int i = 0; i < p.levels(); ++i) {
+               qDebug() << "====" << p.query() << "level" << (i+1) << "(of" << p.levels() << ") ===";
+               qDebug() << p.buildSql(i);
+       }
+
 
        return !success;
 }
index 7112bc25e0d96d8490d340c2627d683a6d45df5e..a98c55f08dbeedaa0cc90bc1e38855f59811a0ff 100644 (file)
@@ -11,7 +11,6 @@ CONFIG -= app_bundle
 TEMPLATE = app
 
 SOURCES += main.cpp \
-       queryparser.cpp \
        tabledata.cpp
 
 include(../localmylist.pri)
@@ -20,5 +19,4 @@ target.path = $${PREFIX}/bin
 INSTALLS += target
 
 HEADERS += \
-       queryparser.h \
        tabledata.h
diff --git a/query-test/queryparser.cpp b/query-test/queryparser.cpp
deleted file mode 100644 (file)
index 4a80626..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-#include "queryparser.h"
-#include <QStringList>
-#include "tabledata.h"
-//#include "conversions.h"
-
-#include <QDebug>
-
-
-QueryParser::QueryParser(DataModel *dataModel) : m_dataModel{dataModel}, m_valid{false}
-{
-}
-
-bool QueryParser::parse(const QString &rawPath)
-{
-       static const QString emptyString{};
-
-       m_errorString = QString{};
-
-       m_path = rawPath;
-       QStringList parts = m_path.split(QChar('/'), QString::SkipEmptyParts);
-       qDebug() << "parse " << parts;
-
-       m_levels.clear();
-       m_levels.resize(parts.length());
-
-       for (int i = 0; i < parts.length(); ++i) {
-               Level currentLevel;
-
-
-               const QString &part = parts[i];
-
-               const QStringList tableColumn = part.split(QChar('.'));
-               const QString &table = tableColumn[0];
-               const QString &column = tableColumn.size() > 1 ? tableColumn[1] : emptyString;
-
-//             qDebug() << "----------------------- Iteration" << i << "-----------------------";
-               qDebug() << "part(" << part.length() << ") =" << table << "(" << column << ")";
-
-               if (!tables.contains(table)) {
-                       m_errorString = QObject::tr("Table %1 does not exist.").arg(table);
-                       m_valid = false;
-                       return m_valid;
-               } else {
-                       currentLevel.table = table;
-                       currentLevel.type = AnimeEntry;
-               }
-
-               if (!column.isEmpty()) {
-                       if (!table_columns[currentLevel.table].contains(column)) {
-                               m_errorString = QObject::tr("Column %1 does not exist in table %2.")
-                                               .arg(column).arg(table);
-                               m_valid = false;
-                               return m_valid;
-                       }
-               } else {
-                       currentLevel.column = column;
-                       currentLevel.type = ColumnEntry;
-               }
-
-               m_levels.push_back(currentLevel);
-       }
-       m_valid = true;
-       return m_valid;
-}
-
-QString QueryParser::buildQuery(int level)
-{
-       if (!m_valid) return {};
-
-       const Level &lastLevel = level(level);
-
-       QString joins;
-
-       if (lastLevel.column.isEmpty()) {
-               return QString("SELECT %1.%2 FROM %1\n\t%3")
-                               .arg(lastLevel.table).arg(lastLevel.column).arg(joins);
-       }
-}
-
-bool QueryParser::isValid() const
-{
-       return m_valid;
-}
-
-int QueryParser::levels() const
-{
-       return m_levels.count();
-}
-
-const QueryParser::Level &QueryParser::level(int i) const
-{
-       Q_ASSERT_X(i > 0 && m_levels.count() < i, "dynamicmodel/query", "Requestesd invlaid level index");
-       return m_levels[i];
-}
-
-QString QueryParser::path() const
-{
-       return m_path;
-}
-
diff --git a/query-test/queryparser.h b/query-test/queryparser.h
deleted file mode 100644 (file)
index 83adc4e..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-#ifndef QUERYPARSER_H
-#define QUERYPARSER_H
-
-#include <QString>
-#include <QVector>
-#include "dynamicmodel/datamodel.h"
-
-using namespace LocalMyList::DynamicModel;
-
-class QueryParser
-{
-public:
-       enum EntryType {
-               ColumnEntry,
-               AnimeEntry,
-               EpisodeEntry,
-               FileEntry,
-       };
-
-       struct Level {
-               EntryType type;
-               QString table;
-               QString column;
-       };
-
-       QueryParser(DataModel *dataModel = 0);
-
-       bool parse(const QString &rawPath);
-
-       QString buildQuery(int level);
-
-       bool isValid() const;
-       int levels() const;
-       const Level &level(int i) const;
-
-       QString path() const;
-
-private:
-       bool m_valid;
-       QString m_path;
-       QString m_errorString;
-       QVector<Level> m_levels;
-       DataModel *m_dataModel;
-
-};
-
-#endif // QUERYPARSER_H
index e88aa330be9f1a0dbad18a8c2f9ead042bd96a59..5167a24eb1b669f035dd216be1691371777ad347 100644 (file)
@@ -113,11 +113,11 @@ const QMap<QString, QStringList> table_columns = []() {
 
 const QMap<QString, QMap<QString, QString>> join_map = []() {
        QMap<QString, QMap<QString, QString>> r;
-       r["anime"]["episode"] = "anime.aid = episode.aid";
-       r["anime"]["file"] = "anime.aid = file.aid";
-       r["episode"]["file"] = "episode.eid = file.eid";
-       r["episode"]["anime"] = "episode.aid = anime.aid";
-       r["file"]["anime"] = "file.aid = anime.aid";
-       r["file"]["episode"] = "file.eid = episode.eid";
+       r["anime"]["episode"] = "a.aid = e.aid";
+       r["anime"]["file"] = "a.aid = f.aid";
+       r["episode"]["file"] = "e.eid = f.eid";
+       r["episode"]["anime"] = "e.aid = a.aid";
+       r["file"]["anime"] = "f.aid = a.aid";
+       r["file"]["episode"] = "f.eid = e.eid";
        return r;
 }();