From 2e9ec030ad5e8c6d247917bcb5376a97fd4d1422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Sun, 13 Aug 2023 11:33:33 +0200 Subject: [PATCH] Implement support for updating FolderModel without resetting the whole model This will make views keep their state after an update. --- YACReaderLibrary/db/folder_model.cpp | 232 +++++++++++++++++++++------ YACReaderLibrary/db/folder_model.h | 21 ++- 2 files changed, 202 insertions(+), 51 deletions(-) diff --git a/YACReaderLibrary/db/folder_model.cpp b/YACReaderLibrary/db/folder_model.cpp index 0eec1c27..1131a1c4 100644 --- a/YACReaderLibrary/db/folder_model.cpp +++ b/YACReaderLibrary/db/folder_model.cpp @@ -53,22 +53,22 @@ void drawMacOSXFinishedFolderIcon() #define ROOT 1 +FolderItem *createRoot() +{ + QList rootData; + rootData << "root"; + auto root = new FolderItem(rootData); + root->id = ROOT; + root->parentItem = nullptr; + + return root; +} + FolderModel::FolderModel(QObject *parent) : QAbstractItemModel(parent), isSubfolder(false), rootItem(nullptr), folderIcon(YACReader::noHighlightedIcon(":/images/sidebar/folder.svg")), folderFinishedIcon(YACReader::noHighlightedIcon(":/images/sidebar/folder_finished.svg")), showRecent(false), recentDays(1) { } -FolderModel::FolderModel(QSqlQuery &sqlquery, QObject *parent) - : QAbstractItemModel(parent), isSubfolder(false), rootItem(nullptr), showRecent(false), recentDays(1) -{ - QList rootData; - rootData << "root"; // id 1, parent 1, title "root" - rootItem = new FolderItem(rootData); - rootItem->id = ROOT; - rootItem->parentItem = nullptr; - setupModelData(sqlquery, rootItem); -} - FolderModel::~FolderModel() { if (rootItem != nullptr) @@ -108,13 +108,147 @@ QHash FolderModel::roleNames() const void FolderModel::reload() { - setupModelData(_databasePath); + if (!isSubfolder) { + auto newModelData = createModelData(_databasePath); + + takeUpdatedChildrenInfo(rootItem, QModelIndex(), newModelData.rootItem); + + // copy items from newModelData to this model that are not in this model + foreach (auto key, newModelData.items.keys()) { + if (!items.contains(key)) { + items[key] = (newModelData.items[key]); + } + } + + delete newModelData.rootItem; + } else { + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + + QSqlQuery selectQuery(db); + selectQuery.prepare("SELECT * FROM folder WHERE parentId = :parentId and id <> 1"); + selectQuery.bindValue(":parentId", rootItem->id); + selectQuery.exec(); + + auto tempRoot = new FolderItem(rootItem->getData(), rootItem->parentItem); + tempRoot->id = rootItem->id; + auto newModelData = createModelData(selectQuery, tempRoot); + takeUpdatedChildrenInfo(rootItem, QModelIndex(), newModelData.rootItem); + + items = newModelData.items; + + // copy items from newModelData to this model that are not in this model + foreach (auto key, newModelData.items.keys()) { + if (!items.contains(key)) { + items[key] = (newModelData.items[key]); + } + } + + delete newModelData.rootItem; + + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + } +} + +void FolderModel::takeUpdatedChildrenInfo(FolderItem *parent, const QModelIndex &parentModelIndex, FolderItem *updated) +{ + auto currentChildren = parent->children(); + auto updatedChildren = updated->children(); + + int lenght = currentChildren.size(); + int lenghtUpdated = updatedChildren.size(); + + int m; // index that reflects modifications on the actual model for this parent + int i; // index of the original children before update + int j; // index of the updated children + for (m = 0, i = 0, j = 0; (i < lenght) && (j < lenghtUpdated);) { + auto child = currentChildren[i]; + auto updatedChild = updatedChildren[j]; + + // same folder + auto sameFolderId = child->id == updatedChild->id; // and also same name + if (sameFolderId) { + // 1. check if child data needs to be udpated + if (child->getData() != updatedChild->getData()) { + auto modelIndexToUpdate = index(m, 0, parentModelIndex); + + child->setData(updatedChild->getData()); + + emit dataChanged(modelIndexToUpdate, modelIndexToUpdate); + } + + // 2. update children info + takeUpdatedChildrenInfo(child, index(m, 0, parentModelIndex), updatedChild); + + m++; + i++; + j++; + continue; + } + + auto childName = child->data(Name).toString(); + auto childUpdatedName = updatedChild->data(Name).toString(); + + // folder added + if (!naturalSortLessThanCI(childName, childUpdatedName)) { + beginInsertRows(parentModelIndex, m, m); + + parent->addChild(updatedChild, m); + updated->removeChild(updatedChild); + + endInsertRows(); + + m++; + j++; + continue; + } + + // folder removed + if (naturalSortLessThanCI(childName, childUpdatedName)) { + beginRemoveRows(parentModelIndex, m, m); + + delete parent->child(m); + parent->removeChild(m); + + endRemoveRows(); + + i++; + continue; + } + } + + // add remaining children + for (; j < lenghtUpdated; j++) { + auto updatedChild = updatedChildren[j]; + + beginInsertRows(parentModelIndex, m, m); + + parent->addChild(updatedChild, m); + updated->removeChild(updatedChild); + + endInsertRows(); + + m++; + } + + // remove remaining children + for (; i < lenght; i++) { + beginRemoveRows(parentModelIndex, m, m); + + delete parent->child(m); + parent->removeChild(m); + + endRemoveRows(); + } } void FolderModel::reload(const QModelIndex &index) { // TODO: reload just the content under index for better efficiency - setupModelData(_databasePath); + reload(); } QVariant FolderModel::data(const QModelIndex &index, int role) const @@ -290,48 +424,49 @@ int FolderModel::rowCount(const QModelIndex &parent) const void FolderModel::setupModelData(QString path) { beginResetModel(); + if (rootItem != nullptr) delete rootItem; // TODO comprobar que se libera bien la memoria rootItem = nullptr; - - // inicializar el nodo ra�z - QList rootData; - rootData << "root"; // id 0, padre 0, title "root" (el id, y el id del padre van a ir en la clase TreeItem) - rootItem = new FolderItem(rootData); - rootItem->id = ROOT; - rootItem->parentItem = nullptr; - - // cargar la base de datos _databasePath = path; - // crear la consulta + + setModelData(createModelData(path)); + + endResetModel(); +} + +void FolderModel::setModelData(const ModelData &modelData) +{ + rootItem = modelData.rootItem; + items = modelData.items; +} + +FolderModel::ModelData FolderModel::createModelData(const QString &path) const +{ + ModelData modelData; + QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(path); QSqlQuery selectQuery("select * from folder where id <> 1 order by parentId,name", db); - setupModelData(selectQuery, rootItem); + auto root = createRoot(); + modelData = createModelData(selectQuery, root); + connectionName = db.connectionName(); } - // selectQuery.finish(); QSqlDatabase::removeDatabase(connectionName); - endResetModel(); + + return modelData; } -void FolderModel::fullSetup(QSqlQuery &sqlquery, FolderItem *parent) +FolderModel::ModelData FolderModel::createModelData(QSqlQuery &sqlquery, FolderItem *parent) const { - rootItem = parent; + QMap itemsLookup; - setupModelData(sqlquery, parent); -} - -void FolderModel::setupModelData(QSqlQuery &sqlquery, FolderItem *parent) -{ - // 64 bits para la primary key, es decir la misma precisi�n que soporta sqlit 2^64 - // el diccionario permitir� encontrar cualquier nodo del �rbol r�pidamente, de forma que a�adir un hijo a un padre sea O(1) - items.clear(); - // se a�ade el nodo 0 - items.insert(parent->id, parent); + // add parent to the lookup + itemsLookup.insert(parent->id, parent); QSqlRecord record = sqlquery.record(); @@ -366,11 +501,13 @@ void FolderModel::setupModelData(QSqlQuery &sqlquery, FolderItem *parent) item->id = sqlquery.value(id).toULongLong(); // la inserci�n de hijos se hace de forma ordenada - FolderItem *parent = items.value(sqlquery.value(parentId).toULongLong()); + FolderItem *parent = itemsLookup.value(sqlquery.value(parentId).toULongLong()); parent->appendChild(item); // se a�ade el item al map, de forma que se pueda encontrar como padre en siguientes iteraciones - items.insert(item->id, item); + itemsLookup.insert(item->id, item); } + + return FolderModel::ModelData { parent, itemsLookup }; } QString FolderModel::getDatabase() @@ -518,7 +655,7 @@ FolderModel *FolderModel::getSubfoldersModel(const QModelIndex &mi) selectQuery.exec(); if (parent != nullptr) { - model->fullSetup(selectQuery, parent); + model->setModelData(createModelData(selectQuery, parent)); } connectionName = db.connectionName(); @@ -553,7 +690,7 @@ Folder FolderModel::getFolder(const QModelIndex &mi) return folder; } -QModelIndex FolderModel::getIndexFromFolder(const Folder &folder, const QModelIndex &parent) +QModelIndex FolderModel::getIndexFromFolderId(qulonglong folderId, const QModelIndex &parent) { if (rootItem == nullptr) { return QModelIndex(); @@ -566,16 +703,16 @@ QModelIndex FolderModel::getIndexFromFolder(const Folder &folder, const QModelIn if (modelIndex.isValid()) { auto folderItem = static_cast(modelIndex.internalPointer()); - if (folderItem->id == folder.id) { + if (folderItem->id == folderId) { return modelIndex; } - auto childModelIndex = getIndexFromFolder(folder, modelIndex); + auto childModelIndex = getIndexFromFolderId(folderId, modelIndex); if (childModelIndex.isValid()) { auto folderItem = static_cast(childModelIndex.internalPointer()); - if (folderItem->id == folder.id) { + if (folderItem->id == folderId) { return childModelIndex; } } @@ -585,6 +722,11 @@ QModelIndex FolderModel::getIndexFromFolder(const Folder &folder, const QModelIn return QModelIndex(); } +QModelIndex FolderModel::getIndexFromFolder(const Folder &folder, const QModelIndex &parent) +{ + return getIndexFromFolderId(folder.id, parent); +} + QModelIndex FolderModel::addFolderAtParent(const QString &folderName, const QModelIndex &parent) { FolderItem *parentItem; diff --git a/YACReaderLibrary/db/folder_model.h b/YACReaderLibrary/db/folder_model.h index eac85b3b..ee44d57c 100644 --- a/YACReaderLibrary/db/folder_model.h +++ b/YACReaderLibrary/db/folder_model.h @@ -44,7 +44,6 @@ class FolderModel : public QAbstractItemModel public: explicit FolderModel(QObject *parent = nullptr); - explicit FolderModel(QSqlQuery &sqlquery, QObject *parent = nullptr); ~FolderModel() override; // QAbstractItemModel methods @@ -72,9 +71,10 @@ public: void updateFolderType(const QModelIndexList &list, YACReader::FileType type); QStringList getSubfoldersNames(const QModelIndex &mi); - FolderModel *getSubfoldersModel(const QModelIndex &mi); + FolderModel *getSubfoldersModel(const QModelIndex &mi); // it creates a model that contains just the direct subfolders Folder getFolder(const QModelIndex &mi); + QModelIndex getIndexFromFolderId(qulonglong folderId, const QModelIndex &parent = QModelIndex()); QModelIndex getIndexFromFolder(const Folder &folder, const QModelIndex &parent = QModelIndex()); QModelIndex addFolderAtParent(const QString &folderName, const QModelIndex &parent); @@ -118,11 +118,20 @@ public slots: void updateFolderChildrenInfo(qulonglong folderId); private: - void fullSetup(QSqlQuery &sqlquery, FolderItem *parent); - void setupModelData(QSqlQuery &sqlquery, FolderItem *parent); + struct ModelData { + FolderItem *rootItem; // items tree + QMap items; // items lookup + }; - FolderItem *rootItem; // el árbol - QMap items; // relación entre folders + void setModelData(const ModelData &modelData); + ModelData createModelData(const QString &path) const; + ModelData createModelData(QSqlQuery &sqlquery, FolderItem *parent) const; + + // parent contains the current data in the model (parentModelIndex is its index), updated contains fresh info loaded from the DB, + void takeUpdatedChildrenInfo(FolderItem *parent, const QModelIndex &parentModelIndex, FolderItem *updated); + + FolderItem *rootItem; // items tree + QMap items; // items lookup QString _databasePath;