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)));
+
+ connect(MyList::instance()->database(), SIGNAL(animeInsert(int)), this, SLOT(animeInsert(int)));
+ connect(MyList::instance()->database(), SIGNAL(episodeInsert(int,int)), this, SLOT(episodeInsert(int,int)));
+ connect(MyList::instance()->database(), SIGNAL(fileInsert(int,int,int)), this, SLOT(fileInsert(int,int,int)));
+ connect(MyList::instance()->database(), SIGNAL(fileLocationInsert(int)), this, SLOT(fileLocationInsert(int)));
}
MyListModel::~MyListModel()
if (!updatedNode)
return;
- updatedNode->updated();
+ if (updatedNode->updated(MyListNode::UpdateOperation))
+ updatedNode->parent()->moveChild(updatedNode, MyListNode::UpdateOperation);
}
void MyListModel::episodeUpdate(int eid, int aid)
return;
}
- updatedNode->updated();
+ if (updatedNode->updated(MyListNode::UpdateOperation))
+ updatedNode->parent()->moveChild(updatedNode, MyListNode::UpdateOperation);
}
void MyListModel::fileUpdate(int fid, int eid, int aid)
return;
}
- updatedNode->updated();
+ if (updatedNode->updated(MyListNode::UpdateOperation))
+ updatedNode->parent()->moveChild(updatedNode, MyListNode::UpdateOperation);
}
void MyListModel::fileLocationUpdate(int id)
if (!updatedNode)
return;
- updatedNode->updated();
+ if (updatedNode->updated(MyListNode::UpdateOperation))
+ updatedNode->parent()->moveChild(updatedNode, MyListNode::UpdateOperation);
+}
+
+void MyListModel::animeInsert(int aid)
+{
+ if (animeIndex(aid).isValid())
+ return;
+ rootItem->childAdded(aid);
+}
+
+void MyListModel::episodeInsert(int eid, int aid)
+{
+ if (episodeIndex(eid).isValid())
+ return;
+
+ MyListNode *parentNode = node(animeIndex(aid));
+
+ if (!parentNode)
+ return;
+
+ parentNode->childAdded(eid);
+}
+
+void MyListModel::fileInsert(int fid, int eid, int aid)
+{
+ if (fileIndex(fid).isValid())
+ return;
+
+ MyListNode *parentNode = node(episodeIndex(eid));
+
+ if (!parentNode)
+ {
+ parentNode = node(animeIndex(aid));
+ if (!parentNode)
+ return;
+ parentNode->updated(MyListNode::UpdateOperation);
+ return;
+ }
+
+ parentNode->childAdded(eid);
+}
+
+void MyListModel::fileLocationInsert(int id)
+{
+ if (fileLocationIndex(id).isValid())
+ return;
+
+
}
QModelIndex MyListModel::index(MyListNode *node) const
#include "database.h"
#include <QSqlQuery>
#include "sqlasyncquery.h"
+#include <algorithm>
#include <QDebug>
namespace LocalMyList {
return "SELECT COUNT(aid) FROM anime";
}
-void MyListNode::childUpdate(const EpisodeData &oldData, const EpisodeData &newData)
+void MyListNode::childUpdate(const EpisodeData &oldData, const EpisodeData &newData, Operation type)
{
Q_UNUSED(oldData)
Q_UNUSED(newData)
+ Q_UNUSED(type)
}
-void MyListNode::childUpdate(const FileData &oldData, const FileData &newData)
+void MyListNode::childUpdate(const FileData &oldData, const FileData &newData, Operation type)
{
Q_UNUSED(oldData)
- Q_UNUSED(newData)}
+ Q_UNUSED(newData)
+ Q_UNUSED(type)
+}
-void MyListNode::childUpdate(const FileLocationData &oldData, const FileLocationData &newData)
+void MyListNode::childUpdate(const FileLocationData &oldData, const FileLocationData &newData, Operation type)
{
Q_UNUSED(oldData)
Q_UNUSED(newData)
+ Q_UNUSED(type)
+}
+
+MyListNodeCompare MyListNode::compareFunction() const
+{
+ return [](MyListNode *a, MyListNode *b) -> bool
+ {
+ const MyListAnimeNode *aa = static_cast<MyListAnimeNode *>(a);
+ const MyListAnimeNode *ab = static_cast<MyListAnimeNode *>(b);
+ return aa->internalData().data.titleRomaji < ab->internalData().data.titleRomaji;
+ };
+}
+
+MyListNode::MoveType MyListNode::moveChild(MyListNode *child, Operation type)
+{
+ const QModelIndex idx = model->index(this);
+
+ if (type == InsertOperation)
+ {
+ auto it = std::upper_bound(childItems.begin(), childItems.end(), child, compareFunction());
+
+ if (it == childItems.end() && canFetchMore())
+ {
+ delete child;
+ return OutOfBoundsMove;
+ }
+
+ const int newRow = qMax(0, (it - childItems.begin()) - 1);
+
+ model->beginInsertRows(idx, newRow, newRow);
+ it = childItems.insert(it, child);
+ model->endInsertRows();
+ return SuccessfulMove;
+ }
+
+ const auto oldPos = std::find(childItems.begin(), childItems.end(), child);
+ const int oldRow = oldPos == childItems.end() ? -1 : oldPos - childItems.begin();
+
+ if (type == DeleteOperation)
+ {
+ if (oldRow != -1)
+ {
+ model->beginRemoveRows(idx, oldRow, oldRow);
+ childItems.removeAt(oldRow);
+ model->endRemoveRows();
+ }
+ delete child;
+ return SuccessfulMove;
+ }
+
+ auto lower = std::upper_bound(childItems.begin(), oldPos, child, compareFunction());
+ auto upper = std::lower_bound(oldPos, childItems.end(), child, compareFunction());
+
+ // No move needed
+ if (lower == upper)
+ {
+ return NoMove;
+ }
+
+ decltype(childItems.begin()) it;
+
+ // New pos in upper part
+ if (lower == oldPos)
+ it = upper;
+ else
+ it = lower;
+
+ // Added item is not in the currently loaded data
+ if (it == childItems.end() && canFetchMore())
+ {
+ model->beginRemoveRows(idx, oldRow, oldRow);
+ childItems.removeAt(oldRow);
+ model->endRemoveRows();
+ delete child;
+
+ return OutOfBoundsMove;
+ }
+
+ const int nextRow = it - childItems.begin();
+ const int newRow = qMax(0, nextRow - 1);
+
+ if (oldRow < newRow)
+ {
+ model->beginMoveRows(idx, oldRow, oldRow, idx, nextRow);
+ childItems.move(oldRow, newRow);
+ model->endMoveRows();
+ }
+ else
+ {
+ model->beginMoveRows(idx, oldRow, oldRow, idx, newRow);
+ childItems.move(oldRow, nextRow);
+ model->endMoveRows();
+ }
+
+ return SuccessfulMove;
}
MyListNode::NodeType MyListNode::type() const
return 0;
}
-void MyListNode::updated()
+void MyListNode::childAdded(int id)
+{
+ qDebug() << "childAdded" << id;
+ if (m_totalRowCount == -1)
+ return;
+
+ MyListAnimeNode *newChild = new MyListAnimeNode(model, AnimeData(id), this);
+ newChild->updated(InsertOperation);
+
+ MoveType result = moveChild(newChild, InsertOperation);
+
+ if (result == SuccessfulMove)
+ ++m_totalRowCount;
+}
+
+bool MyListNode::updated(Operation type)
{
+ Q_UNUSED(type)
+ return false;
}
// ------
return animeData.data.aid;
}
-void MyListAnimeNode::updated()
+void MyListAnimeNode::childAdded(int id)
+{
+ qDebug() << "MyListAnimeNode::childAdded" << id << m_totalRowCount;
+ if (m_totalRowCount == -1)
+ return;
+
+ MyListEpisodeNode *newChild = new MyListEpisodeNode(model, EpisodeData(id), this);
+ newChild->updated(InsertOperation);
+
+ MoveType result = moveChild(newChild, InsertOperation);
+
+ if (result == SuccessfulMove)
+ ++m_totalRowCount;
+}
+
+bool MyListAnimeNode::updated(Operation type)
{
+ Q_UNUSED(type)
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;
+ return false;
if (!q.next())
- return;
+ return false;
AnimeData newData;
fillAnimeData(newData, QSqlResultIterator(q));
+
+ bool orderingChange = animeData.data.titleRomaji != newData.data.titleKanji;
+
animeData = newData;
animeData.node = this;
model->nodeChanged(this);
+
+ return orderingChange;
}
-void MyListAnimeNode::childUpdate(const EpisodeData &oldData, const EpisodeData &newData)
+void MyListAnimeNode::childUpdate(const EpisodeData &oldData, const EpisodeData &newData, Operation type)
{
// Episode got watched
- if (!oldData.watchedDate.isValid() && newData.watchedDate.isValid())
+ if ((type == InsertOperation || !oldData.watchedDate.isValid()) && newData.watchedDate.isValid())
{
++animeData.watchedEpisodes;
model->nodeChanged(this);
}
// Episode got unwatched
- else if (oldData.watchedDate.isValid() && !newData.watchedDate.isValid())
+ else if ((type == DeleteOperation || !newData.watchedDate.isValid()) && oldData.watchedDate.isValid())
{
--animeData.watchedEpisodes;
model->nodeChanged(this);
}
}
+MyListNodeCompare MyListAnimeNode::compareFunction() const
+{
+ return [](MyListNode *a, MyListNode *b) -> bool
+ {
+ const MyListEpisodeNode *aa = static_cast<MyListEpisodeNode *>(a);
+ const MyListEpisodeNode *ab = static_cast<MyListEpisodeNode *>(b);
+ if (aa->internalData().episodeTypeOrdering == ab->internalData().episodeTypeOrdering)
+ return aa->internalData().data.epno < ab->internalData().data.epno;
+ return aa->internalData().episodeTypeOrdering < ab->internalData().episodeTypeOrdering;
+ };
+}
+
+const AnimeData &MyListAnimeNode::internalData() const
+{
+ return animeData;
+}
+
QString MyListAnimeNode::baseQuery()
{
return QString(
return episodeData.data.eid;
}
-void MyListEpisodeNode::updated()
+void MyListEpisodeNode::childAdded(int id)
+{
+ if (m_totalRowCount == -1)
+ return;
+
+ MyListFileNode *newChild = new MyListFileNode(model, FileData(id), this);
+ newChild->updated(InsertOperation);
+
+ MoveType result = moveChild(newChild, InsertOperation);
+
+ if (result == SuccessfulMove)
+ ++m_totalRowCount;
+}
+
+bool MyListEpisodeNode::updated(Operation type)
{
+ Q_UNUSED(type)
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;
+ return false;
if (!q.next())
- return;
+ return false;
EpisodeData newData;
fillEpisodeData(newData, QSqlResultIterator(q));
- parent()->childUpdate(episodeData, newData);
+ parent()->childUpdate(episodeData, newData, type);
+ bool orderingChanged = episodeData.episodeTypeOrdering != newData.episodeTypeOrdering
+ || episodeData.data.epno != newData.data.epno;
episodeData = newData;
episodeData.node = this;
model->nodeChanged(this);
+
+ return orderingChanged;
}
-void MyListEpisodeNode::childUpdate(const FileData &oldData, const FileData &newData)
+void MyListEpisodeNode::childUpdate(const FileData &oldData, const FileData &newData, Operation type)
{
// File from episode got watched
- if (!oldData.data.myWatched.isValid() && newData.data.myWatched.isValid())
+ if ((type == InsertOperation || !oldData.data.myWatched.isValid()) && newData.data.myWatched.isValid())
{
if (!episodeData.watchedDate.isValid()
|| (episodeData.watchedDate.isValid()
episodeData.watchedDate = newData.data.myWatched;
model->nodeChanged(this);
- parent()->childUpdate(oldData, episodeData);
+ parent()->childUpdate(oldData, episodeData, UpdateOperation);
}
}
// Watched date changed
- else if (oldData.data.myWatched.isValid() && newData.data.myWatched.isValid())
+ else if ((type == UpdateOperation && oldData.data.myWatched.isValid()) && newData.data.myWatched.isValid())
{
if (episodeData.watchedDate.isValid() && newData.data.myWatched < episodeData.watchedDate)
{
episodeData.watchedDate = newData.data.myWatched;
model->nodeChanged(this);
- parent()->childUpdate(oldData, episodeData);
+ parent()->childUpdate(oldData, episodeData, UpdateOperation);
}
}
// File got unwatched
- else if (oldData.data.myWatched.isValid() && !newData.data.myWatched.isValid())
+ else if ((type == DeleteOperation || !newData.data.myWatched.isValid()) && oldData.data.myWatched.isValid())
{
// No real way to get the proper watched date without
// looking at other children.
- updated();
+ updated(UpdateOperation);
}
}
+MyListNodeCompare MyListEpisodeNode::compareFunction() const
+{
+ return [](MyListNode *a, MyListNode *b) -> bool
+ {
+ const MyListFileNode *aa = static_cast<MyListFileNode *>(a);
+ const MyListFileNode *ab = static_cast<MyListFileNode *>(b);
+ return aa->internalData().data.fid < ab->internalData().data.fid;
+ };
+}
+
+const EpisodeData &MyListEpisodeNode::internalData() const
+{
+ return episodeData;
+}
+
QString MyListEpisodeNode::baseQuery()
{
return QString(
" 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 "
+ " et.ordering, %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);
+ data.episodeTypeOrdering = query.value(1).toInt();
+ Database::readEpisodeData(query, data.data, 2);
}
// ---------------
return fileData.data.fid;
}
-void MyListFileNode::updated()
+void MyListFileNode::childAdded(int id)
+{
+ Q_UNUSED(id)
+}
+
+bool MyListFileNode::updated(Operation type)
{
QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString(
"SELECT %1 "
q.bindValue(":fid", id());
if(!MyList::instance()->database()->exec(q))
- return;
+ return false;
if (!q.next())
- return;
+ return false;
FileData newData;
fillFileData(newData, QSqlResultIterator(q));
- parent()->childUpdate(fileData, newData);
+ parent()->childUpdate(fileData, newData, type);
fileData = newData;
fileData.node = this;
model->nodeChanged(this);
+
+ return false;
+}
+
+MyListNodeCompare MyListFileNode::compareFunction() const
+{
+ return [](MyListNode *a, MyListNode *b) -> bool
+ {
+ const MyListFileLocationNode *aa = static_cast<MyListFileLocationNode *>(a);
+ const MyListFileLocationNode *ab = static_cast<MyListFileLocationNode *>(b);
+ return aa->internalData().data.locationId < ab->internalData().data.locationId;
+ };
+}
+
+const FileData &MyListFileNode::internalData() const
+{
+ return fileData;
}
QString MyListFileNode::baseQuery()
return fileLocationData.data.locationId;
}
-void MyListFileLocationNode::updated()
+bool MyListFileLocationNode::updated(Operation type)
{
QSqlQuery q = MyList::instance()->database()->prepareOneShot(QString(
" %1 "
q.bindValue(":locationId", id());
if(!MyList::instance()->database()->exec(q))
- return;
+ return false;
if (!q.next())
- return;
+ return false;
FileLocationData newData;
fillFileLocationData(newData, QSqlResultIterator(q));
- parent()->childUpdate(fileLocationData, newData);
+ parent()->childUpdate(fileLocationData, newData, type);
fileLocationData = newData;
fileLocationData.node = this;
model->nodeChanged(this);
+ return false;
+}
+
+const FileLocationData &MyListFileLocationNode::internalData() const
+{
+ return fileLocationData;
}
QString MyListFileLocationNode::baseQuery()
class SqlAsyncQuery;
class SqlResultIteratorInterface;
+typedef bool (*MyListNodeCompare)(MyListNode *a, MyListNode *b);
+
class LOCALMYLISTSHARED_EXPORT MyListNode
{
public:
FileLocationNode
};
+ enum Operation {
+ UpdateOperation,
+ InsertOperation,
+ DeleteOperation
+ };
+
+ enum MoveType {
+ NoMove,
+ SuccessfulMove,
+ OutOfBoundsMove
+ };
+
MyListNode(MyListModel *model, NodeType type = RootNode, MyListNode *parent = 0);
virtual ~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);
+ virtual void childAdded(int id);
+ virtual bool updated(Operation type);
+ virtual void childUpdate(const EpisodeData &oldData, const EpisodeData &newData, Operation type);
+ virtual void childUpdate(const FileData &oldData, const FileData &newData, Operation type);
+ virtual void childUpdate(const FileLocationData &oldData, const FileLocationData &newData, Operation type);
+
+ virtual MyListNodeCompare compareFunction() const;
+
+ MoveType moveChild(MyListNode *child, Operation type);
protected:
virtual QString totalRowCountSql() const;
void fetchMore();
void fetchComplete();
int id() const;
- void updated();
- void childUpdate(const EpisodeData &oldData, const EpisodeData &newData);
+ void childAdded(int id);
+ bool updated(Operation type);
+ void childUpdate(const EpisodeData &oldData, const EpisodeData &newData, Operation type);
+
+ MyListNodeCompare compareFunction() const;
+
+ const AnimeData &internalData() const;
static QString baseQuery();
static void fillAnimeData(AnimeData &data, SqlResultIteratorInterface &q);
void fetchMore();
void fetchComplete();
int id() const;
- void updated();
- void childUpdate(const FileData &oldData, const FileData &newData);
+ void childAdded(int id);
+ bool updated(Operation type);
+ void childUpdate(const FileData &oldData, const FileData &newData, Operation type);
+
+ MyListNodeCompare compareFunction() const;
+
+ const EpisodeData &internalData() const;
static QString baseQuery();
static void fillEpisodeData(EpisodeData &data, SqlResultIteratorInterface &query);
void fetchMore();
void fetchComplete();
int id() const;
- void updated();
+ void childAdded(int id);
+ bool updated(Operation type);
+
+ MyListNodeCompare compareFunction() const;
+
+ const FileData &internalData() const;
static QString baseQuery();
static void fillFileData(FileData &data, SqlResultIteratorInterface &query);
void fetchMore();
void fetchComplete();
int id() const;
- void updated();
+ bool updated(Operation type);
+
+ const FileLocationData &internalData() const;
static QString baseQuery();
static void fillFileLocationData(FileLocationData &data, SqlResultIteratorInterface &query);