#include "dynamicmodeltab.h"
#include "ui_dynamicmodeltab.h"
+#include <QMessageBox>
+
#include "mainwindow.h"
#include "database.h"
#include "mylist.h"
#include "dynamicmodel/types.h"
#include "dynamicmodel/typerelation.h"
+#include <QDebug>
+
using namespace LocalMyList::DynamicModel;
DynamicModelTab::DynamicModelTab(QWidget *parent) :
dataModel->registerTypeRelation(new AnimeAnimeTitleRelation(this));
model = new Model(this);
- model->setDataModel(dataModel);
myListFilterModel = new MyListFilterModel(this);
myListFilterModel->setSourceModel(model);
connect(model, SIGNAL(queryChanged(QString)), ui->modelQuery, SLOT(setText(QString)));
//model->setQuery("anime|episode|file|file_location");
- model->setQuery("anime|episode");
+ Query q(dataModel);
+ q.parse("anime|episode");
+
+ if (!q.isValid()) {
+ qDebug() << "Invalid query" << q.errorString();
+ }
+
+ model->setQuery(q);
}
void DynamicModelTab::activate()
void DynamicModelTab::on_modelQuery_returnPressed()
{
- model->setQuery(ui->modelQuery->text());
+ Query q(dataModel);
+ if (q.parse(ui->modelQuery->text()))
+ {
+ model->setQuery(q);
+ }
+ else
+ {
+ QMessageBox::critical(this, tr("Query parse error"), q.errorString());
+ }
}
void DynamicModelTab::on_modelQueryButton_clicked()
{
- model->setQuery(ui->modelQuery->text());
+ on_modelQuery_returnPressed();
}
SUBDIRS += runscript
}
}
+
+SUBDIRS += query-test
void Data::deref(Node *node)
{
- Q_ASSERT(references.isEmpty());
+ Q_ASSERT(!references.isEmpty());
bool removed = references.removeOne(node);
DataType *DataModel::dataType(const QString &name) const
{
DataType *t = dataTypeNames.value(name, 0);
- Q_ASSERT(t);
+ Q_ASSERT_X(t, "dynamicmodel", "Unregistered data type requested.");
return t;
}
+bool DataModel::hasDataType(const QString &name) const
+{
+ return dataTypeNames.value(name, 0);
+}
+
TypeRelation *DataModel::typeRelation(const QString &source, const QString &destiantion)
{
const auto it = typeRelations.find(source);
if (it == typeRelations.constEnd())
+ {
+ Q_ASSERT_X(false, "dynamicmodel", "Unregistered typerelation Requested (source)");
return 0;
+ }
const auto inner = it.value().find(destiantion);
if (inner == it.value().constEnd())
+ {
+ Q_ASSERT_X(false, "dynamicmodel", "Unregistered typerelation Requested (destination)");
return 0;
+ }
return inner.value();
}
+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();
+}
+
} // namespace DynamicModel
} // namespace LocalMyList
bool registerTypeRelation(TypeRelation *typeRelation);
DataType *dataType(const QString &name) const;
+ bool hasDataType(const QString &name) const;
TypeRelation *typeRelation(const QString &source, const QString &destiantion);
+ bool hasTypeRelation(const QString &source, const QString &destiantion) const;
#include "datamodel.h"
#include "datatype.h"
#include "typerelation.h"
+#include "query.h"
+
+#include <QDebug>
namespace LocalMyList {
namespace DynamicModel {
Model::Model(QObject *parent) :
- QAbstractItemModel(parent), m_dataModel(0)
+ QAbstractItemModel(parent)
{
rootItem = createRootNode();
}
delete rootItem;
}
-QString Model::query() const
+Query Model::query() const
{
return m_query;
}
-void Model::setQuery(const QString &query)
+void Model::setQuery(const Query &query)
{
if (query == m_query)
return;
- dataTypeNames = query.split(QChar('|'));
- reload();
+ if (!query.isValid())
+ return;
m_query = query;
+
+ reload();
+
emit queryChanged(query);
+ emit queryChanged(query.queryString());
}
QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const
return createIndex(node->row(), 0, node);
}
-DataModel *Model::dataModel() const
+DataType *Model::rootDataType() const
{
- return m_dataModel;
+ return m_query.dataModel()->dataType("anime");
}
-DataType *Model::rootDataType() const
+DataModel *Model::dataModel() const
{
- return m_dataModel->dataType("anime");
+ return m_query.dataModel();
}
DataType *Model::grandChildDataType(Node *node) const
DataType *Model::childDataType(int i) const
{
- if (dataTypeNames.count() <= i)
+ if (m_query.dataTypeNames().count() <= i)
return 0;
- return dataModel()->dataType(dataTypeNames.at(i));
+ return dataModel()->dataType(m_query.dataTypeNames().at(i));
}
void Model::reload()
endResetModel();
}
-void Model::setDataModel(DataModel *dataModel)
-{
- if (m_dataModel == dataModel)
- return;
-
- m_dataModel = dataModel;
- emit dataModelChanged(dataModel);
-
- reload();
-}
-
void Model::episodeInsert(int aid, int eid)
{
- DataType *episodeDataType = m_dataModel->dataType("episode");
+ Q_UNUSED(aid);
+ DataType *episodeDataType = m_query.dataModel()->dataType("episode");
if (!episodeDataType)
return;
- if (!m_dataModel->dataType("anime"))
+ if (!m_query.dataModel()->dataType("anime"))
return;
QString previousDataTypeName = QString();
- DataType *previousDataType = 0;
+// DataType *previousDataType = 0;
- for (const QString &dataTypeName : dataTypeNames)
+ for (const QString &dataTypeName : m_query.dataTypeNames())
{
- DataType *currentDataType = m_dataModel->dataType(dataTypeName);
+ DataType *currentDataType = m_query.dataModel()->dataType(dataTypeName);
if (currentDataType == episodeDataType)
{
- TypeRelation *rel = m_dataModel->typeRelation(previousDataTypeName, dataTypeName);
+ TypeRelation *rel = m_query.dataModel()->typeRelation(previousDataTypeName, dataTypeName);
if (previousDataTypeName.isNull())
{
Node *Model::createRootNode()
{
- int size = (m_dataModel && !dataTypeNames.isEmpty())
- ? dataModel()->dataType(dataTypeNames.at(0))->size()
+ int size = (m_query.dataModel() && !m_query.dataTypeNames().isEmpty())
+ ? dataModel()->dataType(m_query.dataTypeNames().at(0))->size()
: 0;
Node *n = new Node(this, 0, size, 0);
- if (m_dataModel && !dataTypeNames.isEmpty())
- n->setChildDataType(dataModel()->dataType(dataTypeNames.at(0)));
+ qDebug() << "SIZE" << size;
+ if (m_query.dataModel() && !m_query.dataTypeNames().isEmpty())
+ n->setChildDataType(dataModel()->dataType(m_query.dataTypeNames().at(0)));
return n;
}
#define MODEL_H
#include "../localmylist_global.h"
+#include "query.h"
#include <QAbstractItemModel>
#include <QStringList>
class Node;
class DataModel;
class DataType;
+class Query;
class LOCALMYLISTSHARED_EXPORT Model : public QAbstractItemModel
{
Q_OBJECT
- Q_PROPERTY(DataModel* dataModel READ dataModel WRITE setDataModel NOTIFY dataModelChanged)
- Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged)
+ Q_PROPERTY(Query query READ query WRITE setQuery NOTIFY queryChanged)
friend class Node;
public:
explicit Model(QObject *parent = 0);
~Model();
- QString query() const;
- void setQuery(const QString &query);
+ Query query() const;
+ void setQuery(const Query &query);
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
Node *node(const QModelIndex &idx) const;
QModelIndex index(Node *node) const;
- DataModel *dataModel() const;
-
DataType *rootDataType() const;
+ DataModel *dataModel() const;
DataType *grandChildDataType(Node *node) const;
DataType *childDataType(int i) const;
public slots:
void reload();
- void setDataModel(DataModel *dataModel);
-
private slots:
void episodeInsert(int aid, int eid);
signals:
- void dataModelChanged(DataModel *dataModel);
+ void queryChanged(Query query);
void queryChanged(QString query);
private:
Node *createRootNode();
Node *rootItem;
- DataModel* m_dataModel;
- QStringList dataTypeNames;
- QString m_query;
+ Query m_query;
};
} // namespace DynamicModel
--- /dev/null
+#include "query.h"
+
+#include "datamodel.h"
+
+namespace LocalMyList {
+namespace DynamicModel {
+
+static const QLatin1Literal noDataModel{"No data model"};
+
+Query::Query(DataModel *dataModel) : m_dataModel{dataModel}, m_valid{false}, m_error{NoDataModel},
+ m_errorString{noDataModel}
+{
+
+}
+
+bool Query::parse(const QString &query)
+{
+ if (m_queryString == query)
+ return m_valid;
+
+ m_queryString = query;
+
+ if (!m_dataModel)
+ {
+ m_error = NoDataModel;
+ m_errorString = noDataModel;
+ m_valid = false;
+ return m_valid;
+ }
+
+ QStringList dataTypeNames = query.split(QChar('|'));
+ QString previousDataTypeName;
+ for (auto&& dataTypeName : dataTypeNames)
+ {
+ if (!m_dataModel->hasDataType(dataTypeName))
+ {
+ m_error = DataTypeError;
+ m_errorString = QString{"Type \"%1\" not registered in data model"}.arg(dataTypeName);
+ m_valid = false;
+ return m_valid;
+ }
+
+ if (!previousDataTypeName.isEmpty()
+ && !m_dataModel->hasTypeRelation(previousDataTypeName, dataTypeName))
+ {
+ m_error = TypeRelationError;
+ m_errorString = QString{"There is no relation registered between type \"%1\" and \"%2\""}
+ .arg(previousDataTypeName).arg(dataTypeName);
+ m_valid = false;
+ return m_valid;
+ }
+ previousDataTypeName = dataTypeName;
+ }
+ m_dataTypeNames = dataTypeNames;
+ m_error = NoError;
+ m_errorString.clear();
+ m_valid = true;
+ return m_valid;
+}
+
+bool Query::isValid() const
+{
+ return m_valid;
+}
+
+Query::QueryError Query::error() const
+{
+ return m_error;
+}
+
+QString Query::errorString() const
+{
+ return m_errorString;
+}
+
+QString Query::queryString() const
+{
+ return m_queryString;
+}
+
+DataModel *Query::dataModel() const
+{
+ return m_dataModel;
+}
+
+QStringList Query::dataTypeNames() const
+{
+ return m_dataTypeNames;
+}
+
+bool operator ==(const Query &a, const Query& b)
+{
+ return a.m_dataModel == b.m_dataModel && a.m_queryString == b.m_queryString;
+}
+
+} // namespace DynamicModel
+} // namespace LocalMyList
--- /dev/null
+#ifndef QUERY_H
+#define QUERY_H
+
+#include "../localmylist_global.h"
+#include <QString>
+#include <QStringList>
+
+namespace LocalMyList {
+namespace DynamicModel {
+
+class DataModel;
+
+class LOCALMYLISTSHARED_EXPORT Query
+{
+public:
+ enum QueryError
+ {
+ NoError,
+ NoDataModel,
+ DataTypeError,
+ TypeRelationError,
+ };
+
+ explicit Query(DataModel *dataModel = 0);
+
+ bool parse(const QString &query);
+
+ bool isValid() const;
+ QueryError error() const;
+ QString errorString() const;
+ QString queryString() const;
+
+ DataModel *dataModel() const;
+ QStringList dataTypeNames() const;
+
+ friend bool operator ==(const Query& a, const Query& b);
+
+private:
+ bool m_valid;
+ QueryError m_error;
+ QString m_errorString;
+ QString m_queryString;
+ DataModel *m_dataModel;
+ QStringList m_dataTypeNames;
+};
+
+} // namespace DynamicModel
+} // namespace LocalMyList
+
+#endif // QUERY_H
dynamicmodel/datatype.cpp \
dynamicmodel/types.cpp \
dynamicmodel/datamodel.cpp \
- dynamicmodel/typerelation.cpp
+ dynamicmodel/typerelation.cpp \
+ dynamicmodel/query.cpp \
+ dynamicmodel/entry.cpp
HEADERS += \
localmylist_global.h \
dynamicmodel/dynamicmodel_global.h \
dynamicmodel/types.h \
dynamicmodel/datamodel.h \
- dynamicmodel/typerelation.h
+ dynamicmodel/typerelation.h \
+ dynamicmodel/query.h \
+ dynamicmodel/entry.h
CONV_HEADERS += \
include/LocalMyList/AbstractTask \
--- /dev/null
+#include <QtCore/QCoreApplication>
+
+#include <QStringList>
+#include <QTextStream>
+#include <QUrl>
+#include "mylist.h"
+#include "settings.h"
+#include "queryparser.h"
+
+#include <QDebug>
+
+using namespace LocalMyList;
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication a(argc, argv);
+ QTextStream cout(stdout);
+ if (a.arguments().count() < 2)
+ {
+ cout << "Usage: " << a.arguments()[0] << " QUERY" << endl;
+ return 1;
+ }
+
+ LocalMyList::instance()->loadLocalSettings();
+ if (!LocalMyList::instance()->database()->connect())
+ {
+ cout << "Could not connect to database.";
+ return 1;
+ }
+
+ DataModel d{};
+ QueryParser p{&d};
+
+ bool success = p.parse(a.arguments()[1]);
+
+ qDebug() << "Success" << success;
+
+ return !success;
+}
--- /dev/null
+QT += core
+QT -= gui
+
+include(../config.pri)
+
+TARGET = lml-import-mylist
+DESTDIR = ../build
+#CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+SOURCES += main.cpp \
+ queryparser.cpp \
+ tabledata.cpp
+
+include(../localmylist.pri)
+
+target.path = $${PREFIX}/bin
+INSTALLS += target
+
+HEADERS += \
+ queryparser.h \
+ tabledata.h
--- /dev/null
+#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;
+}
+
--- /dev/null
+#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
--- /dev/null
+#include "tabledata.h"
+
+namespace Token {
+const QLatin1String AnimeTable{"anime"};
+const QLatin1String EpisodeTable{"episode"};
+const QLatin1String FileTable{"file"};
+const QLatin1String Entries{"entries"};
+const QLatin1String Metadata{"metadata"};
+const QLatin1String FirstUnwatched{"fisrt_unwatched"};
+const QLatin1String BestFile{"best_file"};
+const QLatin1String BestLocation{"best_location"};
+}
+
+// TODO all of these shouldbe generated.
+const QStringList tables = QStringList()
+// TODO anime_title is currently not supported
+// << "anime_title"
+ << "anime"
+ << "episode"
+ << "file";
+
+const QMap<QString, QString> table_main_column = []() {
+ QMap<QString, QString> r;
+ r["anime"] = "title_romaji";
+ r["episode"] = "title_english";
+ r["file"] = "fid";
+ return r;
+}();
+
+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 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";
+ return r;
+}();
--- /dev/null
+#ifndef TABLEDATA_H
+#define TABLEDATA_H
+
+#include <QMap>
+#include <QStringList>
+#include <QString>
+
+namespace Token {
+extern const QLatin1String AnimeTable;
+extern const QLatin1String EpisodeTable;
+extern const QLatin1String FileTable;
+extern const QLatin1String Entries;
+extern const QLatin1String Metadata;
+extern const QLatin1String FirstUnwatched;
+extern const QLatin1String BestFile;
+extern const QLatin1String BestLocation;
+}
+
+extern const QStringList tables;
+extern const QMap<QString, QStringList> table_columns;
+extern const QMap<QString, QString> table_main_column;
+extern const QMap<QString, QMap<QString, QString>> join_map;
+
+#endif // TABLEDATA_H