Добавить очереди операций PBUS/SBUS, общую консоль, настройки Candle и SLCAN, декодеры DSView и обработку плотных графиков. Обновить протокол Бальзам и проверки общих компонентов.
181 lines
12 KiB
PowerShell
181 lines
12 KiB
PowerShell
# Установка декодеров в найденный каталог DSView и проверка размещения. Режим CheckOnly
|
|
# проверяет состояние установки; фактическое копирование зависит от выбранного режима и
|
|
# доступности каталогов назначения.
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$DsViewPath = (Join-Path $env:ProgramFiles 'DSView'),
|
|
[switch]$CheckOnly
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
Set-StrictMode -Version 2
|
|
# Resolve source paths relative to templates, never the caller working directory.
|
|
# Standalone DSView and SETGUI use the same adapters and parsing cores.
|
|
$templateRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
|
$targetRoot = [IO.Path]::GetFullPath($DsViewPath)
|
|
$decoderRoot = Join-Path $targetRoot 'decoders'
|
|
|
|
# Use .NET for compatibility with PowerShell lacking Get-FileHash.
|
|
# Release both stream and hashing provider even if reading fails.
|
|
function Get-ContentHash([string]$Path) {
|
|
$algorithm = [Security.Cryptography.SHA256]::Create()
|
|
$stream = [IO.File]::OpenRead($Path)
|
|
try {
|
|
return [BitConverter]::ToString($algorithm.ComputeHash($stream))
|
|
} finally {
|
|
$stream.Dispose()
|
|
$algorithm.Dispose()
|
|
}
|
|
}
|
|
|
|
# Check all existing ancestors: a junction under decoders/common could
|
|
# redirect a local-looking update outside the chosen installation.
|
|
function Assert-PlainPath([string]$Path) {
|
|
$cursor = [IO.Path]::GetFullPath($Path)
|
|
$first = $true
|
|
while ($cursor) {
|
|
if (Test-Path -LiteralPath $cursor) {
|
|
$item = Get-Item -LiteralPath $cursor -Force
|
|
if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
|
throw "Symbolic links and junctions are not supported: $cursor"
|
|
}
|
|
if (-not $first -and -not $item.PSIsContainer) {
|
|
throw "Expected a parent directory: $cursor"
|
|
}
|
|
}
|
|
$parent = [IO.Path]::GetDirectoryName($cursor)
|
|
if ($parent -eq $cursor) { break }
|
|
$cursor = $parent
|
|
$first = $false
|
|
}
|
|
}
|
|
|
|
Assert-PlainPath $targetRoot
|
|
$exe = Join-Path $targetRoot 'DSView.exe'
|
|
if (-not (Test-Path -LiteralPath $exe -PathType Leaf)) {
|
|
throw "DSView.exe not found in '$targetRoot'. Pass -DsViewPath with the installed DSView directory."
|
|
}
|
|
if (-not (Test-Path -LiteralPath (Join-Path $decoderRoot 'common') -PathType Container)) {
|
|
throw "DSView decoders/common is missing. Select a complete DSView installation."
|
|
}
|
|
|
|
# An explicit allowlist excludes tests, bytecode and unrelated modules.
|
|
# The private common/setgui_decoders layout matches the embedded build.
|
|
$files = @()
|
|
$manifest = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'protocol-files.json') -Encoding UTF8 -Raw | ConvertFrom-Json # Общая ведомость совпадает с рецептом встроенного DSView.
|
|
foreach ($name in $manifest.adapters) { # Список включает все поставляемые анализаторы без отдельного перечня в установщике.
|
|
foreach ($leaf in @('__init__.py', 'pd.py')) {
|
|
$files += [pscustomobject]@{
|
|
Source = Join-Path $PSScriptRoot "decoders/$name/$leaf"
|
|
Relative = "$name/$leaf"
|
|
}
|
|
}
|
|
}
|
|
foreach ($name in $manifest.cores) { # Общие ядра и адаптеры устанавливаются из одной закреплённой версии templates.
|
|
$files += [pscustomobject]@{
|
|
Source = Join-Path $templateRoot "python/logic_analyzer/decoders/$name.py"
|
|
Relative = "common/setgui_decoders/$name.py"
|
|
}
|
|
}
|
|
foreach ($name in $manifest.template_files) { # Подключённым парсерам нужны исходные модули и JSON-словари протоколов.
|
|
$files += [pscustomobject]@{ # Новые зависимости проходят те же проверки, резервное копирование и откат, что существующие ядра.
|
|
Source = Join-Path $templateRoot "python/$name" # Источник всегда находится в общем templates, независимо от текущего каталога.
|
|
Relative = "common/setgui_decoders/templates/$name" # Закрытый каталог не создаёт лишних анализаторов в списке sigrok.
|
|
} # Исходные пакеты сохраняют собственные относительные импорты и не требуют внешнего Python.
|
|
} # Все зависимости включены в транзакцию до первого изменения установленного декодера.
|
|
# Validate the entire payload and destination before changing any decoder.
|
|
foreach ($file in $files) {
|
|
if (-not (Test-Path -LiteralPath $file.Source -PathType Leaf)) {
|
|
throw "Missing templates source: $($file.Source). Initialize the templates submodule."
|
|
}
|
|
$destination = Join-Path $decoderRoot $file.Relative
|
|
Assert-PlainPath $destination
|
|
if (Test-Path -LiteralPath $destination -PathType Container) {
|
|
throw "Expected a file, found a directory: $destination"
|
|
}
|
|
if ([IO.Path]::GetFullPath($file.Source) -eq [IO.Path]::GetFullPath($destination)) {
|
|
throw 'The installation destination must differ from the templates source.'
|
|
}
|
|
}
|
|
# Read-only validation does not create backups or check elevated write access.
|
|
# Real installation performs its own write-access check before copying.
|
|
if ($CheckOnly) {
|
|
Write-Output "Validated $($files.Count) files for $targetRoot. No files changed."
|
|
return
|
|
}
|
|
# Проверяем запущенный процесс отдельно: установка декодеров не требует права записи в DSView.exe.
|
|
foreach ($process in @(Get-Process -Name 'DSView' -ErrorAction SilentlyContinue)) { # Другие экземпляры DSView не мешают обновлению выбранного каталога.
|
|
try { # Доступ к пути процесса может быть ограничен правами владельца.
|
|
$processPath = $process.MainModule.FileName # Путь определяет принадлежность процесса конкретной установке.
|
|
} catch { # Без пути нельзя достоверно исключить работу целевого компонента.
|
|
throw 'Cannot inspect a running DSView process. Close DSView before installing decoders.' # Установщик не завершает процесс и не прерывает захват.
|
|
} # Ошибка просмотра процесса отделена от отсутствия права копировать файлы.
|
|
if ($processPath -and [string]::Equals([IO.Path]::GetFullPath($processPath), $exe, [StringComparison]::OrdinalIgnoreCase)) { # Регистр Windows-пути не влияет на совпадение.
|
|
throw "Close DSView before updating decoders in '$targetRoot'." # После закрытия требуется повторный запуск того же установщика.
|
|
} # Работа другой установки DSView допускается.
|
|
} # Проверка выполняется только при настоящем копировании, после CheckOnly.
|
|
|
|
# Пробные файлы проверяют реальные каталоги назначения и резервных копий, не затрагивая EXE.
|
|
foreach ($directory in @($targetRoot, $decoderRoot)) { # Резервные копии создаются рядом с EXE, а новые модули — внутри decoders.
|
|
$probe = Join-Path $directory ('.set-decoder-write-' + [guid]::NewGuid().ToString('N') + '.tmp') # Уникальное имя исключает перезапись пользовательского файла.
|
|
$handle = $null # Дескриптор освобождается и при неудачном создании файла.
|
|
try { # Права записи проверяются до резервного копирования и замены первого декодера.
|
|
$handle = [IO.File]::Open($probe, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None) # Пробный файл проверяет создание в выбранном каталоге.
|
|
} catch { # Защищённый Program Files требует повышения прав либо выбора доступной установки.
|
|
throw "No write access to '$directory'. Select a writable DSView directory or run PowerShell as administrator for Program Files. $($_.Exception.Message)" # Сообщение называет каталог, а не ошибочно запущенный EXE.
|
|
} finally { # Временные проверки не должны оставлять файлы после успешного или неуспешного запуска.
|
|
if ($null -ne $handle) { $handle.Dispose() } # Закрываем только дескриптор созданного нами файла.
|
|
if ([IO.File]::Exists($probe)) { [IO.File]::Delete($probe) } # Удаляется только уникальный файл текущей проверки, без обхода каталогов.
|
|
} # Исходные декодеры на этом этапе ещё не изменены.
|
|
} # Дальнейшая транзакция сохраняет существующую политику резервного копирования и отката.
|
|
|
|
# Keep each previous version outside decoder discovery directories.
|
|
# Retain backups after success for manual recovery, not only after errors.
|
|
$backup = Join-Path $targetRoot ('set-decoder-backups/' + (Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + [guid]::NewGuid().ToString('N'))
|
|
Assert-PlainPath $backup
|
|
# Back up all existing files before writing the first replacement.
|
|
foreach ($file in $files) {
|
|
$destination = Join-Path $decoderRoot $file.Relative
|
|
if (Test-Path -LiteralPath $destination -PathType Leaf) {
|
|
$saved = Join-Path $backup $file.Relative
|
|
[IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($saved)) | Out-Null
|
|
Copy-Item -LiteralPath $destination -Destination $saved
|
|
}
|
|
}
|
|
# Track files before copying: a failed copy may leave a partial destination.
|
|
# Rollback must include that file as well as completed replacements.
|
|
$attempted = @()
|
|
try {
|
|
foreach ($file in $files) {
|
|
$destination = Join-Path $decoderRoot $file.Relative
|
|
[IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($destination)) | Out-Null
|
|
$attempted += $file
|
|
Copy-Item -LiteralPath $file.Source -Destination $destination -Force
|
|
if ((Get-ContentHash $file.Source) -ne (Get-ContentHash $destination)) {
|
|
throw "Copy verification failed: $destination"
|
|
}
|
|
}
|
|
} catch {
|
|
$failure = $_
|
|
# Restore overwritten files and remove only new files from our allowlist.
|
|
# Never recursively delete decoder folders or remove unrelated analyzers.
|
|
foreach ($file in $attempted) {
|
|
$destination = Join-Path $decoderRoot $file.Relative
|
|
$saved = Join-Path $backup $file.Relative
|
|
try {
|
|
if (Test-Path -LiteralPath $saved -PathType Leaf) {
|
|
Copy-Item -LiteralPath $saved -Destination $destination -Force
|
|
} elseif (Test-Path -LiteralPath $destination -PathType Leaf) {
|
|
Remove-Item -LiteralPath $destination -Force
|
|
}
|
|
} catch {
|
|
Write-Warning "Could not restore $destination. Retained backup: $backup"
|
|
}
|
|
}
|
|
throw $failure
|
|
}
|
|
Write-Output "Installed: $($manifest.adapters -join ', ') in $decoderRoot" # Список формируется по общей ведомости, включая Plates PBus и Plates SBus.
|
|
if (Test-Path -LiteralPath $backup) { Write-Output "Previous files retained in: $backup" }
|
|
Write-Output 'Restart DSView. Plates PBus/SBus use logic channels directly; SET UART/CAN decoders stack over UART/CAN. No external Python is required.' # Подключение шинных анализаторов отличается от надстроек над UART/CAN.
|