From fe4e94a8277865a078b232eb85ab8c7b955280ef Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Fri, 1 Jan 2021 17:18:04 +0100
Subject: [PATCH] Collections are now properly initialized when enabled.

---
 es-app/src/CollectionSystemsManager.cpp       | 75 +++++++++++++++++++
 es-app/src/CollectionSystemsManager.h         |  3 +
 .../src/guis/GuiCollectionSystemsOptions.cpp  | 52 ++++++++++++-
 3 files changed, 128 insertions(+), 2 deletions(-)

diff --git a/es-app/src/CollectionSystemsManager.cpp b/es-app/src/CollectionSystemsManager.cpp
index 522174808..a5fbde3f7 100644
--- a/es-app/src/CollectionSystemsManager.cpp
+++ b/es-app/src/CollectionSystemsManager.cpp
@@ -935,6 +935,74 @@ void CollectionSystemsManager::reactivateCustomCollectionEntry(FileData* game)
     }
 }
 
+void CollectionSystemsManager::repopulateCollection(SystemData* sysData)
+{
+    for (auto it = mAutoCollectionSystemsData.cbegin();
+            it != mAutoCollectionSystemsData.cend(); it++) {
+        if ((*it).second.system == sysData) {
+            LOG(LogDebug) << "CollectionSystemsManager::repopulateCollection(): "
+                    "Repopulating auto collection '" << it->first << "'.";
+
+            CollectionSystemData* autoSystem = &mAutoCollectionSystemsData[it->first];
+            std::vector<FileData*> systemEntries =
+                    autoSystem->system->getRootFolder()->getFilesRecursive(true, true, false);
+
+            if (systemEntries.empty())
+                return;
+
+            // Delete all children from the system and remove them from the index too.
+            for (FileData* entry : systemEntries) {
+                autoSystem->system->getIndex()->removeFromIndex(entry);
+                autoSystem->system->getRootFolder()->removeChild(entry);
+                delete entry;
+            }
+
+            // Flag the collection as not populated so it gets repopulated.
+            autoSystem->isPopulated = false;
+            populateAutoCollection(autoSystem);
+
+            // The cursor value is now pointing to some random memory address so we need to set
+            // it to something valid, as done here by selecting the first child. This does however
+            // not mean it's the first row of the gamelist, so we follow up with selecting the
+            // first entry after that. If doing this second step without the first step we would
+            // crash the application as it would try to access the old (now invalid) pointer.
+            auto autoView = ViewController::get()->getGameListView(autoSystem->system).get();
+            autoView->setCursor(autoSystem->system->getRootFolder()->
+                    getChildrenRecursive().front());
+            autoView->setCursor(autoView->getFirstEntry());
+        }
+    }
+
+    for (auto it = mCustomCollectionSystemsData.cbegin();
+            it != mCustomCollectionSystemsData.cend(); it++) {
+        if ((*it).second.system == sysData) {
+            LOG(LogDebug) << "CollectionSystemsManager::repopulateCollection(): "
+                    "Repopulating custom collection '" << it->first << "'.";
+
+            CollectionSystemData* customSystem = &mCustomCollectionSystemsData[it->first];
+            std::vector<FileData*> systemEntries =
+                    customSystem->system->getRootFolder()->getFilesRecursive(true, true, false);
+
+            if (systemEntries.empty())
+                return;
+
+            for (FileData* entry : systemEntries) {
+                customSystem->system->getIndex()->removeFromIndex(entry);
+                customSystem->system->getRootFolder()->removeChild(entry);
+                delete entry;
+            }
+
+            customSystem->isPopulated = false;
+            populateCustomCollection(customSystem);
+
+            auto autoView = ViewController::get()->getGameListView(customSystem->system).get();
+            autoView->setCursor(customSystem->system->getRootFolder()->
+                    getChildrenRecursive().front());
+            autoView->setCursor(autoView->getFirstEntry());
+        }
+    }
+}
+
 void CollectionSystemsManager::initAutoCollectionSystems()
 {
     for (std::map<std::string, CollectionSystemDecl, stringComparator>::const_iterator
@@ -1038,6 +1106,13 @@ void CollectionSystemsManager::populateAutoCollection(CollectionSystemData* sysD
     // collection was trimmed down to 50 items. If we don't do this, the game count will
     // not be correct as it would include all the games prior to trimming.
     if (rootFolder->getName() == "recent") {
+        // The following is needed to avoid a crash when repopulating the system as the previous
+        // cursor pointer may point to a random memory address.
+        auto recentGamelist = ViewController::get()->getGameListView(rootFolder->getSystem()).get();
+        recentGamelist->setCursor(rootFolder->getSystem()->getRootFolder()->
+                getChildrenRecursive().front());
+        recentGamelist->setCursor(recentGamelist->getFirstEntry());
+
         ViewController::get()->getGameListView(rootFolder->getSystem()).get()->
                 onFileChanged(rootFolder->getChildren().front(), false);
     }
diff --git a/es-app/src/CollectionSystemsManager.h b/es-app/src/CollectionSystemsManager.h
index 81f8e559c..b90fd8288 100644
--- a/es-app/src/CollectionSystemsManager.h
+++ b/es-app/src/CollectionSystemsManager.h
@@ -112,6 +112,9 @@ public:
     // Reactivate a game in all custom collections where it has an entry in the configuration file.
     void reactivateCustomCollectionEntry(FileData* game);
 
+    // Repopulate the collection, which is basically a forced update of its complete content.
+    void repopulateCollection(SystemData* sysData);
+
     inline std::map<std::string, CollectionSystemData, stringComparator>
             getAutoCollectionSystems() { return mAutoCollectionSystemsData; };
     inline std::map<std::string, CollectionSystemData, stringComparator>
diff --git a/es-app/src/guis/GuiCollectionSystemsOptions.cpp b/es-app/src/guis/GuiCollectionSystemsOptions.cpp
index e5458346a..902de5bff 100644
--- a/es-app/src/guis/GuiCollectionSystemsOptions.cpp
+++ b/es-app/src/guis/GuiCollectionSystemsOptions.cpp
@@ -14,6 +14,7 @@
 #include "guis/GuiMsgBox.h"
 #include "guis/GuiSettings.h"
 #include "guis/GuiTextEditPopup.h"
+#include "utils/StringUtil.h"
 #include "views/ViewController.h"
 #include "CollectionSystemsManager.h"
 
@@ -49,7 +50,7 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
         collection_systems_auto->add(it->second.decl.longName, it->second.decl.name,
                 it->second.isEnabled);
     addWithLabel("AUTOMATIC GAME COLLECTIONS", collection_systems_auto);
-    addSaveFunc([this] {
+    addSaveFunc([this, autoSystems] {
         std::string autoSystemsSelected =
             Utils::String::vectorToCommaString(collection_systems_auto->getSelectedObjects(), true);
         std::string autoSystemsConfig = Settings::getInstance()->getString("CollectionSystemsAuto");
@@ -57,6 +58,29 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
             if (CollectionSystemsManager::get()->isEditing())
                 CollectionSystemsManager::get()->exitEditMode();
             Settings::getInstance()->setString("CollectionSystemsAuto", autoSystemsSelected);
+            // Check if any systems have been enabled, and if so repopulate them, which results in
+            // a complete initialization of their content. This is necessary as collections aren't
+            // updated while they are disabled.
+            std::vector<std::string> addedAutoSystems;
+            if (autoSystemsConfig == "") {
+                addedAutoSystems = Utils::String::delimitedStringToVector(autoSystemsSelected, ",");
+            }
+            else if (autoSystemsSelected != "") {
+                std::vector<std::string> selectedVector =
+                        Utils::String::delimitedStringToVector(autoSystemsSelected, ",");
+                std::vector<std::string> configuredVector =
+                        Utils::String::delimitedStringToVector(autoSystemsConfig, ",");
+                for (std::string system : selectedVector) {
+                    if (std::find(configuredVector.begin(), configuredVector.end(), system) ==
+                            configuredVector.end())
+                        addedAutoSystems.push_back(system);
+                }
+            }
+            if (!addedAutoSystems.empty()) {
+                for (std::string system : addedAutoSystems)
+                    CollectionSystemsManager::get()->
+                            repopulateCollection(autoSystems.find(system)->second.system);
+            }
             setNeedsSaving();
             setNeedsReloading();
             setNeedsCollectionsUpdate();
@@ -76,7 +100,7 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
         collection_systems_custom->add(it->second.decl.longName, it->second.decl.name,
                 it->second.isEnabled);
     addWithLabel("CUSTOM GAME COLLECTIONS", collection_systems_custom);
-    addSaveFunc([this] {
+    addSaveFunc([this, customSystems] {
         if (!mDeletedCustomCollection) {
             std::string customSystemsSelected = Utils::String::vectorToCommaString(
                     collection_systems_custom->getSelectedObjects(), true);
@@ -87,6 +111,30 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(
                     CollectionSystemsManager::get()->exitEditMode();
                 Settings::getInstance()->setString("CollectionSystemsCustom",
                         customSystemsSelected);
+                // Check if any systems have been enabled, and if so repopulate them, which
+                // results in a complete initialization of their content. This is necessary as
+                // collections aren't updated while they are disabled.
+                std::vector<std::string> addedCustomSystems;
+                if (customSystemsConfig == "") {
+                    addedCustomSystems =
+                            Utils::String::delimitedStringToVector(customSystemsSelected, ",");
+                }
+                else if (customSystemsSelected != "") {
+                    std::vector<std::string> selectedVector =
+                            Utils::String::delimitedStringToVector(customSystemsSelected, ",");
+                    std::vector<std::string> configuredVector =
+                            Utils::String::delimitedStringToVector(customSystemsConfig, ",");
+                    for (std::string system : selectedVector) {
+                        if (std::find(configuredVector.begin(), configuredVector.end(), system) ==
+                                configuredVector.end())
+                            addedCustomSystems.push_back(system);
+                    }
+                }
+                if (!addedCustomSystems.empty()) {
+                    for (std::string system : addedCustomSystems)
+                        CollectionSystemsManager::get()->
+                                repopulateCollection(customSystems.find(system)->second.system);
+                }
                 setNeedsSaving();
                 setNeedsReloading();
                 setNeedsCollectionsUpdate();