From a0fc4fbe394a0af1b5f3dba78520d33afed2d947 Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Mon, 26 Oct 2020 21:53:11 +0100
Subject: [PATCH] Multiple optimizations to make gamelist interactions feel
 more responsive.

Also removed a jump to the first line when editing the recently played gamelist.
---
 es-app/src/CollectionSystemManager.cpp        | 62 +++++++++++++------
 es-app/src/CollectionSystemManager.h          |  1 +
 .../src/views/gamelist/BasicGameListView.cpp  |  6 +-
 3 files changed, 48 insertions(+), 21 deletions(-)

diff --git a/es-app/src/CollectionSystemManager.cpp b/es-app/src/CollectionSystemManager.cpp
index 25792da31..de8e99c9a 100644
--- a/es-app/src/CollectionSystemManager.cpp
+++ b/es-app/src/CollectionSystemManager.cpp
@@ -24,6 +24,7 @@
 #include "guis/GuiInfoPopup.h"
 #include "utils/FileSystemUtil.h"
 #include "utils/StringUtil.h"
+#include "utils/TimeUtil.h"
 #include "views/gamelist/IGameListView.h"
 #include "views/ViewController.h"
 #include "FileData.h"
@@ -331,6 +332,12 @@ void CollectionSystemManager::refreshCollectionSystems(FileData* file)
 void CollectionSystemManager::updateCollectionSystem(FileData* file, CollectionSystemData sysData)
 {
     if (sysData.isPopulated) {
+        // Skip all custom collections where the game does not exist.
+        if (sysData.decl.isCustom) {
+            if (!inCustomCollection(sysData.system->getFullName(), file))
+                return;
+        }
+
         // Collection files use the full path as key, to avoid clashes.
         std::string key = file->getFullPath();
 
@@ -390,7 +397,6 @@ void CollectionSystemManager::updateCollectionSystem(FileData* file, CollectionS
                 CollectionFileData* newGame = new CollectionFileData(file, curSys);
                 rootFolder->addChild(newGame);
                 fileIndex->addToIndex(newGame);
-                ViewController::get()->onFileChanged(file, FILE_METADATA_CHANGED);
                 ViewController::get()->
                         getGameListView(curSys)->onFileChanged(newGame, FILE_METADATA_CHANGED);
             }
@@ -419,11 +425,20 @@ void CollectionSystemManager::updateCollectionSystem(FileData* file, CollectionS
         if (name == "recent") {
             trimCollectionCount(rootFolder, LAST_PLAYED_MAX);
             ViewController::get()->onFileChanged(rootFolder, FILE_METADATA_CHANGED);
-
-            // Select the first row of the gamelist (the game just played).
-            IGameListView* gameList =
-                    ViewController::get()->getGameListView(getSystemToView(sysData.system)).get();
-            gameList->setCursor(gameList->getFirstEntry());
+            // This is a bit of a hack to prevent a jump to the first line of the gamelist
+            // if an entry is manually adjusted from within the 'recent' gamelist, for example
+            // by toggling a game as favorite. If the time since the last played timestamp is
+            // less than two seconds, then assume that the game was actually just launched,
+            // and therefore jump to the first line. The two seconds is incredibly generous
+            // as normally it would rather be some milliseconds, but who knows what special
+            // circumstances could cause a slight delay so let's keep a large margin.
+            if (Utils::Time::now() -
+                    Utils::Time::stringToTime(file->metadata.get("lastplayed")) < 2) {
+                // Select the first row of the gamelist (the game just played).
+                IGameListView* gameList = ViewController::get()->
+                        getGameListView(getSystemToView(sysData.system)).get();
+                gameList->setCursor(gameList->getFirstEntry());
+            }
         }
         else {
             ViewController::get()->onFileChanged(rootFolder, FILE_SORTED);
@@ -606,6 +621,20 @@ void CollectionSystemManager::exitEditMode()
     mEditingCollectionSystemData->system->onMetaDataSavePoint();
 }
 
+bool CollectionSystemManager::inCustomCollection(
+        const std::string& collectionName, FileData* gameFile)
+{
+    auto collectionEntry = mCustomCollectionSystemsData.find(collectionName);
+
+    if (collectionEntry != mCustomCollectionSystemsData.end()) {
+        const std::unordered_map<std::string, FileData*>& children =
+                collectionEntry->second.system->getRootFolder()->getChildrenByFilename();
+        return children.find(gameFile->getFullPath()) != children.cend();
+    }
+
+    return false;
+}
+
 // Add or remove a game from a specific collection.
 bool CollectionSystemManager::toggleGameInCollection(FileData* file)
 {
@@ -634,35 +663,32 @@ bool CollectionSystemManager::toggleGameInCollection(FileData* file)
                 adding = false;
                 // If we found it, we need to remove it.
                 FileData* collectionEntry = children.at(key);
-                // Remove from index.
-                fileIndex->removeFromIndex(collectionEntry);
-                // Remove from bundle index as well, if needed.
-                if (systemViewToUpdate != sysData)
-                    systemViewToUpdate->getIndex()->removeFromIndex(collectionEntry);
-
                 ViewController::get()->getGameListView(systemViewToUpdate).get()->
                         remove(collectionEntry, false);
                 systemViewToUpdate->getRootFolder()->sort(rootFolder->getSortTypeFromString(
                         rootFolder->getSortTypeString()),
                         Settings::getInstance()->getBool("FavFirstCustom"));
+                if (rootFolder->getChildren().size() == 0) {
+                    ViewController::get()->reloadGameListView(systemViewToUpdate);
+                }
+
+                updateCollectionFolderMetadata(systemViewToUpdate);
             }
             else {
                 // We didn't find it here, so we should add it.
                 CollectionFileData* newGame = new CollectionFileData(file, sysData);
                 rootFolder->addChild(newGame);
-                fileIndex->addToIndex(newGame);
-                ViewController::get()->getGameListView(systemViewToUpdate)->
-                        onFileChanged(newGame, FILE_METADATA_CHANGED);
-                if (name == "recent")
-                    rootFolder->sort(rootFolder->getSortTypeFromString("last played, descending"));
 
+                systemViewToUpdate->getRootFolder()->sort(rootFolder->getSortTypeFromString(
+                        rootFolder->getSortTypeString()),
+                        Settings::getInstance()->getBool("FavFirstCustom"));
                 ViewController::get()->onFileChanged(systemViewToUpdate->
                         getRootFolder(), FILE_SORTED);
+                fileIndex->addToIndex(newGame);
 
                 // Add to bundle index as well, if needed.
                 if (systemViewToUpdate != sysData)
                     systemViewToUpdate->getIndex()->addToIndex(newGame);
-                refreshCollectionSystems(newGame);
             }
             saveCustomCollection(sysData);
         }
diff --git a/es-app/src/CollectionSystemManager.h b/es-app/src/CollectionSystemManager.h
index 2b690f766..6f1ad9ec2 100644
--- a/es-app/src/CollectionSystemManager.h
+++ b/es-app/src/CollectionSystemManager.h
@@ -97,6 +97,7 @@ public:
     void exitEditMode();
     inline bool isEditing() { return mIsEditingCustom; };
     inline std::string getEditingCollection() { return mEditingCollection; };
+    bool inCustomCollection(const std::string& collectionName, FileData* gameFile);
     bool toggleGameInCollection(FileData* file);
 
     SystemData* getSystemToView(SystemData* sys);
diff --git a/es-app/src/views/gamelist/BasicGameListView.cpp b/es-app/src/views/gamelist/BasicGameListView.cpp
index 7f3d39663..d22ecb34e 100644
--- a/es-app/src/views/gamelist/BasicGameListView.cpp
+++ b/es-app/src/views/gamelist/BasicGameListView.cpp
@@ -200,11 +200,11 @@ void BasicGameListView::remove(FileData *game, bool deleteFile)
     // Remove before repopulating (removes from parent), then update the view.
     delete game;
 
-    if (deleteFile)
+    if (deleteFile) {
         parent->sort(parent->getSortTypeFromString(parent->getSortTypeString()),
                 Settings::getInstance()->getBool("FavoritesFirst"));
-
-    onFileChanged(parent, FILE_REMOVED);
+        onFileChanged(parent, FILE_REMOVED);
+    }
 }
 
 void BasicGameListView::removeMedia(FileData *game)