From fbed6b0f0f1d1e308bc96b3c2e2be8b2ac6c18b2 Mon Sep 17 00:00:00 2001 From: APTX Date: Tue, 26 Mar 2013 23:07:36 +0100 Subject: [PATCH] MyListModel can now update itself when database entries change. --- localmylist/database.cpp | 68 +++++- localmylist/database.h | 9 +- localmylist/mylistmodel.cpp | 191 +++++++++++++++ localmylist/mylistmodel.h | 15 ++ localmylist/mylistnode.cpp | 363 +++++++++++++++++++++------- localmylist/mylistnode.h | 26 ++ localmylist/mylistnodedata.h | 12 + localmylist/share/schema/schema.sql | 16 +- 8 files changed, 604 insertions(+), 96 deletions(-) diff --git a/localmylist/database.cpp b/localmylist/database.cpp index 275d8d3..dee3533 100644 --- a/localmylist/database.cpp +++ b/localmylist/database.cpp @@ -1288,7 +1288,8 @@ bool Database::connect() return success; } - QObject::connect(d->db.driver(), SIGNAL(notification(QString)), this, SLOT(handleNotification(QString))); + QObject::connect(d->db.driver(), SIGNAL(notification(QString,QSqlDriver::NotificationSource,QVariant)), + this, SLOT(handleNotification(QString,QSqlDriver::NotificationSource,QVariant))); subscribeToNotifications(); emit connected(); @@ -1310,6 +1311,10 @@ void Database::disconnect() d->db.close(); + auto subscribedNotifications = d->db.driver()->subscribedToNotifications(); + foreach (const QString ¬ification, subscribedNotifications) + d->db.driver()->unsubscribeFromNotification(notification); + emit disconnected(); } @@ -1467,6 +1472,10 @@ void Database::subscribeToNotifications() d->db.driver()->subscribeToNotification("new_pending_mylist_update"); d->db.driver()->subscribeToNotification("rename_data_changed"); d->db.driver()->subscribeToNotification("config_changed"); + d->db.driver()->subscribeToNotification("anime_update"); + d->db.driver()->subscribeToNotification("episode_update"); + d->db.driver()->subscribeToNotification("file_update"); + d->db.driver()->subscribeToNotification("file_location_update"); } OpenFileData Database::readOpenFileData(QSqlQuery &q) @@ -1713,25 +1722,70 @@ bool Database::notifyRenameDataChanged() return notify("rename_data_changed"); } -void Database::handleNotification(const QString ¬ification) +void Database::handleNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload) { - qDebug() << "Recieved notification" << notification; - if (notification == "new_pending_request") + Q_UNUSED(source); + Q_UNUSED(payload); + + qDebug() << "Recieved notification" << name; + if (name == "new_pending_request") { emit newPendingRequest(); } - else if (notification == "new_pending_mylist_update") + else if (name == "new_pending_mylist_update") { emit newPendingMyListUpdate(); } - else if (notification == "rename_data_changed") + else if (name == "rename_data_changed") { emit renameDataChanged(); } - else if (notification == "config_changed") + else if (name == "config_changed") { emit configChanged(); } + else if (name == "anime_update") + { + int id = payload.toInt(); + if (id) + emit animeUpdate(id); + } + else if (name == "episode_update") + { + QStringList ids = payload.toString().split(QChar(','), QString::SkipEmptyParts); + int eid = 0; + int aid = 0; + if (ids.count()) + eid = ids.takeFirst().toInt(); + if (ids.count()) + aid = ids.takeFirst().toInt(); + + if (eid) + emit episodeUpdate(eid, aid); + } + else if (name == "file_update") + { + QStringList ids = payload.toString().split(QChar(','), QString::SkipEmptyParts); + int fid = 0; + int eid = 0; + int aid = 0; + + if (ids.count()) + fid = ids.takeFirst().toInt(); + if (ids.count()) + eid = ids.takeFirst().toInt(); + if (ids.count()) + aid = ids.takeFirst().toInt(); + + if (fid) + emit fileUpdate(fid, eid, aid); + } + else if (name == "file_location_update") + { + int id = payload.toInt(); + if (id) + emit fileLocationUpdate(id); + } } diff --git a/localmylist/database.h b/localmylist/database.h index 196d726..d569522 100644 --- a/localmylist/database.h +++ b/localmylist/database.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "databaseclasses.h" #include "sqlresultiteratorinterface.h" @@ -151,8 +153,13 @@ signals: void renameDataChanged(); void configChanged(); + void animeUpdate(int aid); + void episodeUpdate(int eid, int aid); + void fileUpdate(int fid, int eid, int aid); + void fileLocationUpdate(int id); + private slots: - void handleNotification(const QString ¬ification); + void handleNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload); private: void subscribeToNotifications(); diff --git a/localmylist/mylistmodel.cpp b/localmylist/mylistmodel.cpp index d7a38c9..4ac83e2 100644 --- a/localmylist/mylistmodel.cpp +++ b/localmylist/mylistmodel.cpp @@ -1,5 +1,6 @@ #include "mylistmodel.h" +#include "mylist.h" #include "mylistnode.h" #include @@ -12,6 +13,11 @@ MyListModel::MyListModel(QObject *parent) : rootItem = new MyListNode(this); delayFetchTimer.setSingleShot(true); connect(&delayFetchTimer, SIGNAL(timeout()), this, SLOT(finishDelayedFetch())); + + connect(MyList::instance()->database(), SIGNAL(animeUpdate(int)), this, SLOT(animeUpdate(int))); + connect(MyList::instance()->database(), SIGNAL(episodeUpdate(int,int)), this, SLOT(episodeUpdate(int,int))); + connect(MyList::instance()->database(), SIGNAL(fileUpdate(int,int,int)), this, SLOT(fileUpdate(int,int,int))); + connect(MyList::instance()->database(), SIGNAL(fileLocationUpdate(int)), this, SLOT(fileLocationUpdate(int))); } MyListModel::~MyListModel() @@ -19,6 +25,142 @@ MyListModel::~MyListModel() delete rootItem; } +Anime MyListModel::anime(int id) const +{ + auto it = animeSet.find(id); + + if (it == animeSet.end()) + return Anime(); + return it->data; +} + +Anime MyListModel::anime(const QModelIndex &index) const +{ + if (!index.isValid()) + return Anime(); + + MyListNode *node = static_cast(index.internalPointer()); + + if (node->type() != MyListNode::AnimeNode) + return Anime(); + + return anime(node->id()); +} + +QModelIndex MyListModel::animeIndex(int id) const +{ + auto it = animeSet.find(id); + + if (it == animeSet.end()) + return QModelIndex(); + + MyListAnimeNode *node = it->node; + + return index(node); +} + +Episode MyListModel::episode(int id) const +{ + auto it = episodeSet.find(id); + + if (it == episodeSet.end()) + return Episode(); + return it->data; +} + +Episode MyListModel::episode(const QModelIndex &index) const +{ + if (!index.isValid()) + return Episode(); + + MyListNode *node = static_cast(index.internalPointer()); + + if (node->type() != MyListNode::EpisodeNode) + return Episode(); + + return episode(node->id()); +} + +QModelIndex MyListModel::episodeIndex(int id) const +{ + auto it = episodeSet.find(id); + + if (it == episodeSet.end()) + return QModelIndex(); + + MyListEpisodeNode *node = it->node; + + return index(node); +} + +File MyListModel::file(int id) const +{ + auto it = fileSet.find(id); + + if (it == fileSet.end()) + return File(); + return it->data; +} + +File MyListModel::file(const QModelIndex &index) const +{ + if (!index.isValid()) + return File(); + + MyListNode *node = static_cast(index.internalPointer()); + + if (node->type() != MyListNode::FileNode) + return File(); + + return file(node->id()); +} + +QModelIndex MyListModel::fileIndex(int id) const +{ + auto it = fileSet.find(id); + + if (it == fileSet.end()) + return QModelIndex(); + + MyListFileNode *node = it->node; + + return index(node); +} + +FileLocation MyListModel::fileLocation(int id) const +{ + auto it = fileLocationSet.find(id); + + if (it == fileLocationSet.end()) + return FileLocation(); + return it->data; +} + +FileLocation MyListModel::fileLocation(const QModelIndex &index) const +{ + if (!index.isValid()) + return FileLocation(); + + MyListNode *node = static_cast(index.internalPointer()); + + if (node->type() != MyListNode::FileLocationNode) + return FileLocation(); + + return fileLocation(node->id()); +} + +QModelIndex MyListModel::fileLocationIndex(int id) const +{ + auto it = fileLocationSet.find(id); + + if (it == fileLocationSet.end()) + return QModelIndex(); + + MyListFileLocationNode *node = it->node; + + return index(node); +} + QVariant MyListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) @@ -160,6 +302,48 @@ void MyListModel::finishDelayedFetch() delayedFetchNode = 0; } +void MyListModel::animeUpdate(int aid) +{ + MyListNode *updatedNode = node(animeIndex(aid)); + if (!updatedNode) + return; + + updatedNode->updated(); +} + +void MyListModel::episodeUpdate(int eid, int aid) +{ + MyListNode *updatedNode = node(episodeIndex(eid)); + if (!updatedNode) + { + animeUpdate(aid); + return; + } + + updatedNode->updated(); +} + +void MyListModel::fileUpdate(int fid, int eid, int aid) +{ + MyListNode *updatedNode = node(fileIndex(fid)); + if (!updatedNode) + { + episodeUpdate(eid, aid); + return; + } + + updatedNode->updated(); +} + +void MyListModel::fileLocationUpdate(int id) +{ + MyListNode *updatedNode = node(fileLocationIndex(id)); + if (!updatedNode) + return; + + updatedNode->updated(); +} + QModelIndex MyListModel::index(MyListNode *node) const { if (!node || node == rootItem) @@ -197,4 +381,11 @@ void MyListModel::fetchFinished(MyListNode *node, int newrows) qDebug() << "added" << newrows << "new rows"; } +void MyListModel::nodeChanged(MyListNode *node) +{ + const int row = node->row(); + const QModelIndex parentIndex(index(node->parent())); + emit dataChanged(index(row, 0, parentIndex), index(row, node->columnCount() - 1, parentIndex)); +} + } // namespace LocalMyList diff --git a/localmylist/mylistmodel.h b/localmylist/mylistmodel.h index b1315b7..0ccba04 100644 --- a/localmylist/mylistmodel.h +++ b/localmylist/mylistmodel.h @@ -39,9 +39,18 @@ public: Anime anime(const QModelIndex &index) const; QModelIndex animeIndex(int id) const; + Episode episode(int id) const; + Episode episode(const QModelIndex &index) const; + QModelIndex episodeIndex(int id) const; + File file(int id) const; + File file(const QModelIndex &index) const; QModelIndex fileIndex(int id) const; + FileLocation fileLocation(int id) const; + FileLocation fileLocation(const QModelIndex &index) const; + QModelIndex fileLocationIndex(int id) const; + 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; @@ -66,9 +75,15 @@ public slots: private slots: void finishDelayedFetch(); + void animeUpdate(int aid); + void episodeUpdate(int eid, int aid); + void fileUpdate(int fid, int eid, int aid); + void fileLocationUpdate(int id); + protected: QModelIndex index(MyListNode *node) const; void fetchFinished(MyListNode *node, int newrows); + void nodeChanged(MyListNode *node); private: boost::intrusive::set animeSet; diff --git a/localmylist/mylistnode.cpp b/localmylist/mylistnode.cpp index 7ab2082..811d03c 100644 --- a/localmylist/mylistnode.cpp +++ b/localmylist/mylistnode.cpp @@ -99,20 +99,11 @@ bool MyListNode::canFetchMore() const void MyListNode::fetchMore() { query->prepare(QString( - "SELECT " - " (SELECT COUNT(e.eid) " - " FROM episode e " - " WHERE e.aid = a.aid), " - " (SELECT COUNT(DISTINCT f.eid) " - " FROM episode e " - " JOIN file f ON (f.eid = e.eid) " - " WHERE e.aid = a.aid " - " AND f.my_watched IS NOT NULL), " - " %1 " - " FROM anime a " - " ORDER BY title_romaji ASC " - "LIMIT :limit " - "OFFSET :offset ").arg(Database::animeFields())); + "%1 " + "ORDER BY title_romaji ASC " + "LIMIT :limit " + "OFFSET :offset ") + .arg(MyListAnimeNode::baseQuery())); query->bindValue(":limit", LIMIT); query->bindValue(":offset", childCount()); @@ -123,12 +114,8 @@ void MyListNode::fetchComplete() { while (query->next()) { - int epsInMyList = query->value(0).toInt(); - int watchedEps = query->value(1).toInt(); - Anime a; - Database::readAnimeData(*query, a, 2); - - AnimeData ad(a, epsInMyList, watchedEps); + AnimeData ad; + MyListAnimeNode::fillAnimeData(ad, *query); auto node = new MyListAnimeNode(model, ad, this); newItems << node; } @@ -167,6 +154,23 @@ QString MyListNode::totalRowCountSql() const return "SELECT COUNT(aid) FROM anime"; } +void MyListNode::childUpdate(const EpisodeData &oldData, const EpisodeData &newData) +{ + Q_UNUSED(oldData) + Q_UNUSED(newData) +} + +void MyListNode::childUpdate(const FileData &oldData, const FileData &newData) +{ + Q_UNUSED(oldData) + Q_UNUSED(newData)} + +void MyListNode::childUpdate(const FileLocationData &oldData, const FileLocationData &newData) +{ + Q_UNUSED(oldData) + Q_UNUSED(newData) +} + MyListNode::NodeType MyListNode::type() const { return m_type; @@ -177,6 +181,10 @@ int MyListNode::id() const return 0; } +void MyListNode::updated() +{ +} + // ------ MyListAnimeNode::MyListAnimeNode(MyListModel *model, const AnimeData &data, MyListNode *parent) : @@ -230,26 +238,11 @@ void MyListAnimeNode::fetchMore() { qDebug() << "fetching some more for aid" << id(); query->prepare(QString( - "SELECT " - " (SELECT MIN(my_watched) " - " FROM " - " (SELECT my_watched " - " FROM file " - " WHERE eid = e.eid " - " AND my_watched IS NOT NULL " - " UNION " - " SELECT f.my_watched " - " FROM file f " - " JOIN file_episode_rel fer ON (fer.fid = f.fid) " - " WHERE fer.eid = e.eid " - " AND my_watched IS NOT NULL) AS sq) AS my_watched, " - " %1 " - " FROM episode e " - " JOIN episode_type et ON (et.type = e.type)" + " %1 " " WHERE e.aid = :aid " " ORDER BY et.ordering ASC, e.epno ASC " " LIMIT :limit " - " OFFSET :offset ").arg(Database::episodeFields())); + " OFFSET :offset ").arg(MyListEpisodeNode::baseQuery())); query->bindValue(":aid", id()); query->bindValue(":limit", LIMIT); query->bindValue(":offset", childCount()); @@ -261,11 +254,8 @@ void MyListAnimeNode::fetchComplete() { while (query->next()) { - QDateTime watchedDate = query->value(0).toDateTime(); - Episode e; - Database::readEpisodeData(*query, e, 1); - - EpisodeData ed(e, watchedDate); + EpisodeData ed; + MyListEpisodeNode::fillEpisodeData(ed, *query); auto node = new MyListEpisodeNode(model, ed, this); newItems << node; } @@ -280,6 +270,66 @@ int MyListAnimeNode::id() const return animeData.data.aid; } +void MyListAnimeNode::updated() +{ + QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString( + " %1 " + "WHERE a.aid = :aid").arg(baseQuery())); + q.bindValue(":aid", id()); + + if(!MyList::instance()->database()->exec(q)) + return; + + if (!q.next()) + return; + + AnimeData newData; + fillAnimeData(newData, QSqlResultIterator(q)); + animeData = newData; + animeData.node = this; + model->nodeChanged(this); +} + +void MyListAnimeNode::childUpdate(const EpisodeData &oldData, const EpisodeData &newData) +{ + // Episode got watched + if (!oldData.watchedDate.isValid() && newData.watchedDate.isValid()) + { + ++animeData.watchedEpisodes; + model->nodeChanged(this); + } + // Episode got unwatched + else if (oldData.watchedDate.isValid() && !newData.watchedDate.isValid()) + { + --animeData.watchedEpisodes; + model->nodeChanged(this); + } +} + +QString MyListAnimeNode::baseQuery() +{ + return QString( + "SELECT " + " (SELECT COUNT(e.eid) " + " FROM episode e " + " WHERE e.aid = a.aid), " + " (SELECT COUNT(DISTINCT f.eid) " + " FROM episode e " + " JOIN file f ON (f.eid = e.eid) " + " WHERE e.aid = a.aid " + " AND f.my_watched IS NOT NULL), " + " %1 " + " FROM anime a ") + .arg(Database::animeFields()); +} + +void MyListAnimeNode::fillAnimeData(AnimeData &data, SqlResultIteratorInterface &query) +{ + data.episodesInMyList = query.value(0).toInt(); + data.watchedEpisodes = query.value(1).toInt(); + Database::readAnimeData(query, data.data, 2); +} + // ---- MyListEpisodeNode::MyListEpisodeNode(MyListModel *model, const EpisodeData &data, MyListNode *parent) : @@ -349,32 +399,15 @@ void MyListEpisodeNode::fetchMore() query->bindValue(":eidb", id()); query->exec(); -/* - if (!LocalMyList::instance()->database()->exec(q)) - return 0; - - while (q.next()) - { - int id = q.value(0).toInt(); - QVariantList data; - data << q.value(1) << "v" + q.value(2).toString() << q.value(3) << "" - << (q.value(4).toDateTime().isValid() ? QObject::tr("Yes, on %1").arg(q.value(4).toDateTime().toString()) : QObject::tr("No")); - newItems << new MyListFileNode(id, data, this); - } - - q.finish(); - -*/ } void MyListEpisodeNode::fetchComplete() { while (query->next()) { - File f; - Database::readFileData(*query, f); + FileData fd; + MyListFileNode::fillFileData(fd, *query); - FileData fd(f); auto node = new MyListFileNode(model, fd, this); newItems << node; } @@ -389,11 +422,108 @@ int MyListEpisodeNode::id() const return episodeData.data.eid; } +void MyListEpisodeNode::updated() +{ + QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString( + " %1 " + "WHERE e.eid = :eid").arg(baseQuery())); + q.bindValue(":eid", id()); + + if(!MyList::instance()->database()->exec(q)) + return; + + if (!q.next()) + return; + + EpisodeData newData; + fillEpisodeData(newData, QSqlResultIterator(q)); + + parent()->childUpdate(episodeData, newData); + + episodeData = newData; + episodeData.node = this; + model->nodeChanged(this); +} + +void MyListEpisodeNode::childUpdate(const FileData &oldData, const FileData &newData) +{ + // File from episode got watched + if (!oldData.data.myWatched.isValid() && newData.data.myWatched.isValid()) + { + if (!episodeData.watchedDate.isValid() + || (episodeData.watchedDate.isValid() + && newData.data.myWatched < episodeData.watchedDate)) + { + EpisodeData oldData = episodeData; + + episodeData.watchedDate = newData.data.myWatched; + model->nodeChanged(this); + + parent()->childUpdate(oldData, episodeData); + } + } + // Watched date changed + else if (oldData.data.myWatched.isValid() && newData.data.myWatched.isValid()) + { + if (episodeData.watchedDate.isValid() && newData.data.myWatched < episodeData.watchedDate) + { + EpisodeData oldData = episodeData; + + episodeData.watchedDate = newData.data.myWatched; + model->nodeChanged(this); + + parent()->childUpdate(oldData, episodeData); + } + } + // File got unwatched + else if (oldData.data.myWatched.isValid() && !newData.data.myWatched.isValid()) + { + // No real way to get the proper watched date without + // looking at other children. + updated(); + } +} + +QString MyListEpisodeNode::baseQuery() +{ + return QString( + "SELECT " + " (SELECT MIN(my_watched) " + " FROM " + " (SELECT my_watched " + " FROM file " + " WHERE eid = e.eid " + " AND my_watched IS NOT NULL " + " UNION " + " SELECT f.my_watched " + " FROM file f " + " JOIN file_episode_rel fer ON (fer.fid = f.fid) " + " WHERE fer.eid = e.eid " + " AND my_watched IS NOT NULL) AS sq) AS my_watched, " + " %1 " + " FROM episode e " + " JOIN episode_type et ON (et.type = e.type)") + .arg(Database::episodeFields()); +} + +void MyListEpisodeNode::fillEpisodeData(EpisodeData &data, SqlResultIteratorInterface &query) +{ + data.watchedDate = query.value(0).toDateTime(); + Database::readEpisodeData(query, data.data, 1); +} + // --------------- MyListFileNode::MyListFileNode(MyListModel *model, const FileData &data, MyListNode *parent) : MyListNode(model, FileNode, parent), fileData(data) { + fileData.node = this; + model->fileSet.insert(fileData); +} + +MyListFileNode::~MyListFileNode() +{ + model->fileSet.erase(model->fileSet.s_iterator_to(fileData)); } QVariant MyListFileNode::data(int column) const @@ -420,42 +550,20 @@ void MyListFileNode::fetchMore() { qDebug() << "fetching some more for fid" << id(); query->prepare(QString( - "SELECT h.name, %1 FROM file_location fl " - " JOIN host h ON (fl.host_id = h.host_id) " + " %1 " "WHERE fl.fid = :fid") - .arg(Database::fileLocationFields())); + .arg(MyListFileLocationNode::baseQuery())); query->bindValue(":fid", id()); query->exec(); -/* - while (q.next()) - { - int id = q.value(0).toInt(); - QVariantList data; - data << q.value(3) - << QObject::tr("%1 (%2)").arg(q.value(2).toString()).arg(q.value(1).toString()) - << "" - << "" - << (q.value(5).toBool() ? QObject::tr("Rename Failed") : q.value(4).toDateTime().isValid() ? QObject::tr("Yes, on %1").arg(q.value(4).toDateTime().toString()) : QObject::tr("Not Renamed")); - newItems << new MyListFileLocationNode(id, data, this); - } - - q.finish(); - - return newItems.count(); -*/ } void MyListFileNode::fetchComplete() { while (query->next()) { - QString hostName = query->value(0).toString(); - - FileLocation fl; - Database::readFileLocationData(*query, fl, 1); - - FileLocationData fld(fl, hostName); + FileLocationData fld; + MyListFileLocationNode::fillFileLocationData(fld, *query); auto node = new MyListFileLocationNode(model, fld, this); newItems << node; @@ -471,6 +579,41 @@ int MyListFileNode::id() const return fileData.data.fid; } +void MyListFileNode::updated() +{ + QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString( + "SELECT %1 " + " FROM file f " + "WHERE f.fid = :fid") + .arg(Database::fileFields())); + q.bindValue(":fid", id()); + + if(!MyList::instance()->database()->exec(q)) + return; + + if (!q.next()) + return; + + FileData newData; + fillFileData(newData, QSqlResultIterator(q)); + + parent()->childUpdate(fileData, newData); + + fileData = newData; + fileData.node = this; + model->nodeChanged(this); +} + +QString MyListFileNode::baseQuery() +{ + return QString(); +} + +void MyListFileNode::fillFileData(FileData &data, SqlResultIteratorInterface &query) +{ + Database::readFileData(query, data.data); +} + QString MyListFileNode::totalRowCountSql() const { return "SELECT COUNT(fid) FROM file_location WHERE fid = " + QString::number(id()); @@ -481,6 +624,13 @@ QString MyListFileNode::totalRowCountSql() const MyListFileLocationNode::MyListFileLocationNode(MyListModel *model, const FileLocationData &data, MyListNode *parent) : MyListNode(model, FileLocationNode, parent), fileLocationData(data) { + fileLocationData.node = this; + model->fileLocationSet.insert(fileLocationData); +} + +MyListFileLocationNode::~MyListFileLocationNode() +{ + model->fileLocationSet.erase(model->fileLocationSet.s_iterator_to(fileLocationData)); } QVariant MyListFileLocationNode::data(int column) const @@ -519,6 +669,45 @@ int MyListFileLocationNode::id() const return fileLocationData.data.locationId; } +void MyListFileLocationNode::updated() +{ + QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString( + " %1 " + "WHERE fl.location_id = :locationId") + .arg(baseQuery())); + q.bindValue(":locationId", id()); + + if(!MyList::instance()->database()->exec(q)) + return; + + if (!q.next()) + return; + + FileLocationData newData; + fillFileLocationData(newData, QSqlResultIterator(q)); + + parent()->childUpdate(fileLocationData, newData); + + fileLocationData = newData; + fileLocationData.node = this; + model->nodeChanged(this); + +} + +QString MyListFileLocationNode::baseQuery() +{ + return QString( + "SELECT h.name, %1 FROM file_location fl " + " JOIN host h ON (fl.host_id = h.host_id) ") + .arg(Database::fileLocationFields()); +} + +void MyListFileLocationNode::fillFileLocationData(FileLocationData &data, SqlResultIteratorInterface &query) +{ + data.hostName = query.value(0).toString(); + Database::readFileLocationData(query, data.data, 1); +} + QString MyListFileLocationNode::totalRowCountSql() const { return "SELECT 0"; diff --git a/localmylist/mylistnode.h b/localmylist/mylistnode.h index d4e01cc..fa429a9 100644 --- a/localmylist/mylistnode.h +++ b/localmylist/mylistnode.h @@ -10,6 +10,7 @@ namespace LocalMyList { class MyListModel; class SqlAsyncQuery; +class SqlResultIteratorInterface; class LOCALMYLISTSHARED_EXPORT MyListNode { @@ -46,6 +47,11 @@ public: NodeType type() const; virtual int id() const; + virtual void updated(); + virtual void childUpdate(const EpisodeData &oldData, const EpisodeData &newData); + virtual void childUpdate(const FileData &oldData, const FileData &newData); + virtual void childUpdate(const FileLocationData &oldData, const FileLocationData &newData); + protected: virtual QString totalRowCountSql() const; @@ -76,6 +82,11 @@ public: void fetchMore(); void fetchComplete(); int id() const; + void updated(); + void childUpdate(const EpisodeData &oldData, const EpisodeData &newData); + + static QString baseQuery(); + static void fillAnimeData(AnimeData &data, SqlResultIteratorInterface &q); protected: QString totalRowCountSql() const; @@ -95,6 +106,11 @@ public: void fetchMore(); void fetchComplete(); int id() const; + void updated(); + void childUpdate(const FileData &oldData, const FileData &newData); + + static QString baseQuery(); + static void fillEpisodeData(EpisodeData &data, SqlResultIteratorInterface &query); protected: QString totalRowCountSql() const; @@ -107,11 +123,16 @@ class LOCALMYLISTSHARED_EXPORT MyListFileNode : public MyListNode { public: MyListFileNode(MyListModel *model, const FileData &data, MyListNode *parent); + ~MyListFileNode(); QVariant data(int column) const; void fetchMore(); void fetchComplete(); int id() const; + void updated(); + + static QString baseQuery(); + static void fillFileData(FileData &data, SqlResultIteratorInterface &query); protected: QString totalRowCountSql() const; @@ -124,11 +145,16 @@ class LOCALMYLISTSHARED_EXPORT MyListFileLocationNode : public MyListNode { public: MyListFileLocationNode(MyListModel *model, const FileLocationData &data, MyListNode *parent); + ~MyListFileLocationNode(); QVariant data(int column) const; void fetchMore(); void fetchComplete(); int id() const; + void updated(); + + static QString baseQuery(); + static void fillFileLocationData(FileLocationData &data, SqlResultIteratorInterface &query); protected: QString totalRowCountSql() const; diff --git a/localmylist/mylistnodedata.h b/localmylist/mylistnodedata.h index 4ed0016..046f5ae 100644 --- a/localmylist/mylistnodedata.h +++ b/localmylist/mylistnodedata.h @@ -22,6 +22,9 @@ struct AnimeData : public boost::intrusive::set_base_hook< > friend bool operator<(const AnimeData &a, const AnimeData &b) { return a.data.aid < b.data.aid; } + AnimeData(int aid = 0) + { data.aid = aid; } + AnimeData(const Anime &animeData, int epsInML, int watchedEps) : data(animeData), node(0), episodesInMyList(epsInML), watchedEpisodes(watchedEps) {} @@ -37,6 +40,9 @@ struct EpisodeData : public boost::intrusive::set_base_hook< > friend bool operator<(const EpisodeData &a, const EpisodeData &b) { return a.data.eid < b.data.eid; } + EpisodeData(int eid = 0) + { data.eid = eid; } + EpisodeData(const Episode &episodeData, const QDateTime &watchedDate_) : data(episodeData), node(0), watchedDate(watchedDate_) {} @@ -50,6 +56,9 @@ struct FileData : public boost::intrusive::set_base_hook< > friend bool operator<(const FileData &a, const FileData &b) { return a.data.fid < b.data.fid; } + FileData(int fid = 0) + { data.fid = fid; } + FileData(const File &fileData) : data(fileData), node(0) {} @@ -65,6 +74,9 @@ struct FileLocationData : public boost::intrusive::set_base_hook< > friend bool operator<(const FileLocationData &a, const FileLocationData &b) { return a.data.locationId < b.data.locationId; } + FileLocationData(int locationId = 0) + { data.locationId = locationId; } + FileLocationData(const FileLocation &fileLocationData, const QString &hostName_) : data(fileLocationData), node(0), hostName(hostName_) {} diff --git a/localmylist/share/schema/schema.sql b/localmylist/share/schema/schema.sql index 7d56fe9..fe3c07e 100644 --- a/localmylist/share/schema/schema.sql +++ b/localmylist/share/schema/schema.sql @@ -223,7 +223,7 @@ CREATE TABLE log ( CREATE VIEW file_data AS SELECT f.fid, f.eid, f.aid, f.gid, f.anidb_update, f.entry_update, f.my_update, f.ed2k, f.size, f.length, f.extension, f.group_name, f.group_name_short, f.crc, f.release_date, f.version, f.censored, f.source, f.quality_id, f.quality, f.resolution, f.video_codec, f.audio_codec, f.audio_language, f.subtitle_language, f.aspect_ratio, f.my_watched, f.my_state, f.my_file_state, f.my_storage, f.my_source, f.my_other, a.title_romaji AS atitle, e.title_english AS eptitle FROM ((file f LEFT JOIN anime a ON ((f.aid = a.aid))) LEFT JOIN episode e ON ((f.eid = e.eid))); -CREATE VIEW rename_data AS +CREATE VIEW rename_data AS SELECT f.fid, f.eid, f.aid, f.gid, a.anidb_update AS anime_anidb_update, a.entry_update AS anime_entry_update, a.my_update AS anime_my_update, a.title_english AS anime_title_english, a.title_romaji AS anime_title_romaji, a.title_kanji AS anime_title_kanji, a.description, a.year, a.start_date, a.end_date, a.type AS anime_type, a.total_episode_count, a.highest_epno, a.rating AS anime_rating, a.votes AS anime_votes, a.temp_rating, a.temp_votes, a.my_vote AS anime_my_vote, a.my_vote_date AS anime_my_vote_date, a.my_temp_vote, a.my_temp_vote_date, @@ -291,3 +291,17 @@ CREATE RULE unknown_file_ignore_duplicate AS ON INSERT TO unknown_file WHERE (EX COMMENT ON RULE unknown_file_ignore_duplicate ON unknown_file IS 'Adding the same file more than once can happen'; CREATE RULE new_pending_mylist_update_rule AS ON INSERT TO pending_mylist_update DO NOTIFY new_pending_mylist_update; + + +-- Update rules +CREATE OR REPLACE RULE anime_update_rule AS + ON UPDATE TO anime DO SELECT pg_notify('anime_update', new.aid::text); + +CREATE OR REPLACE RULE episode_update_rule AS + ON UPDATE TO episode DO SELECT pg_notify('episode_update', new.eid::text || ',' || new.aid::text); + +CREATE OR REPLACE RULE file_update_rule AS + ON UPDATE TO file DO SELECT pg_notify('file_update', new.fid::text || ',' || new.eid::text || ',' || new.aid::text); + +CREATE OR REPLACE RULE file_location_update_rule AS + ON UPDATE TO file_location DO SELECT pg_notify('file_location_update', new.location_id::text); -- 2.52.0