Files
templates/tools/dsview/install_decoders.ps1
T
Andrey a5e46d4627 Расширить обмен по шинам, CAN-адаптеры и обработку графиков
Добавить очереди операций PBUS/SBUS, общую консоль, настройки Candle и SLCAN, декодеры DSView и обработку плотных графиков. Обновить протокол Бальзам и проверки общих компонентов.
2026-10-17 14:36:14 +03:00

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.