diff --git a/CHANGELOG.md b/CHANGELOG.md index e8c3322789..aa21358cb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### Features -- **scoop-search:** Use SQLite for caching apps to speed up local search ([#5851](https://github.com/ScoopInstaller/Scoop/issues/5851), [#5918](https://github.com/ScoopInstaller/Scoop/issues/5918), [#5946](https://github.com/ScoopInstaller/Scoop/issues/5946), [#5949](https://github.com/ScoopInstaller/Scoop/issues/5949), [#5955](https://github.com/ScoopInstaller/Scoop/issues/5955), [#5966](https://github.com/ScoopInstaller/Scoop/issues/5966)) +- **scoop-search:** Use SQLite for caching apps to speed up local search ([#5851](https://github.com/ScoopInstaller/Scoop/issues/5851), [#5918](https://github.com/ScoopInstaller/Scoop/issues/5918), [#5946](https://github.com/ScoopInstaller/Scoop/issues/5946), [#5949](https://github.com/ScoopInstaller/Scoop/issues/5949), [#5955](https://github.com/ScoopInstaller/Scoop/issues/5955), [#5966](https://github.com/ScoopInstaller/Scoop/issues/5966), [#5967](https://github.com/ScoopInstaller/Scoop/issues/5967)) - **core:** New cache filename format ([#5929](https://github.com/ScoopInstaller/Scoop/issues/5929)) ### Bug Fixes diff --git a/lib/buckets.ps1 b/lib/buckets.ps1 index 2adac4f339..566cbd7119 100644 --- a/lib/buckets.ps1 +++ b/lib/buckets.ps1 @@ -172,6 +172,11 @@ function rm_bucket($name) { } Remove-Item $dir -Recurse -Force -ErrorAction Stop + if (get_config USE_SQLITE_CACHE) { + info 'Updating cache...' + Remove-ScoopDBItem -Bucket $name + } + success "The $name bucket was removed successfully." return 0 } diff --git a/lib/database.ps1 b/lib/database.ps1 index 6ca35ce1a4..ae45f46dc1 100644 --- a/lib/database.ps1 +++ b/lib/database.ps1 @@ -79,6 +79,7 @@ function Open-ScoopDB { suggest TEXT, PRIMARY KEY (name, version, bucket) )" + $tableCommand.CommandType = [System.Data.CommandType]::Text $tableCommand.ExecuteNonQuery() | Out-Null $tableCommand.Dispose() return $db @@ -88,7 +89,7 @@ function Open-ScoopDB { .SYNOPSIS Set Scoop database item(s). .DESCRIPTION - Insert or replace Scoop database item(s) into the database. + Insert or replace item(s) into the Scoop SQLite database. .PARAMETER InputObject System.Object[] The database item(s) to insert or replace. @@ -113,6 +114,7 @@ function Set-ScoopDBItem { $dbQuery = "INSERT OR REPLACE INTO app ($($colName -join ', ')) VALUES ($('@' + ($colName -join ', @')))" $dbCommand = $db.CreateCommand() $dbCommand.CommandText = $dbQuery + $dbCommand.CommandType = [System.Data.CommandType]::Text } process { foreach ($item in $InputObject) { @@ -161,7 +163,7 @@ function Set-ScoopDB { ) begin { - $list = [System.Collections.Generic.List[PSCustomObject]]::new() + $list = [System.Collections.Generic.List[psobject]]::new() $arch = Get-DefaultArchitecture } process { @@ -220,11 +222,14 @@ function Set-ScoopDB { .SYNOPSIS Select Scoop database item(s). .DESCRIPTION - Select Scoop database item(s) from the database. + Select item(s) from the Scoop SQLite database. The pattern is matched against the name, binaries, and shortcuts columns for apps. .PARAMETER Pattern System.String The pattern to search for. If is an empty string, all items will be returned. +.PARAMETER From + System.String[] + The fields to search from. .INPUTS System.String .OUTPUTS @@ -239,6 +244,7 @@ function Select-ScoopDBItem { [string] $Pattern, [Parameter(Mandatory, Position = 1)] + [ValidateNotNullOrEmpty()] [string[]] $From ) @@ -252,10 +258,10 @@ function Select-ScoopDBItem { $dbCommand = $db.CreateCommand() $dbCommand.CommandText = $dbQuery $dbCommand.CommandType = [System.Data.CommandType]::Text + $dbAdapter.SelectCommand = $dbCommand } process { $dbCommand.Parameters.AddWithValue('@Pattern', $(if ($Pattern -eq '') { '%' } else { '%' + $Pattern + '%' })) | Out-Null - $dbAdapter.SelectCommand = $dbCommand [void]$dbAdapter.Fill($result) } end { @@ -269,7 +275,7 @@ function Select-ScoopDBItem { .SYNOPSIS Get Scoop database item. .DESCRIPTION - Get Scoop database item from the database. + Get item from the Scoop SQLite database. .PARAMETER Name System.String The name of the item to get. @@ -312,12 +318,12 @@ function Get-ScoopDBItem { $dbCommand = $db.CreateCommand() $dbCommand.CommandText = $dbQuery $dbCommand.CommandType = [System.Data.CommandType]::Text + $dbAdapter.SelectCommand = $dbCommand } process { $dbCommand.Parameters.AddWithValue('@Name', $Name) | Out-Null $dbCommand.Parameters.AddWithValue('@Bucket', $Bucket) | Out-Null $dbCommand.Parameters.AddWithValue('@Version', $Version) | Out-Null - $dbAdapter.SelectCommand = $dbCommand [void]$dbAdapter.Fill($result) } end { @@ -326,3 +332,60 @@ function Get-ScoopDBItem { return $result } } + +<# +.SYNOPSIS + Remove Scoop database item(s). +.DESCRIPTION + Remove item(s) from the Scoop SQLite database. +.PARAMETER Name + System.String + The name of the item to remove. +.PARAMETER Bucket + System.String + The bucket of the item to remove. +.INPUTS + System.String +.OUTPUTS + None +#> +function Remove-ScoopDBItem { + [CmdletBinding()] + param ( + [Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] + [string] + $Name, + [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)] + [string] + $Bucket + ) + + begin { + $db = Open-ScoopDB + $dbTrans = $db.BeginTransaction() + $dbQuery = 'DELETE FROM app WHERE bucket = @Bucket' + $dbCommand = $db.CreateCommand() + $dbCommand.CommandText = $dbQuery + $dbCommand.CommandType = [System.Data.CommandType]::Text + } + process { + $dbCommand.Parameters.AddWithValue('@Bucket', $Bucket) | Out-Null + if ($Name) { + $dbCommand.CommandText = $dbQuery + ' AND name = @Name' + $dbCommand.Parameters.AddWithValue('@Name', $Name) | Out-Null + } + $dbCommand.ExecuteNonQuery() | Out-Null + } + end { + try { + $dbTrans.Commit() + } catch { + $dbTrans.Rollback() + throw $_ + } finally { + $dbCommand.Dispose() + $dbTrans.Dispose() + $db.Dispose() + } + } +} diff --git a/libexec/scoop-update.ps1 b/libexec/scoop-update.ps1 index 79bd706a65..f8e010de06 100644 --- a/libexec/scoop-update.ps1 +++ b/libexec/scoop-update.ps1 @@ -181,7 +181,8 @@ function Sync-Bucket { $buckets | Where-Object { !$_.valid } | ForEach-Object { Write-Host "'$($_.name)' is not a git repository. Skipped." } - $updatedFiles = [System.Collections.ArrayList]::Synchronized([System.Collections.ArrayList]::new()) + $updatedFiles = [System.Collections.Generic.SynchronizedCollection[string]]::new() + $removedFiles = [System.Collections.Generic.SynchronizedCollection[psobject]]::new() if ($PSVersionTable.PSVersion.Major -ge 7) { # Parallel parameter is available since PowerShell 7 $buckets | Where-Object { $_.valid } | ForEach-Object -ThrottleLimit 5 -Parallel { @@ -198,10 +199,21 @@ function Sync-Bucket { Invoke-GitLog -Path $bucketLoc -Name $name -CommitHash $previousCommit } if (get_config USE_SQLITE_CACHE) { - Invoke-Git -Path $bucketLoc -ArgumentList @('diff', '--name-only', '--diff-filter=d', $previousCommit) | ForEach-Object { - $filePath = Join-Path $bucketLoc $_ + Invoke-Git -Path $bucketLoc -ArgumentList @('diff', '--name-status', $previousCommit) | ForEach-Object { + $status, $file = $_ -split '\s+', 2 + $filePath = Join-Path $bucketLoc $file if ($filePath -match "^$([regex]::Escape($innerBucketLoc)).*\.json$") { - [void]($using:updatedFiles).Add($filePath) + switch ($status) { + { $_ -in 'A', 'M', 'R' } { + [void]($using:updatedFiles).Add($filePath) + } + 'D' { + [void]($using:removedFiles).Add([pscustomobject]@{ + Name = ([System.IO.FileInfo]$file).BaseName + Bucket = $name + }) + } + } } } } @@ -218,18 +230,30 @@ function Sync-Bucket { Invoke-GitLog -Path $bucketLoc -Name $name -CommitHash $previousCommit } if (get_config USE_SQLITE_CACHE) { - Invoke-Git -Path $bucketLoc -ArgumentList @('diff', '--name-only', '--diff-filter=d', $previousCommit) | ForEach-Object { - $filePath = Join-Path $bucketLoc $_ + Invoke-Git -Path $bucketLoc -ArgumentList @('diff', '--name-status', $previousCommit) | ForEach-Object { + $status, $file = $_ -split '\s+', 2 + $filePath = Join-Path $bucketLoc $file if ($filePath -match "^$([regex]::Escape($innerBucketLoc)).*\.json$") { - [void]($updatedFiles).Add($filePath) + switch ($status) { + { $_ -in 'A', 'M', 'R' } { + [void]($updatedFiles).Add($filePath) + } + 'D' { + [void]($removedFiles).Add([pscustomobject]@{ + Name = ([System.IO.FileInfo]$file).BaseName + Bucket = $name + }) + } + } } } } } } - if ((get_config USE_SQLITE_CACHE) -and ($updatedFiles.Count -gt 0)) { + if ((get_config USE_SQLITE_CACHE) -and ($updatedFiles.Count -gt 0 -or $removedFiles.Count -gt 0)) { info 'Updating cache...' Set-ScoopDB -Path $updatedFiles + $removedFiles | Remove-ScoopDBItem } }