From: APTX Date: Mon, 17 Nov 2014 07:05:38 +0000 (+0100) Subject: misc changes X-Git-Url: https://gitweb.aptx.org/?a=commitdiff_plain;h=abc8dabc0e877a5a4fc38b6dd67eec8e5594a055;p=localmylist.git misc changes --- diff --git a/localmylist-management/tabs/dynamicmodeltab.cpp b/localmylist-management/tabs/dynamicmodeltab.cpp index 493c4e6..e016f05 100644 --- a/localmylist-management/tabs/dynamicmodeltab.cpp +++ b/localmylist-management/tabs/dynamicmodeltab.cpp @@ -1,6 +1,8 @@ #include "dynamicmodeltab.h" #include "ui_dynamicmodeltab.h" +#include + #include "mainwindow.h" #include "database.h" #include "mylist.h" @@ -12,6 +14,8 @@ #include "dynamicmodel/types.h" #include "dynamicmodel/typerelation.h" +#include + using namespace LocalMyList::DynamicModel; DynamicModelTab::DynamicModelTab(QWidget *parent) : @@ -59,7 +63,6 @@ void DynamicModelTab::init() dataModel->registerTypeRelation(new AnimeAnimeTitleRelation(this)); model = new Model(this); - model->setDataModel(dataModel); myListFilterModel = new MyListFilterModel(this); myListFilterModel->setSourceModel(model); @@ -85,7 +88,14 @@ void DynamicModelTab::init() 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() @@ -185,10 +195,18 @@ void DynamicModelTab::updateSelection() 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(); } diff --git a/localmylist.pro b/localmylist.pro index 7c4ca36..ab9795b 100644 --- a/localmylist.pro +++ b/localmylist.pro @@ -22,3 +22,5 @@ SUBDIRS += localmylist SUBDIRS += runscript } } + +SUBDIRS += query-test diff --git a/localmylist/dynamicmodel/data.cpp b/localmylist/dynamicmodel/data.cpp index 3fa9e33..0b06031 100644 --- a/localmylist/dynamicmodel/data.cpp +++ b/localmylist/dynamicmodel/data.cpp @@ -51,7 +51,7 @@ void Data::ref(Node *node) void Data::deref(Node *node) { - Q_ASSERT(references.isEmpty()); + Q_ASSERT(!references.isEmpty()); bool removed = references.removeOne(node); diff --git a/localmylist/dynamicmodel/datamodel.cpp b/localmylist/dynamicmodel/datamodel.cpp index 7e7232d..3dde9e8 100644 --- a/localmylist/dynamicmodel/datamodel.cpp +++ b/localmylist/dynamicmodel/datamodel.cpp @@ -58,24 +58,46 @@ bool DataModel::registerTypeRelation(TypeRelation *typeRelation) 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 diff --git a/localmylist/dynamicmodel/datamodel.h b/localmylist/dynamicmodel/datamodel.h index 8f589ac..0e42b57 100644 --- a/localmylist/dynamicmodel/datamodel.h +++ b/localmylist/dynamicmodel/datamodel.h @@ -28,7 +28,9 @@ public: 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; diff --git a/localmylist/dynamicmodel/model.cpp b/localmylist/dynamicmodel/model.cpp index d55180f..ffa2b9e 100644 --- a/localmylist/dynamicmodel/model.cpp +++ b/localmylist/dynamicmodel/model.cpp @@ -4,12 +4,15 @@ #include "datamodel.h" #include "datatype.h" #include "typerelation.h" +#include "query.h" + +#include namespace LocalMyList { namespace DynamicModel { Model::Model(QObject *parent) : - QAbstractItemModel(parent), m_dataModel(0) + QAbstractItemModel(parent) { rootItem = createRootNode(); } @@ -19,21 +22,25 @@ Model::~Model() 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 @@ -162,14 +169,14 @@ QModelIndex Model::index(Node *node) 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 @@ -181,9 +188,9 @@ 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() @@ -194,37 +201,27 @@ 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()) { @@ -244,12 +241,13 @@ void Model::episodeInsert(int aid, int eid) 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; } diff --git a/localmylist/dynamicmodel/model.h b/localmylist/dynamicmodel/model.h index 037a329..ee4cb6f 100644 --- a/localmylist/dynamicmodel/model.h +++ b/localmylist/dynamicmodel/model.h @@ -2,6 +2,7 @@ #define MODEL_H #include "../localmylist_global.h" +#include "query.h" #include #include @@ -11,20 +12,20 @@ namespace DynamicModel { 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; @@ -43,9 +44,8 @@ public: 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; @@ -53,23 +53,19 @@ public: 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 diff --git a/localmylist/dynamicmodel/query.cpp b/localmylist/dynamicmodel/query.cpp new file mode 100644 index 0000000..5a1626f --- /dev/null +++ b/localmylist/dynamicmodel/query.cpp @@ -0,0 +1,97 @@ +#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 diff --git a/localmylist/dynamicmodel/query.h b/localmylist/dynamicmodel/query.h new file mode 100644 index 0000000..3be54e3 --- /dev/null +++ b/localmylist/dynamicmodel/query.h @@ -0,0 +1,50 @@ +#ifndef QUERY_H +#define QUERY_H + +#include "../localmylist_global.h" +#include +#include + +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 diff --git a/localmylist/localmylist.pro b/localmylist/localmylist.pro index dcb3be5..e36ade5 100644 --- a/localmylist/localmylist.pro +++ b/localmylist/localmylist.pro @@ -43,7 +43,9 @@ SOURCES += \ 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 \ @@ -81,7 +83,9 @@ HEADERS += \ 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 \ diff --git a/query-test/main.cpp b/query-test/main.cpp new file mode 100644 index 0000000..d62dcca --- /dev/null +++ b/query-test/main.cpp @@ -0,0 +1,39 @@ +#include + +#include +#include +#include +#include "mylist.h" +#include "settings.h" +#include "queryparser.h" + +#include + +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; +} diff --git a/query-test/query-test.pro b/query-test/query-test.pro new file mode 100644 index 0000000..7112bc2 --- /dev/null +++ b/query-test/query-test.pro @@ -0,0 +1,24 @@ +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 diff --git a/query-test/queryparser.cpp b/query-test/queryparser.cpp new file mode 100644 index 0000000..4a80626 --- /dev/null +++ b/query-test/queryparser.cpp @@ -0,0 +1,100 @@ +#include "queryparser.h" +#include +#include "tabledata.h" +//#include "conversions.h" + +#include + + +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 new file mode 100644 index 0000000..83adc4e --- /dev/null +++ b/query-test/queryparser.h @@ -0,0 +1,47 @@ +#ifndef QUERYPARSER_H +#define QUERYPARSER_H + +#include +#include +#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 m_levels; + DataModel *m_dataModel; + +}; + +#endif // QUERYPARSER_H diff --git a/query-test/tabledata.cpp b/query-test/tabledata.cpp new file mode 100644 index 0000000..e88aa33 --- /dev/null +++ b/query-test/tabledata.cpp @@ -0,0 +1,123 @@ +#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 table_main_column = []() { + QMap r; + r["anime"] = "title_romaji"; + r["episode"] = "title_english"; + r["file"] = "fid"; + return r; +}(); + +const QMap table_columns = []() { + QMap 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> join_map = []() { + QMap> 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; +}(); diff --git a/query-test/tabledata.h b/query-test/tabledata.h new file mode 100644 index 0000000..d055d7b --- /dev/null +++ b/query-test/tabledata.h @@ -0,0 +1,24 @@ +#ifndef TABLEDATA_H +#define TABLEDATA_H + +#include +#include +#include + +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 table_columns; +extern const QMap table_main_column; +extern const QMap> join_map; + +#endif // TABLEDATA_H