From b3c05e71fac58f20f90ea4ae2a0f86fa0f6444ed Mon Sep 17 00:00:00 2001 From: Richard Kuhnt Date: Tue, 3 Oct 2023 16:05:45 +0200 Subject: [PATCH] perf(scoop-search): Improve performance for local search (#5644) * perf(search): improve local search performance * Update libexec/scoop-search.ps1 Co-authored-by: Hsiao-nan Cheung * Update libexec/scoop-search.ps1 Co-authored-by: Hsiao-nan Cheung * Update libexec/scoop-search.ps1 Co-authored-by: Hsiao-nan Cheung * Update libexec/scoop-search.ps1 Co-authored-by: Hsiao-nan Cheung * Update libexec/scoop-search.ps1 Co-authored-by: Hsiao-nan Cheung * Update libexec/scoop-search.ps1 Co-authored-by: Hsiao-nan Cheung * Added [JsonDocument]::Parse for testing * Fix array length check * Used wrong function * Add fallback function for PowerShell 5 * Check for System.Text.Json in Assemblies instead * Show help output * Revert "Show help output" This reverts commit d3d6b01d0846d7c82fec0cbae5b898bdc1426ef9. * Update CHANGELOG.md --------- Co-authored-by: Hsiao-nan Cheung Co-authored-by: Rashil Gandhi <46838874+rashil2000@users.noreply.github.com> --- CHANGELOG.md | 3 +- libexec/scoop-search.ps1 | 118 +++++++++++++++++++++++++++++---------- 2 files changed, 90 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b020ab2fda..355360824a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,13 +29,14 @@ - **core:** Use relative path as fallback of `$scoopdir` ([#5544](https://github.com/ScoopInstaller/Scoop/issues/5544)) - **scoop-checkup:** Skip defender check in Windows Sandbox ([#5519](https://github.com/ScoopInstaller/Scoop/issues/5519)) - **buckets:** Avoid error messages for unexpected dir ([#5549](https://github.com/ScoopInstaller/Scoop/issues/5549)) -- **scoop-virustotal**: Fix `scoop-virustotal` when `--all' has been passed without app ([#5593](https://github.com/ScoopInstaller/Scoop/pull/5593)) +- **scoop-virustotal:** Fix `scoop-virustotal` when `--all` has been passed without app ([#5593](https://github.com/ScoopInstaller/Scoop/pull/5593)) - **scoop-checkup:** Change the message level of helpers from ERROR to WARN ([#5549](https://github.com/ScoopInstaller/Scoop/issues/5614)) - **scoop-(un)hold:** Correct output the messages when manifest not found, (already|not) held ([#5519](https://github.com/ScoopInstaller/Scoop/issues/5519)) ### Performance Improvements - **decompress:** Disable progress bar to improve `Expand-Archive` performance ([#5410](https://github.com/ScoopInstaller/Scoop/issues/5410)) +- **scoop-search:** Improve performance for local search ([#5324](https://github.com/ScoopInstaller/Scoop/issues/5324)) ### Code Refactoring diff --git a/libexec/scoop-search.ps1 b/libexec/scoop-search.ps1 index adef8b1835..a9013afd02 100644 --- a/libexec/scoop-search.ps1 +++ b/libexec/scoop-search.ps1 @@ -9,7 +9,7 @@ param($query) . "$PSScriptRoot\..\lib\manifest.ps1" # 'manifest' . "$PSScriptRoot\..\lib\versions.ps1" # 'Get-LatestVersion' -$list = @() +$list = [System.Collections.Generic.List[PSCustomObject]]::new() try { $query = New-Object Regex $query, 'IgnoreCase' @@ -32,24 +32,90 @@ function bin_match($manifest, $query) { if ((strip_ext $fname) -match $query) { $fname } elseif ($alias -match $query) { $alias } } + + if ($bins) { return $bins } + else { return $false } +} + +function bin_match_json($json, $query) { + [System.Text.Json.JsonElement]$bin = [System.Text.Json.JsonElement]::new() + if (!$json.RootElement.TryGetProperty("bin", [ref] $bin)) { return $false } + $bins = @() + if($bin.ValueKind -eq [System.Text.Json.JsonValueKind]::String -and [System.IO.Path]::GetFileNameWithoutExtension($bin) -match $query) { + $bins += [System.IO.Path]::GetFileName($bin) + } elseif ($bin.ValueKind -eq [System.Text.Json.JsonValueKind]::Array) { + foreach($subbin in $bin.EnumerateArray()) { + if($subbin.ValueKind -eq [System.Text.Json.JsonValueKind]::String -and [System.IO.Path]::GetFileNameWithoutExtension($subbin) -match $query) { + $bins += [System.IO.Path]::GetFileName($subbin) + } elseif ($subbin.ValueKind -eq [System.Text.Json.JsonValueKind]::Array) { + if([System.IO.Path]::GetFileNameWithoutExtension($subbin[0]) -match $query) { + $bins += [System.IO.Path]::GetFileName($subbin[0]) + } elseif ($subbin.GetArrayLength() -ge 2 -and $subbin[1] -match $query) { + $bins += $subbin[1] + } + } + } + } + if ($bins) { return $bins } else { return $false } } function search_bucket($bucket, $query) { - $apps = apps_in_bucket (Find-BucketDirectory $bucket) | ForEach-Object { @{ name = $_ } } + $apps = Get-ChildItem (Find-BucketDirectory $bucket) -Filter '*.json' -Recurse + + $apps | ForEach-Object { + $json = [System.Text.Json.JsonDocument]::Parse([System.IO.File]::ReadAllText($_.FullName)) + $name = $_.BaseName + + if ($name -match $query) { + $list.Add([PSCustomObject]@{ + Name = $name + Version = $json.RootElement.GetProperty("version") + Source = $bucket + Binaries = "" + }) + } else { + $bin = bin_match_json $json $query + if ($bin) { + $list.Add([PSCustomObject]@{ + Name = $name + Version = $json.RootElement.GetProperty("version") + Source = $bucket + Binaries = $bin -join ' | ' + }) + } + } + } +} - if ($query) { - $apps = $apps | Where-Object { - if ($_.name -match $query) { return $true } - $bin = bin_match (manifest $_.name $bucket) $query +# fallback function for PowerShell 5 +function search_bucket_legacy($bucket, $query) { + $apps = Get-ChildItem (Find-BucketDirectory $bucket) -Filter '*.json' -Recurse + + $apps | ForEach-Object { + $manifest = [System.IO.File]::ReadAllText($_.FullName) | ConvertFrom-Json -ErrorAction Continue + $name = $_.BaseName + + if ($name -match $query) { + $list.Add([PSCustomObject]@{ + Name = $name + Version = $manifest.Version + Source = $bucket + Binaries = "" + }) + } else { + $bin = bin_match $manifest $query if ($bin) { - $_.bin = $bin - return $true + $list.Add([PSCustomObject]@{ + Name = $name + Version = $manifest.Version + Source = $bucket + Binaries = $bin -join ' | ' + }) } } } - $apps | ForEach-Object { $_.version = (Get-LatestVersion -AppName $_.name -Bucket $bucket); $_ } } function download_json($url) { @@ -96,43 +162,35 @@ function search_remotes($query) { (add them using 'scoop bucket add ')" } + $remote_list = @() $results | ForEach-Object { - $name = $_.bucket + $bucket = $_.bucket $_.results | ForEach-Object { $item = [ordered]@{} $item.Name = $_ - $item.Source = $name - $list += [PSCustomObject]$item + $item.Source = $bucket + $remote_list += [PSCustomObject]$item } } - - $list + $remote_list } -Get-LocalBucket | ForEach-Object { - $res = search_bucket $_ $query - $local_results = $local_results -or $res - if ($res) { - $name = "$_" +$jsonTextAvailable = [System.AppDomain]::CurrentDomain.GetAssemblies() | Where-object { [System.IO.Path]::GetFileNameWithoutExtension($_.Location) -eq "System.Text.Json" } - $res | ForEach-Object { - $item = [ordered]@{} - $item.Name = $_.name - $item.Version = $_.version - $item.Source = $name - $item.Binaries = "" - if ($_.bin) { $item.Binaries = $_.bin -join ' | ' } - $list += [PSCustomObject]$item - } +Get-LocalBucket | ForEach-Object { + if ($jsonTextAvailable) { + search_bucket $_ $query + } else { + search_bucket_legacy $_ $query } } -if ($list.Length -gt 0) { +if ($list.Count -gt 0) { Write-Host "Results from local buckets..." $list } -if (!$local_results -and !(github_ratelimit_reached)) { +if ($list.Count -eq 0 -and !(github_ratelimit_reached)) { $remote_results = search_remotes $query if (!$remote_results) { warn "No matches found."