Skip to content

Commit 9c8a18c

Browse files
authored
Allow an empty Discover search to list every WinGet package (#5536)
1 parent 434a7b4 commit 9c8a18c

13 files changed

Lines changed: 363 additions & 48 deletions

File tree

‎src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,10 @@ partial void OnGlobalQueryTextChanged(string value)
658658
{
659659
MegaQueryText = "";
660660
MegaQueryVisible = true;
661-
Loader?.ClearPackages(emitFinishSignal: false);
661+
if (Loader is DiscoverablePackagesLoader discoverLoader)
662+
discoverLoader.ResetSearch();
663+
else
664+
Loader?.ClearPackages(emitFinishSignal: false);
662665
}
663666
else
664667
{
@@ -717,7 +720,7 @@ private Func<PackageWrapper, bool> BuildPackageFilter(out string query)
717720
foreach (var f in filters) normalizedQuery = f(normalizedQuery);
718721
query = normalizedQuery;
719722

720-
Func<IPackage, bool> matchFunc = SearchMode switch
723+
Func<IPackage, bool> matchFunc = normalizedQuery.Length == 0 ? _ => true : SearchMode switch
721724
{
722725
SearchMode.Name => pkg => FilterHelpers.NameContains(pkg, normalizedQuery, filters),
723726
SearchMode.Id => pkg => FilterHelpers.IdContains(pkg, normalizedQuery, filters),
@@ -759,7 +762,7 @@ private void UpdateFilteredState(string query)
759762
{
760763
bool noQuery = string.IsNullOrWhiteSpace(query);
761764
BackgroundText = noQuery ? NoPackagesText : NoMatchesText;
762-
BackgroundTextVisible = !MegaQueryBoxEnabled || !noQuery;
765+
BackgroundTextVisible = !MegaQueryBoxEnabled || !noQuery || !MegaQueryVisible;
763766
LoadingImageVisible = false;
764767
NoPackagesImageVisible = noQuery && BackgroundTextVisible && NoPackagesImage is not null;
765768
}

‎src/UniGetUI.PackageEngine.Enums/Enums.cs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
namespace UniGetUI.PackageEngine.Enums
22
{
3+
public static class SearchLimits
4+
{
5+
public const int MaxEmptyQueryResultsPerSource = 100;
6+
}
7+
38
/// <summary>
49
/// Represents the installation scope of a package
510
/// </summary>

‎src/UniGetUI.PackageEngine.Enums/ManagerCapabilities.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public struct ManagerCapabilities
4343
public bool SupportsCustomScopesOnUpdate = true;
4444
public bool SupportsCustomScopesOnUninstall = true;
4545
public bool SupportsPreRelease = false;
46+
public bool SupportsEmptySearchQuery = false;
4647
public bool SerializesOperations = false;
4748
public bool SupportsCustomLocations = false;
4849
public bool SupportsCustomSources = false;

‎src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetHelper.cs‎

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ public IReadOnlyList<Package> FindPackages_UnSafe(string query)
184184
List<Package> packages = [];
185185
INativeTaskLogger logger = Manager.TaskLogger.CreateNew(LoggableTaskType.FindPackages);
186186
Dictionary<
187-
(PackageCatalogReference, PackageMatchField),
187+
(PackageCatalogReference, PackageMatchField?),
188188
Task<FindPackagesResult>
189189
> FindPackageTasks = [];
190190

@@ -203,24 +203,27 @@ public IReadOnlyList<Package> FindPackages_UnSafe(string query)
203203
ConnectResult result = CatalogReference.Connect();
204204
if (result.Status == ConnectResultStatus.Ok)
205205
{
206-
foreach (
207-
var filter_type in new[]
208-
{
209-
PackageMatchField.Name,
210-
PackageMatchField.Id,
211-
PackageMatchField.Moniker,
212-
}
213-
)
206+
PackageMatchField?[] filterTypes = query.Length == 0
207+
? [null]
208+
: [PackageMatchField.Name, PackageMatchField.Id, PackageMatchField.Moniker];
209+
foreach (PackageMatchField? filter_type in filterTypes)
214210
{
215211
FindPackagesOptions PackageFilters = Factory.CreateFindPackagesOptions();
216212

217-
logger.Log("Generating filters...");
218-
// Name filter
219-
PackageMatchFilter FilterName = Factory.CreatePackageMatchFilter();
220-
FilterName.Field = filter_type;
221-
FilterName.Value = query;
222-
FilterName.Option = PackageFieldMatchOption.ContainsCaseInsensitive;
223-
PackageFilters.Filters.Add(FilterName);
213+
if (filter_type is { } field)
214+
{
215+
logger.Log("Generating filters...");
216+
// Name filter
217+
PackageMatchFilter FilterName = Factory.CreatePackageMatchFilter();
218+
FilterName.Field = field;
219+
FilterName.Value = query;
220+
FilterName.Option = PackageFieldMatchOption.ContainsCaseInsensitive;
221+
PackageFilters.Filters.Add(FilterName);
222+
}
223+
else
224+
{
225+
PackageFilters.ResultLimit = (uint)SearchLimits.MaxEmptyQueryResultsPerSource;
226+
}
224227

225228
try
226229
{

‎src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/PingetCliHelper.cs‎

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,35 @@ public IReadOnlyList<Package> GetInstalledPackages_UnSafe()
113113

114114
public IReadOnlyList<Package> FindPackages_UnSafe(string query)
115115
{
116-
SearchResponse result = RunJson<SearchResponse>(
117-
LoggableTaskType.FindPackages,
118-
$"search {Quote(query)} --output json"
119-
);
116+
if (query.Length == 0)
117+
{
118+
return Manager
119+
.SourcesHelper.GetSources()
120+
.SelectMany(SearchSourceForBrowse)
121+
.ToArray();
122+
}
123+
124+
return SearchPackages($"search {Quote(query)} --output json");
125+
}
126+
127+
private Package[] SearchSourceForBrowse(IManagerSource source)
128+
{
129+
try
130+
{
131+
return SearchPackages(
132+
$"search {Quote(string.Empty)} --source {Quote(source.Name)} --count {SearchLimits.MaxEmptyQueryResultsPerSource} --output json"
133+
);
134+
}
135+
catch (Exception ex)
136+
{
137+
Logger.Warn($"Could not list the packages of the WinGet source {source.Name}: {ex.Message}");
138+
return [];
139+
}
140+
}
141+
142+
private Package[] SearchPackages(string arguments)
143+
{
144+
SearchResponse result = RunJson<SearchResponse>(LoggableTaskType.FindPackages, arguments);
120145

121146
return result
122147
.Matches.Select(match =>

‎src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/WinGetCliHelper.cs‎

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System.Diagnostics;
22
using System.Text;
3+
using System.Text.Json;
4+
using System.Text.Json.Serialization;
35
using System.Text.RegularExpressions;
46
using UniGetUI.Core.Logging;
57
using UniGetUI.Core.Tools;
@@ -11,7 +13,7 @@
1113

1214
namespace UniGetUI.PackageEngine.Managers.WingetManager;
1315

14-
internal sealed class WinGetCliHelper : IWinGetManagerHelper
16+
internal sealed partial class WinGetCliHelper : IWinGetManagerHelper
1517
{
1618
// "winget search a" returns ~12k results; cap to the most relevant to avoid the freeze/RAM spike.
1719
private const int MAX_SEARCH_RESULTS = 100;
@@ -270,6 +272,80 @@ IEnumerable<string> outputLines
270272
}
271273

272274
public IReadOnlyList<Package> FindPackages_UnSafe(string query)
275+
{
276+
if (query.Length == 0)
277+
{
278+
return GetSourcesForBrowse()
279+
.SelectMany(source => SearchPackages(query, source, SearchLimits.MaxEmptyQueryResultsPerSource))
280+
.ToArray();
281+
}
282+
283+
return SearchPackages(query, null, MAX_SEARCH_RESULTS);
284+
}
285+
286+
private List<IManagerSource> GetSourcesForBrowse()
287+
{
288+
using var _cliLock = WinGet.AcquireCliLock();
289+
List<IManagerSource> sources = [];
290+
using Process p = new()
291+
{
292+
StartInfo = new()
293+
{
294+
FileName = _cliExecutablePath,
295+
Arguments = Manager.Status.ExecutableCallArgs + " source export",
296+
RedirectStandardOutput = true,
297+
RedirectStandardError = true,
298+
UseShellExecute = false,
299+
CreateNoWindow = true,
300+
StandardOutputEncoding = Encoding.UTF8,
301+
},
302+
};
303+
304+
IProcessTaskLogger logger = Manager.TaskLogger.CreateNew(LoggableTaskType.ListSources, p);
305+
306+
if (CoreTools.IsAdministrator())
307+
{
308+
string WinGetTemp = AppPaths.RedirectTempToElevatedDirectory(p.StartInfo);
309+
logger.AddToStdErr(
310+
$"[WARN] Redirecting %TEMP% folder to {WinGetTemp}, since UniGetUI was run as admin"
311+
);
312+
}
313+
314+
p.Start();
315+
316+
foreach (string name in ParseExportedSourceNames(ReadOutputLines(p, logger)))
317+
{
318+
sources.Add(Manager.SourcesHelper.Factory.GetSourceOrDefault(name));
319+
}
320+
321+
logger.AddToStdErr(p.StandardError.ReadToEnd());
322+
p.WaitForExit();
323+
logger.Close(p.ExitCode);
324+
return sources;
325+
}
326+
327+
internal static IEnumerable<string> ParseExportedSourceNames(IEnumerable<string> outputLines)
328+
{
329+
foreach (string line in outputLines)
330+
{
331+
string? name;
332+
try
333+
{
334+
name = JsonSerializer.Deserialize(line, WinGetCliJsonContext.Default.WinGetExportedSource)?.Name;
335+
}
336+
catch (JsonException)
337+
{
338+
continue;
339+
}
340+
341+
if (!string.IsNullOrWhiteSpace(name))
342+
{
343+
yield return name;
344+
}
345+
}
346+
}
347+
348+
private List<Package> SearchPackages(string query, IManagerSource? source, int count)
273349
{
274350
using var _cliLock = WinGet.AcquireCliLock();
275351
List<Package> Packages = [];
@@ -282,8 +358,10 @@ public IReadOnlyList<Package> FindPackages_UnSafe(string query)
282358
Manager.Status.ExecutableCallArgs
283359
+ " search \""
284360
+ query
285-
+ "\" --count "
286-
+ MAX_SEARCH_RESULTS
361+
+ "\""
362+
+ (source is null ? "" : " --source \"" + source.Name + "\"")
363+
+ " --count "
364+
+ count
287365
+ " --accept-source-agreements "
288366
+ WinGet.GetProxyArgument(),
289367
RedirectStandardOutput = true,
@@ -306,7 +384,7 @@ public IReadOnlyList<Package> FindPackages_UnSafe(string query)
306384

307385
p.Start();
308386

309-
Packages.AddRange(ParseFoundPackages(Manager, ReadOutputLines(p, logger)));
387+
Packages.AddRange(ParseFoundPackages(Manager, ReadOutputLines(p, logger), source));
310388

311389
logger.AddToStdErr(p.StandardError.ReadToEnd());
312390
p.WaitForExit();
@@ -317,7 +395,8 @@ public IReadOnlyList<Package> FindPackages_UnSafe(string query)
317395

318396
internal static IReadOnlyList<Package> ParseFoundPackages(
319397
WinGet manager,
320-
IEnumerable<string> outputLines
398+
IEnumerable<string> outputLines,
399+
IManagerSource? queriedSource = null
321400
)
322401
{
323402
List<Package> packages = [];
@@ -338,7 +417,7 @@ IEnumerable<string> outputLines
338417

339418
IManagerSource source =
340419
sourceName.Length == 0
341-
? manager.DefaultSource
420+
? queriedSource ?? manager.DefaultSource
342421
: manager.SourcesHelper.Factory.GetSourceOrDefault(sourceName);
343422

344423
packages.Add(new Package(name, id, version, source, manager));
@@ -512,4 +591,9 @@ public IReadOnlyList<IManagerSource> GetSources_UnSafe()
512591
logger.Close(p.ExitCode);
513592
return sources;
514593
}
594+
595+
internal sealed record WinGetExportedSource(string? Name);
596+
597+
[JsonSerializable(typeof(WinGetExportedSource))]
598+
internal sealed partial class WinGetCliJsonContext : JsonSerializerContext { }
515599
}

‎src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ public WinGet()
128128
SupportsCustomSources = true,
129129
SupportsCustomPackageIcons = true,
130130
SupportsCustomPackageScreenshots = true,
131+
SupportsEmptySearchQuery = true,
131132
Sources = new SourceCapabilities
132133
{
133134
KnowsPackageCount = false,

‎src/UniGetUI.PackageEngine.PackageLoader/DiscoverablePackagesLoader.cs‎

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using UniGetUI.Core.SettingsEngine;
22
using UniGetUI.Core.Tools;
33
using UniGetUI.Interface.Enums;
4+
using UniGetUI.PackageEngine.Enums;
45
using UniGetUI.PackageEngine.Interfaces;
56

67
namespace UniGetUI.PackageEngine.PackageLoader
@@ -13,7 +14,7 @@ public class DiscoverablePackagesLoader : AbstractPackageLoader
1314
// tags and renders thousands of packages, freezing the UI and spiking RAM.
1415
private const int MAX_RESULTS_PER_MANAGER = 100;
1516

16-
private string QUERY_TEXT = string.Empty;
17+
private string? QUERY_TEXT;
1718
private volatile bool _pendingReload;
1819

1920
public DiscoverablePackagesLoader(IReadOnlyList<IPackageManager> managers)
@@ -45,9 +46,15 @@ public async Task ReloadPackages(string query)
4546
await ReloadPackages();
4647
}
4748

49+
public void ResetSearch()
50+
{
51+
QUERY_TEXT = null;
52+
ClearPackages(emitFinishSignal: false);
53+
}
54+
4855
public override async Task ReloadPackages()
4956
{
50-
if (QUERY_TEXT == "")
57+
if (QUERY_TEXT is null)
5158
{
5259
return;
5360
}
@@ -76,23 +83,38 @@ public void SetManagerSearched(IPackageManager manager, bool searched)
7683
=> Settings.SetDictionaryItem(Settings.K.ExcludedSearchManagers, manager.Name, !searched);
7784

7885
protected override bool WillQueryAnyManager()
79-
=> Managers.Any(manager => manager.IsReady() && IsManagerSearched(manager));
86+
{
87+
string? query = QUERY_TEXT;
88+
return Managers.Any(manager =>
89+
manager.IsReady() && IsManagerSearched(manager) && CanSearch(manager, query));
90+
}
91+
92+
private static bool CanSearch(IPackageManager manager, string? query)
93+
=> query != string.Empty || manager.Capabilities.SupportsEmptySearchQuery;
8094

8195
protected override IReadOnlyList<IPackage> LoadPackagesFromManager(IPackageManager manager)
8296
{
83-
if (!IsManagerSearched(manager))
97+
string? query = QUERY_TEXT;
98+
if (query is null || !IsManagerSearched(manager) || !CanSearch(manager, query))
8499
{
85100
return [];
86101
}
87102

88-
string text = QUERY_TEXT;
89-
text = CoreTools.EnsureSafeQueryString(text);
90-
if (text == string.Empty)
103+
string text = CoreTools.EnsureSafeQueryString(query);
104+
if (text.Length == 0 && query.Length > 0)
91105
{
92106
return [];
93107
}
94108

95109
IReadOnlyList<IPackage> found = manager.FindPackages(text);
110+
if (text.Length == 0)
111+
{
112+
return found
113+
.GroupBy(package => package.Source.Name)
114+
.SelectMany(group => group.Take(SearchLimits.MaxEmptyQueryResultsPerSource))
115+
.ToArray();
116+
}
117+
96118
return found.Count > MAX_RESULTS_PER_MANAGER
97119
? found.Take(MAX_RESULTS_PER_MANAGER).ToArray()
98120
: found;

0 commit comments

Comments
 (0)