From 30cae46cbdf44021e850f63122d90d803052ca9d Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 31 Mar 2021 14:57:28 +0900
Subject: [PATCH] Group large drag drop imports into a single operation

---
 osu.Desktop/OsuGameDesktop.cs | 34 +++++++++++++++++++++++++++++++---
 osu.Game/OsuGameBase.cs       |  3 +++
 2 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index b2487568ce..0c21c75290 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -2,6 +2,7 @@
 // See the LICENCE file in the repository root for full licence text.
 
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Reflection;
@@ -18,6 +19,7 @@ using osu.Framework.Screens;
 using osu.Game.Screens.Menu;
 using osu.Game.Updater;
 using osu.Desktop.Windows;
+using osu.Framework.Threading;
 using osu.Game.IO;
 
 namespace osu.Desktop
@@ -144,13 +146,39 @@ namespace osu.Desktop
             desktopWindow.DragDrop += f => fileDrop(new[] { f });
         }
 
+        private readonly List<string> importableFiles = new List<string>();
+        private ScheduledDelegate importSchedule;
+
         private void fileDrop(string[] filePaths)
         {
-            var firstExtension = Path.GetExtension(filePaths.First());
+            lock (importableFiles)
+            {
+                var firstExtension = Path.GetExtension(filePaths.First());
 
-            if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
+                if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
 
-            Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning);
+                importableFiles.AddRange(filePaths);
+
+                Logger.Log($"Adding {filePaths.Length} files for import");
+
+                // File drag drop operations can potentially trigger hundreds or thousands of these calls on some platforms.
+                // In order to avoid spawning multiple import tasks for a single drop operation, debounce a touch.
+                importSchedule?.Cancel();
+                importSchedule = Scheduler.AddDelayed(handlePendingImports, 100);
+            }
+        }
+
+        private void handlePendingImports()
+        {
+            lock (importableFiles)
+            {
+                Logger.Log($"Handling batch import of {importableFiles.Count} files");
+
+                var paths = importableFiles.ToArray();
+                importableFiles.Clear();
+
+                Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning);
+            }
         }
     }
 }
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index e1c7b67a8c..7eef0f7158 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -429,6 +429,9 @@ namespace osu.Game
 
         public async Task Import(params string[] paths)
         {
+            if (paths.Length == 0)
+                return;
+
             var extension = Path.GetExtension(paths.First())?.ToLowerInvariant();
 
             foreach (var importer in fileImporters)