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();
d->db.close();
+ auto subscribedNotifications = d->db.driver()->subscribedToNotifications();
+ foreach (const QString ¬ification, subscribedNotifications)
+ d->db.driver()->unsubscribeFromNotification(notification);
+
emit disconnected();
}
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)
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);
+ }
}
#include <QSqlRecord>
#include <QDateTime>
#include <QStringList>
+#include <QSqlDriver>
+#include <QVariant>
#include "databaseclasses.h"
#include "sqlresultiteratorinterface.h"
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();
#include "mylistmodel.h"
+#include "mylist.h"
#include "mylistnode.h"
#include <QDebug>
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()
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)
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)
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
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;
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;
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());
{
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;
}
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;
return 0;
}
+void MyListNode::updated()
+{
+}
+
// ------
MyListAnimeNode::MyListAnimeNode(MyListModel *model, const AnimeData &data, MyListNode *parent) :
{
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());
{
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;
}
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) :
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;
}
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
{
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;
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());
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
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";
class MyListModel;
class SqlAsyncQuery;
+class SqlResultIteratorInterface;
class LOCALMYLISTSHARED_EXPORT MyListNode
{
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;
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;
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;
{
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;
{
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;
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)
{}
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_)
{}
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)
{}
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_)
{}
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,
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);