diff --git a/.gitignore b/.gitignore index 67ecc85d82..932c35449e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.log .DS_Store ._.DS_Store +.lock scoop.sublime-workspace test/installer/tmp/* test/tmp/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 15dcae7f20..e08a0dcff3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - **install:** Add separator at the end of notes, highlight suggestions ([#6418](https://github.com/ScoopInstaller/Scoop/issues/6418)) - **download|scoop-download:** Add GitHub issue prompt when the default downloader fails ([#6539](https://github.com/ScoopInstaller/Scoop/issues/6539)) - **download|scoop-config:** Allow disabling automatic fallback to the default downloader when Aria2c download fails ([#6538](https://github.com/ScoopInstaller/Scoop/issues/6538)) +- **core:** Add file lock to prevent concurrent executions ([#6557](https://github.com/ScoopInstaller/Scoop/issues/6557)) ### Bug Fixes diff --git a/bin/scoop.ps1 b/bin/scoop.ps1 index 036a05ba29..f17de55ed7 100644 --- a/bin/scoop.ps1 +++ b/bin/scoop.ps1 @@ -6,48 +6,74 @@ Set-StrictMode -Off . "$PSScriptRoot\..\lib\commands.ps1" . "$PSScriptRoot\..\lib\help.ps1" -$subCommand = $Args[0] +$lock_file_path = "$PSScriptRoot\..\.lock" +$lock_stream = $null +$lock_show_waiting_message = $true -# for aliases where there's a local function, re-alias so the function takes precedence -$aliases = Get-Alias | Where-Object { $_.Options -notmatch 'ReadOnly|AllScope' } | ForEach-Object { $_.Name } -Get-ChildItem Function: | Where-Object -Property Name -In -Value $aliases | ForEach-Object { - Set-Alias -Name $_.Name -Value Local:$($_.Name) -Scope Script +while (-not $lock_stream) { + try { + $lock_stream = [System.IO.File]::Open($lock_file_path, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write) + } catch [System.IO.IOException] { + # Only deal with ERROR_SHARING_VIOLATION or ERROR_LOCK_VIOLATION. + $error_code = $_.Exception.HResult -band 0xFFFF + if ($error_code -notin 32, 33) { + throw + } + + if ($lock_show_waiting_message) { + Write-Host 'Waiting for exclusive access...' + $lock_show_waiting_message = $false + } + Start-Sleep -Seconds 1 + } } -switch ($subCommand) { - ({ $subCommand -in @($null, '-h', '--help', '/?') }) { - exec 'help' +try { + $subCommand = $Args[0] + + # for aliases where there's a local function, re-alias so the function takes precedence + $aliases = Get-Alias | Where-Object { $_.Options -notmatch 'ReadOnly|AllScope' } | ForEach-Object { $_.Name } + Get-ChildItem Function: | Where-Object -Property Name -In -Value $aliases | ForEach-Object { + Set-Alias -Name $_.Name -Value Local:$($_.Name) -Scope Script } - ({ $subCommand -in @('-v', '--version') }) { - Write-Host 'Current Scoop version:' - if ((Test-GitAvailable) -and (Test-Path "$PSScriptRoot\..\.git") -and ((get_config SCOOP_BRANCH 'master') -ne 'master')) { - Invoke-Git -Path "$PSScriptRoot\.." -ArgumentList @('--no-pager', 'log', 'HEAD', '-1', '--oneline') - } else { - $version = Select-String -Pattern '^## \[(v[\d.]+)\].*?([\d-]+)$' -Path "$PSScriptRoot\..\CHANGELOG.md" - Write-Host $version.Matches.Groups[1].Value -ForegroundColor Cyan -NoNewline - Write-Host " - Released at $($version.Matches.Groups[2].Value)" + + switch ($subCommand) { + ({ $subCommand -in @($null, '-h', '--help', '/?') }) { + exec 'help' } - Write-Host '' - - Get-LocalBucket | ForEach-Object { - $bucketLoc = Find-BucketDirectory $_ -Root - if ((Test-GitAvailable) -and (Test-Path "$bucketLoc\.git")) { - Write-Host "'$_' bucket:" - Invoke-Git -Path $bucketLoc -ArgumentList @('--no-pager', 'log', 'HEAD', '-1', '--oneline') - Write-Host '' + ({ $subCommand -in @('-v', '--version') }) { + Write-Host 'Current Scoop version:' + if ((Test-GitAvailable) -and (Test-Path "$PSScriptRoot\..\.git") -and ((get_config SCOOP_BRANCH 'master') -ne 'master')) { + Invoke-Git -Path "$PSScriptRoot\.." -ArgumentList @('--no-pager', 'log', 'HEAD', '-1', '--oneline') + } else { + $version = Select-String -Pattern '^## \[(v[\d.]+)\].*?([\d-]+)$' -Path "$PSScriptRoot\..\CHANGELOG.md" + Write-Host $version.Matches.Groups[1].Value -ForegroundColor Cyan -NoNewline + Write-Host " - Released at $($version.Matches.Groups[2].Value)" + } + Write-Host '' + + Get-LocalBucket | ForEach-Object { + $bucketLoc = Find-BucketDirectory $_ -Root + if ((Test-GitAvailable) -and (Test-Path "$bucketLoc\.git")) { + Write-Host "'$_' bucket:" + Invoke-Git -Path $bucketLoc -ArgumentList @('--no-pager', 'log', 'HEAD', '-1', '--oneline') + Write-Host '' + } } } - } - ({ $subCommand -in (commands) }) { - [string[]]$arguments = $Args | Select-Object -Skip 1 - if ($null -ne $arguments -and $arguments[0] -in @('-h', '--help', '/?')) { - exec 'help' @($subCommand) - } else { - exec $subCommand $arguments + ({ $subCommand -in (commands) }) { + [string[]]$arguments = $Args | Select-Object -Skip 1 + if ($null -ne $arguments -and $arguments[0] -in @('-h', '--help', '/?')) { + exec 'help' @($subCommand) + } else { + exec $subCommand $arguments + } + } + default { + warn "scoop: '$subCommand' isn't a scoop command. See 'scoop help'." + exit 1 } } - default { - warn "scoop: '$subCommand' isn't a scoop command. See 'scoop help'." - exit 1 - } +} finally { + $lock_stream.Dispose() }