]> Some of my projects - localmylist.git/commitdiff
MyListModel can now update itself when database entries change.
authorAPTX <marek321@gmail.com>
Tue, 26 Mar 2013 22:07:36 +0000 (23:07 +0100)
committerAPTX <marek321@gmail.com>
Tue, 26 Mar 2013 22:07:36 +0000 (23:07 +0100)
localmylist/database.cpp
localmylist/database.h
localmylist/mylistmodel.cpp
localmylist/mylistmodel.h
localmylist/mylistnode.cpp
localmylist/mylistnode.h
localmylist/mylistnodedata.h
localmylist/share/schema/schema.sql

index 275d8d3a532ae1d429e3ab6ac2e22917371082ec..dee3533dd1b40a3c9f09f959a897981070d6461d 100644 (file)
@@ -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 &notification, 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 &notification)
+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);
+       }
 }
 
 
index 196d726c6379df86192e7c1105805a867b951db3..d569522e491fd1733f591997ff8991095e44eeac 100644 (file)
@@ -8,6 +8,8 @@
 #include <QSqlRecord>
 #include <QDateTime>
 #include <QStringList>
+#include <QSqlDriver>
+#include <QVariant>
 
 #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 &notification);
+       void handleNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload);
 
 private:
        void subscribeToNotifications();
index d7a38c9d71a2b258c3edac394767b1d909813b5a..4ac83e2701346596cad8415c8f3e5f3d7f1ad113 100644 (file)
@@ -1,5 +1,6 @@
 #include "mylistmodel.h"
 
+#include "mylist.h"
 #include "mylistnode.h"
 
 #include <QDebug>
@@ -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<MyListNode *>(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<MyListNode *>(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<MyListNode *>(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<MyListNode *>(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
index b1315b786509d0f9063cb3dda65ad9b7e3bd7c0f..0ccba04608d523296370ba96e0e37bce4421596e 100644 (file)
@@ -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<AnimeData> animeSet;
index 7ab2082450b619904ae8d0aad0fee975a791db7a..811d03c8117b9a98d3e5477d84b4d589d9a12dc2 100644 (file)
@@ -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";
index d4e01ccbc6b7857cb7693992e4c72137cd60afd2..fa429a9f8bf3ca91f5d98197aa56d604aed4f591 100644 (file)
@@ -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;
index 4ed0016fd16966f49d2e37ee96cc28567274f0f3..046f5aef8abf5102c160d8c2c42e958a418c86c6 100644 (file)
@@ -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_)
        {}
index 7d56fe95fa4cad70390a32aa1145b8276db5f5e5..fe3c07efa048f73470d295b185b93a50bed56e29 100644 (file)
@@ -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);