From 9089c4ffe5a8582063801e70513897e071ef005e Mon Sep 17 00:00:00 2001
From: Evan Husted <greem@greemdev.net>
Date: Tue, 21 Jan 2025 18:59:19 -0600
Subject: [PATCH] misc: chore: Multi/Single file/folder picker extensions (for
 convenience) The result of these extensions is an empty Optional when the
 user hits Cancel on the shown file picker.

---
 src/Ryujinx/Common/ApplicationHelper.cs       | 29 +++++-------
 .../Utilities/StorageProviderExtensions.cs    | 47 +++++++++++++++++++
 2 files changed, 58 insertions(+), 18 deletions(-)
 create mode 100644 src/Ryujinx/Utilities/StorageProviderExtensions.cs

diff --git a/src/Ryujinx/Common/ApplicationHelper.cs b/src/Ryujinx/Common/ApplicationHelper.cs
index 61c5cfe00..e5b4da728 100644
--- a/src/Ryujinx/Common/ApplicationHelper.cs
+++ b/src/Ryujinx/Common/ApplicationHelper.cs
@@ -1,6 +1,6 @@
-using Avalonia.Controls.Notifications;
 using Avalonia.Platform.Storage;
 using Avalonia.Threading;
+using Gommon;
 using LibHac;
 using LibHac.Account;
 using LibHac.Common;
@@ -15,6 +15,7 @@ using LibHac.Tools.FsSystem.NcaUtils;
 using Ryujinx.Ava.Common.Locale;
 using Ryujinx.Ava.UI.Controls;
 using Ryujinx.Ava.UI.Helpers;
+using Ryujinx.Ava.Utilities;
 using Ryujinx.Ava.Utilities.Configuration;
 using Ryujinx.Common.Helper;
 using Ryujinx.Common.Logging;
@@ -418,35 +419,27 @@ namespace Ryujinx.Ava.Common
 
         public static async Task ExtractAoc(IStorageProvider storageProvider, string updateFilePath, string updateName)
         {
-            var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
+            Optional<IStorageFolder> result = await storageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
             {
-                Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
-                AllowMultiple = false,
+                Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle]
             });
 
-            if (result.Count == 0)
-            {
-                return;
-            }
-
-            ExtractAoc(result[0].Path.LocalPath, updateFilePath, updateName);
+            if (!result.HasValue) return;
+            
+            ExtractAoc(result.Value.Path.LocalPath, updateFilePath, updateName);
         }
 
 
         public static async Task ExtractSection(IStorageProvider storageProvider, NcaSectionType ncaSectionType, string titleFilePath, string titleName, int programIndex = 0)
         {
-            var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
+            Optional<IStorageFolder> result = await storageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
             {
-                Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
-                AllowMultiple = false,
+                Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle]
             });
 
-            if (result.Count == 0)
-            {
-                return;
-            }
+            if (!result.HasValue) return;
 
-            ExtractSection(result[0].Path.LocalPath, ncaSectionType, titleFilePath, titleName, programIndex);
+            ExtractSection(result.Value.Path.LocalPath, ncaSectionType, titleFilePath, titleName, programIndex);
         }
 
         public static (Result? result, bool canceled) CopyDirectory(FileSystemClient fs, string sourcePath, string destPath, CancellationToken token)
diff --git a/src/Ryujinx/Utilities/StorageProviderExtensions.cs b/src/Ryujinx/Utilities/StorageProviderExtensions.cs
new file mode 100644
index 000000000..502b4a541
--- /dev/null
+++ b/src/Ryujinx/Utilities/StorageProviderExtensions.cs
@@ -0,0 +1,47 @@
+using Avalonia.Platform.Storage;
+using Gommon;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Ava.Utilities
+{
+    public static class StorageProviderExtensions
+    {
+        public static async Task<Optional<IStorageFolder>> OpenSingleFolderPickerAsync(this IStorageProvider storageProvider, FolderPickerOpenOptions openOptions = null) =>
+            await storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, false))
+                .Then(folders => folders.FindFirst());
+
+        public static async Task<Optional<IStorageFile>> OpenSingleFilePickerAsync(this IStorageProvider storageProvider, FilePickerOpenOptions openOptions = null) =>
+            await storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, false))
+                .Then(files => files.FindFirst());
+        
+        public static async Task<Optional<IReadOnlyList<IStorageFolder>>> OpenMultiFolderPickerAsync(this IStorageProvider storageProvider, FolderPickerOpenOptions openOptions = null) =>
+            await storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, true))
+                .Then(folders => folders.Count > 0 ? Optional.Of(folders) : default);
+
+        public static async Task<Optional<IReadOnlyList<IStorageFile>>> OpenMultiFilePickerAsync(this IStorageProvider storageProvider, FilePickerOpenOptions openOptions = null) =>
+            await storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, true))
+                .Then(files => files.Count > 0 ? Optional.Of(files) : default);
+        
+        private static FilePickerOpenOptions FixOpenOptions(this FilePickerOpenOptions openOptions, bool allowMultiple)
+        {
+            if (openOptions is null)
+                return new FilePickerOpenOptions { AllowMultiple = allowMultiple };
+
+            openOptions.AllowMultiple = allowMultiple;
+            
+            return openOptions;
+        }
+        
+        private static FolderPickerOpenOptions FixOpenOptions(this FolderPickerOpenOptions openOptions, bool allowMultiple)
+        {
+            if (openOptions is null)
+                return new FolderPickerOpenOptions { AllowMultiple = allowMultiple };
+
+            openOptions.AllowMultiple = allowMultiple;
+            
+            return openOptions;
+        }
+    }
+}